From ce38e64ee73b7339f5aaff07a52fa1dd48f59133 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Tue, 17 Mar 2026 15:43:07 -0700 Subject: [PATCH 001/298] Add release notes for 7.0 (#4053) --- .github/prompts/release-notes.prompt.md | 4 + CHANGELOG.md | 131 ++++++ release-notes/7.0/7.0.0.md | 410 ++++++++++++++++++ release-notes/7.0/README.md | 1 + .../Extensions/Abstractions/1.0/1.0.0.md | 30 ++ .../Extensions/Abstractions/1.0/README.md | 1 + release-notes/Extensions/Azure/1.0/1.0.0.md | 66 +++ release-notes/Extensions/Azure/1.0/README.md | 1 + .../Extensions/Logging/1.0/README.md | 3 + release-notes/Internal/Logging/1.0/1.0.0.md | 37 ++ release-notes/Internal/Logging/1.0/README.md | 11 + release-notes/README.md | 11 +- .../AzureKeyVaultProvider/7.0/7.0.0.md | 54 +++ .../AzureKeyVaultProvider/7.0/README.md | 1 + 14 files changed, 756 insertions(+), 5 deletions(-) create mode 100644 release-notes/7.0/7.0.0.md create mode 100644 release-notes/Extensions/Abstractions/1.0/1.0.0.md create mode 100644 release-notes/Extensions/Azure/1.0/1.0.0.md create mode 100644 release-notes/Internal/Logging/1.0/1.0.0.md create mode 100644 release-notes/Internal/Logging/1.0/README.md create mode 100644 release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.0.md diff --git a/.github/prompts/release-notes.prompt.md b/.github/prompts/release-notes.prompt.md index ca03933236..55cc7785e2 100644 --- a/.github/prompts/release-notes.prompt.md +++ b/.github/prompts/release-notes.prompt.md @@ -86,6 +86,10 @@ For each package that has relevant PRs in the milestone: - Look up dependencies using the Dependency Sources from the lookup table above. Resolve concrete versions from [Directory.Packages.props](Directory.Packages.props). - List dependencies per target framework. Use the project file's `` to determine which frameworks to list. - Omit the Contributors section for packages with no public contributors. + - **GA releases (all packages):** When the release is a stable (non-preview) version, structure the notes with two sections: + 1. **"Changes Since [last preview]"** — only the delta since the most recent preview of this package. + 2. **"Cumulative Changes Since [last stable]"** — all changes since the last stable release of this package, synthesized from all preview release notes plus the GA milestone. This applies to every package (MDS, AKV, Extensions.Azure, Abstractions, Internal.Logging, etc.), not just the core driver. Apply the cross-referencing from Step 3 to eliminate items already shipped in prior stable patch releases. + - **Preview releases:** Only include the delta since the previous release (preview or stable). No cumulative section is needed. 3. **Create or update the version README** at `/README.md`. Follow the existing format — see [release-notes/add-ons/AzureKeyVaultProvider/6.1/README.md](release-notes/add-ons/AzureKeyVaultProvider/6.1/README.md) for reference: diff --git a/CHANGELOG.md b/CHANGELOG.md index dc51c7d2bc..fedf1ad568 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,137 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) > **Note:** Releases are sorted in reverse chronological order (newest first). +## [Stable Release 7.0.0] - 2026-03-17 + +This section summarizes all changes across the 7.0 preview cycle for users upgrading from the latest 6.1 stable release. +See the [full release notes](release-notes/7.0/7.0.0.md) for detailed descriptions. + +Also released as part of this milestone: +- Released Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0. See [release notes](release-notes/Extensions/Abstractions/1.0/1.0.0.md). +- Released Microsoft.Data.SqlClient.Extensions.Azure 1.0.0. See [release notes](release-notes/Extensions/Azure/1.0/1.0.0.md). +- Released Microsoft.Data.SqlClient.Internal.Logging 1.0.0. See [release notes](release-notes/Internal/Logging/1.0/1.0.0.md). +- Released Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 7.0.0. See [release notes](release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.0.md). + +### Changed + +- **Breaking:** Removed Azure dependencies from the core package. Entra ID authentication (`ActiveDirectoryAuthenticationProvider` and related types) has been extracted into a new `Microsoft.Data.SqlClient.Extensions.Azure` package. The core `Microsoft.Data.SqlClient` package no longer depends on `Azure.Core`, `Azure.Identity`, or their transitive dependencies. Applications using Entra ID authentication must now install `Microsoft.Data.SqlClient.Extensions.Azure` separately. + ([#1108](https://github.com/dotnet/SqlClient/issues/1108), + [#3680](https://github.com/dotnet/SqlClient/pull/3680), + [#3902](https://github.com/dotnet/SqlClient/pull/3902), + [#3904](https://github.com/dotnet/SqlClient/pull/3904), + [#3908](https://github.com/dotnet/SqlClient/pull/3908), + [#3917](https://github.com/dotnet/SqlClient/pull/3917), + [#3982](https://github.com/dotnet/SqlClient/pull/3982), + [#3978](https://github.com/dotnet/SqlClient/pull/3978), + [#3986](https://github.com/dotnet/SqlClient/pull/3986)) + +- Two additional packages were introduced to support this separation: `Microsoft.Data.SqlClient.Extensions.Abstractions` (shared types between the core driver and extensions) and `Microsoft.Data.SqlClient.Internal.Logging` (shared ETW tracing infrastructure). + ([#3626](https://github.com/dotnet/SqlClient/pull/3626), + [#3628](https://github.com/dotnet/SqlClient/pull/3628), + [#3967](https://github.com/dotnet/SqlClient/pull/3967), + [#4038](https://github.com/dotnet/SqlClient/pull/4038)) + +- Deprecated `SqlAuthenticationMethod.ActiveDirectoryPassword` (ROPC flow). The method is now marked `[Obsolete]` and will generate compiler warnings. Migrate to `ActiveDirectoryInteractive`, `ActiveDirectoryServicePrincipal`, `ActiveDirectoryManagedIdentity`, or `ActiveDirectoryDefault`. + ([#3671](https://github.com/dotnet/SqlClient/pull/3671)) + +- Reverted public visibility of internal interop enums (`IoControlCodeAccess` and `IoControlTransferType`) that were accidentally made public during the project merge. + ([#3900](https://github.com/dotnet/SqlClient/pull/3900)) + +- Removed `Constrained Execution Region` error handling blocks and associated `SqlConnection` cleanup. + ([#3535](https://github.com/dotnet/SqlClient/pull/3535)) + +- Performance improvements across SqlStatistics timing, Always Encrypted scenarios, and connection opening: + ([#3609](https://github.com/dotnet/SqlClient/pull/3609), + [#3612](https://github.com/dotnet/SqlClient/pull/3612), + [#3732](https://github.com/dotnet/SqlClient/pull/3732), + [#3660](https://github.com/dotnet/SqlClient/pull/3660), + [#3791](https://github.com/dotnet/SqlClient/pull/3791), + [#3772](https://github.com/dotnet/SqlClient/pull/3772), + [#3554](https://github.com/dotnet/SqlClient/pull/3554)) + +- Allow `SqlBulkCopy` to operate on hidden columns. + ([#3590](https://github.com/dotnet/SqlClient/pull/3590)) + +- Updated UserAgent feature to use a pipe-delimited format, replacing the previous JSON format. + ([#3826](https://github.com/dotnet/SqlClient/pull/3826)) + +- Minor improvements to Managed SNI tracing to capture continuation events and errors. + ([#3859](https://github.com/dotnet/SqlClient/pull/3859)) + +### Added + +- Added `SspiContextProvider` abstract class and `SqlConnection.SspiContextProvider` property, enabling custom SSPI authentication for scenarios like cross-domain Kerberos negotiation and NTLM username/password authentication. + ([#2253](https://github.com/dotnet/SqlClient/issues/2253), + [#2494](https://github.com/dotnet/SqlClient/pull/2494)) + +- Continued refinement of packet multiplexing with bug fixes and stability improvements, plus new app context switches for opt-in control. + ([#3534](https://github.com/dotnet/SqlClient/pull/3534), + [#3537](https://github.com/dotnet/SqlClient/pull/3537), + [#3605](https://github.com/dotnet/SqlClient/pull/3605)) + +- Added support for enhanced routing, a TDS feature that allows the server to redirect connections to a specific server and database during login, enabling Azure SQL Hyperscale read replica load balancing. + ([#3641](https://github.com/dotnet/SqlClient/issues/3641), + [#3969](https://github.com/dotnet/SqlClient/pull/3969), + [#3970](https://github.com/dotnet/SqlClient/pull/3970), + [#3973](https://github.com/dotnet/SqlClient/pull/3973)) + +- Updated pipelines and test suites to compile the driver using the .NET 10 SDK. + ([#3686](https://github.com/dotnet/SqlClient/pull/3686)) + +- Added `SqlConfigurableRetryFactory.BaselineTransientErrors` static property exposing the default transient error codes list as a `ReadOnlyCollection`. + ([#3903](https://github.com/dotnet/SqlClient/pull/3903)) + +- Added app context switch `Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault` to set `MultiSubnetFailover=true` globally without modifying connection strings. + ([#3841](https://github.com/dotnet/SqlClient/pull/3841)) + +- Added app context switch `Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner` to let the client ignore server-provided failover partner info in Basic Availability Groups. + ([#3625](https://github.com/dotnet/SqlClient/pull/3625)) + +- Enabled `SqlClientDiagnosticListener` for `SqlCommand` on .NET Framework, closing a long-standing observability gap where diagnostic events were previously only available on .NET Core. + ([#3658](https://github.com/dotnet/SqlClient/pull/3658)) + +- Brought the 15 strongly-typed diagnostic event classes in the `Microsoft.Data.SqlClient.Diagnostics` namespace (e.g., `SqlClientCommandBefore`, `SqlClientConnectionOpenAfter`, `SqlClientTransactionCommitError`) to .NET Framework as part of the codebase merge. These types were originally introduced for .NET Core in 6.0. + ([#3493](https://github.com/dotnet/SqlClient/pull/3493)) + +- Enabled User Agent Feature Extension (opt-in via `Switch.Microsoft.Data.SqlClient.EnableUserAgent`). + ([#3606](https://github.com/dotnet/SqlClient/pull/3606)) + +- Added actionable error message when Entra ID authentication methods are used without the `Microsoft.Data.SqlClient.Extensions.Azure` package installed. + ([#3962](https://github.com/dotnet/SqlClient/issues/3962), + [#4046](https://github.com/dotnet/SqlClient/pull/4046)) + +### Fixed + +- Fixed a connection performance regression where SPN generation was triggered for non-integrated authentication modes (e.g., SQL authentication) on the native SNI path. + ([#3929](https://github.com/dotnet/SqlClient/pull/3929)) + +- Fixed `ExecuteScalar` to propagate errors when the server sends data followed by an error token. + ([#3912](https://github.com/dotnet/SqlClient/pull/3912)) + +- Fixed `NullReferenceException` in `SqlDataAdapter` when processing batch scenarios. + ([#3857](https://github.com/dotnet/SqlClient/pull/3857)) + +- Fixed reading of multiple app context switches from a single `AppContextSwitchOverrides` configuration field. + ([#3960](https://github.com/dotnet/SqlClient/pull/3960)) + +- Fixed an edge case in `TdsParserStateObject.TryReadPlpBytes` where zero-length reads returned `null` instead of an empty array. + ([#3872](https://github.com/dotnet/SqlClient/pull/3872)) + +- Fixed issue where extra connection deactivation was occurring. + ([#3758](https://github.com/dotnet/SqlClient/pull/3758)) + +- Fixed debug assertion in connection pool (no impact to production code). + ([#3587](https://github.com/dotnet/SqlClient/pull/3587)) + +- Prevented uninitialized performance counters escaping `CreatePerformanceCounters`. + ([#3623](https://github.com/dotnet/SqlClient/pull/3623)) + +- Fixed `SetProvider` to return immediately if user-defined authentication provider found. + ([#3620](https://github.com/dotnet/SqlClient/pull/3620)) + +- Fixed connection pool concurrency issue. + ([#3632](https://github.com/dotnet/SqlClient/pull/3632)) + ## [Preview Release 7.0.0-preview4.26064.3] - 2026-03-05 This update brings the below changes over the previous preview release: diff --git a/release-notes/7.0/7.0.0.md b/release-notes/7.0/7.0.0.md new file mode 100644 index 0000000000..f62178076b --- /dev/null +++ b/release-notes/7.0/7.0.0.md @@ -0,0 +1,410 @@ +# Release Notes + +## Stable Release 7.0.0 - 2026-03-17 + +This is the general availability release of **Microsoft.Data.SqlClient 7.0**, a major milestone for the .NET data provider for SQL Server. This release addresses the most upvoted issue in the repository's history — extracting Azure dependencies from the core package — introduces pluggable SSPI authentication, adds enhanced routing for Azure SQL Hyperscale, and delivers async read performance improvements. + +Also released as part of this milestone: +- Released Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0. See [release notes](../Extensions/Abstractions/1.0/1.0.0.md). +- Released Microsoft.Data.SqlClient.Extensions.Azure 1.0.0. See [release notes](../Extensions/Azure/1.0/1.0.0.md). +- Released Microsoft.Data.SqlClient.Internal.Logging 1.0.0. See [release notes](../Internal/Logging/1.0/1.0.0.md). +- Released Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 7.0.0. See [release notes](../add-ons/AzureKeyVaultProvider/7.0/7.0.0.md). + +## Changes Since [7.0.0-preview4](7.0.0-preview4.md) + +### Added + +- Added actionable error message when Entra ID authentication methods are used without the `Microsoft.Data.SqlClient.Extensions.Azure` package installed, guiding users to install the correct package. + ([#3962](https://github.com/dotnet/SqlClient/issues/3962), + [#4046](https://github.com/dotnet/SqlClient/pull/4046)) + +- Added Azure authentication sample application. + ([#3988](https://github.com/dotnet/SqlClient/pull/3988)) + +### Changed + +#### Other changes + +- Renamed the `Microsoft.Data.SqlClient.Extensions.Logging` package to `Microsoft.Data.SqlClient.Internal.Logging` to indicate it is for internal use only and should not be referenced directly by application code. + ([#4038](https://github.com/dotnet/SqlClient/pull/4038)) + +- Fixed non-localized exception strings. + ([#4022](https://github.com/dotnet/SqlClient/pull/4022)) + +- Codebase merge and cleanup: + ([#3997](https://github.com/dotnet/SqlClient/pull/3997), + [#4052](https://github.com/dotnet/SqlClient/pull/4052)) + +- Various test improvements: + ([#3891](https://github.com/dotnet/SqlClient/pull/3891), + [#3996](https://github.com/dotnet/SqlClient/pull/3996), + [#4002](https://github.com/dotnet/SqlClient/pull/4002), + [#4034](https://github.com/dotnet/SqlClient/pull/4034), + [#4041](https://github.com/dotnet/SqlClient/pull/4041), + [#4044](https://github.com/dotnet/SqlClient/pull/4044)) + +- Documentation improvements (including Entra ID branding updates): + ([#4021](https://github.com/dotnet/SqlClient/pull/4021), + [#4047](https://github.com/dotnet/SqlClient/pull/4047), + [#4049](https://github.com/dotnet/SqlClient/pull/4049)) + +- Updated Dependencies + ([#4045](https://github.com/dotnet/SqlClient/pull/4045)): + - Updated `Azure.Core` to v1.51.1 + - Updated `Azure.Identity` to v1.18.0 + - Updated `Azure.Security.KeyVault.Keys` to v4.9.0 + - Updated `Microsoft.Extensions.Caching.Memory` to v9.0.13 (.NET 9.0) + - Updated `Microsoft.IdentityModel.JsonWebTokens` to v8.16.0 + - Updated `Microsoft.IdentityModel.Protocols.OpenIdConnect` to v8.16.0 + - Updated `Microsoft.Bcl.Cryptography` to v9.0.13 (.NET 9.0) + - Updated `System.Configuration.ConfigurationManager` to v9.0.13 (.NET 9.0) + - Updated `System.Diagnostics.DiagnosticSource` to v10.0.3 + - Updated `System.Security.Cryptography.Pkcs` to v9.0.13 (.NET 9.0) + - Updated `System.Text.Json` to v10.0.3 + - Updated `System.Threading.Channels` to v10.0.3 + - Updated `System.ValueTuple` to v4.6.2 + +## Cumulative Changes Since [6.1](../6.1/README.md) + +This section summarizes all changes across the 7.0 preview cycle for users upgrading from the latest 6.1 stable release. + +### Changed + +#### Azure Dependencies Removed from Core Package + +*What Changed:* + +- The core `Microsoft.Data.SqlClient` package no longer depends on `Azure.Core`, `Azure.Identity`, or their transitive dependencies (e.g., `Microsoft.Identity.Client`, `Microsoft.Web.WebView2`). Azure Active Directory / Entra ID authentication functionality (`ActiveDirectoryAuthenticationProvider` and related types) has been extracted into a new `Microsoft.Data.SqlClient.Extensions.Azure` package. + ([#1108](https://github.com/dotnet/SqlClient/issues/1108), + [#3680](https://github.com/dotnet/SqlClient/pull/3680), + [#3902](https://github.com/dotnet/SqlClient/pull/3902), + [#3904](https://github.com/dotnet/SqlClient/pull/3904), + [#3908](https://github.com/dotnet/SqlClient/pull/3908), + [#3917](https://github.com/dotnet/SqlClient/pull/3917), + [#3982](https://github.com/dotnet/SqlClient/pull/3982), + [#3978](https://github.com/dotnet/SqlClient/pull/3978), + [#3986](https://github.com/dotnet/SqlClient/pull/3986)) +- Two additional packages were introduced to support this separation: `Microsoft.Data.SqlClient.Extensions.Abstractions` (shared types between the core driver and extensions) and `Microsoft.Data.SqlClient.Internal.Logging` (shared ETW tracing infrastructure). + ([#3626](https://github.com/dotnet/SqlClient/pull/3626), + [#3628](https://github.com/dotnet/SqlClient/pull/3628), + [#3967](https://github.com/dotnet/SqlClient/pull/3967), + [#4038](https://github.com/dotnet/SqlClient/pull/4038)) + +*Who Benefits:* + +- All users benefit from a significantly lighter core package. Previously, the Azure dependency chain pulled in numerous assemblies even for applications that only needed basic SQL Server connectivity. This was the [most upvoted open issue](https://github.com/dotnet/SqlClient/issues/1108) in the repository. +- Users who do not use Entra ID authentication no longer carry Azure-related assemblies in their build output. +- Users who do use Entra ID authentication can now manage Azure dependency versions independently from the core driver. + +*Impact:* + +- Applications using Entra ID authentication (e.g., `ActiveDirectoryInteractive`, `ActiveDirectoryDefault`, `ActiveDirectoryManagedIdentity`, etc.) must now install the `Microsoft.Data.SqlClient.Extensions.Azure` NuGet package separately: + +``` +dotnet add package Microsoft.Data.SqlClient.Extensions.Azure +``` + +- No code changes are required beyond adding the package reference. +- If an Entra ID authentication method is used without the Azure package installed, the driver now provides an actionable error message guiding users to install the correct package. + +### Added + +#### Pluggable Authentication with SspiContextProvider + +*What Changed:* + +- Added a public `SspiContextProvider` property on `SqlConnection`, completing the SSPI extensibility work begun in 6.1.0. Applications can now supply a custom SSPI context provider for integrated authentication, enabling custom Kerberos ticket negotiation and NTLM username/password authentication scenarios. + ([#2253](https://github.com/dotnet/SqlClient/issues/2253), + [#2494](https://github.com/dotnet/SqlClient/pull/2494)) + +*Who Benefits:* + +- Users authenticating across untrusted domains, non-domain-joined machines, or cross-platform environments where configuring integrated authentication is difficult. +- Users running in containers who need manual Kerberos negotiation without deploying sidecars or external ticket-refresh mechanisms. +- Users who need NTLM username/password authentication to SQL Server, which the driver does not provide natively. + +*Impact:* + +- Applications can set a custom `SspiContextProvider` on `SqlConnection` before opening the connection: + +```c# +var connection = new SqlConnection(connectionString); +connection.SspiContextProvider = new MyKerberosProvider(); +connection.Open(); +``` + +- The provider handles the authentication token exchange during integrated authentication. Existing authentication behavior is unchanged when no custom provider is set. See [SspiContextProvider_CustomProvider.cs](../../doc/samples/SspiContextProvider_CustomProvider.cs) for a sample implementation. +- **Note:** The `SspiContextProvider` is part of the connection pool key. Care should be taken when using this property to ensure the implementation returns a stable identity per resource. + +#### Async Read Performance: Packet Multiplexing (Preview) + +*What Changed:* + +- Continued refinement of packet multiplexing with bug fixes and stability improvements since 6.1.0, plus new app context switches for opt-in control. + ([#3534](https://github.com/dotnet/SqlClient/pull/3534), + [#3537](https://github.com/dotnet/SqlClient/pull/3537), + [#3605](https://github.com/dotnet/SqlClient/pull/3605)) + +*Who Benefits:* + +- Applications performing large async reads (`ExecuteReaderAsync` with big result sets, streaming scenarios, or bulk data retrieval). + +*Impact:* + +- Packet multiplexing ships behind two opt-in feature switches: + +```c# +AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour", false); +AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni", false); +``` + +- Setting both switches to `false` enables the new async processing path. By default, the driver uses the existing (compatible) behavior. + +#### Enhanced Routing Support + +*What Changed:* + +- Added support for enhanced routing, a TDS feature that allows the server to redirect connections to a specific server *and* database during login. + ([#3641](https://github.com/dotnet/SqlClient/issues/3641), + [#3969](https://github.com/dotnet/SqlClient/pull/3969), + [#3970](https://github.com/dotnet/SqlClient/pull/3970), + [#3973](https://github.com/dotnet/SqlClient/pull/3973)) + +*Who Benefits:* + +- Users connecting to Azure SQL Hyperscale environments that use named read replicas and gateway-based load balancing. + +*Impact:* + +- Enhanced routing is negotiated automatically during login when the server supports it. No application code changes are required. + +#### Support for .NET 10 + +*What Changed:* + +- Updated pipelines and test suites to compile the driver using the .NET 10 SDK. + ([#3686](https://github.com/dotnet/SqlClient/pull/3686)) + +*Who Benefits:* + +- Developers targeting .NET 10 on day one. + +*Impact:* + +- SqlClient 7.0 compiles and tests against .NET 10, ensuring compatibility. + +#### Strongly-Typed Diagnostic Events on .NET Framework + +*What Changed:* + +- Enabled `SqlClientDiagnosticListener` for `SqlCommand` on .NET Framework, closing a long-standing observability gap where diagnostic events were previously only emitted on .NET Core. + ([#3658](https://github.com/dotnet/SqlClient/pull/3658)) + +- Brought the 15 strongly-typed diagnostic event classes in the `Microsoft.Data.SqlClient.Diagnostics` namespace — originally introduced for .NET Core in 6.0 — to .NET Framework as part of the codebase merge. Both platforms now use the same strongly-typed event model. The types cover command, connection, and transaction lifecycle events: + - `SqlClientCommandBefore`, `SqlClientCommandAfter`, `SqlClientCommandError` + - `SqlClientConnectionOpenBefore`, `SqlClientConnectionOpenAfter`, `SqlClientConnectionOpenError` + - `SqlClientConnectionCloseBefore`, `SqlClientConnectionCloseAfter`, `SqlClientConnectionCloseError` + - `SqlClientTransactionCommitBefore`, `SqlClientTransactionCommitAfter`, `SqlClientTransactionCommitError` + - `SqlClientTransactionRollbackBefore`, `SqlClientTransactionRollbackAfter`, `SqlClientTransactionRollbackError` + + ([#3493](https://github.com/dotnet/SqlClient/pull/3493)) + +*Who Benefits:* + +- .NET Framework users subscribing to `SqlClientDiagnosticListener` events for observability, distributed tracing, or custom telemetry. These users now have parity with .NET Core, gaining IntelliSense, compile-time safety, and eliminating the need to access diagnostic payloads via reflection or dictionary lookups. + +*Impact:* + +- On .NET Framework, `SqlCommand` now emits the same diagnostic events that were previously only available on .NET Core. Subscribers to `DiagnosticListener` events (e.g., `Microsoft.Data.SqlClient.WriteCommandBefore`) receive the strongly-typed objects: + +```c# +listener.Subscribe(new Observer>(kvp => +{ + if (kvp.Value is SqlClientCommandBefore before) + { + Console.WriteLine($"Executing: {before.Command.CommandText}"); + } +})); +``` + +- The types implement `IReadOnlyList>` for backward compatibility with code that iterates properties generically. + +#### Other Additions + +- Added `SqlConfigurableRetryFactory.BaselineTransientErrors` static property exposing the default transient error codes list as a `ReadOnlyCollection`, making it easier to extend the default list with application-specific error codes. + ([#3903](https://github.com/dotnet/SqlClient/pull/3903)) + +- Added app context switch `Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault` to set `MultiSubnetFailover=true` globally without modifying connection strings. + ([#3841](https://github.com/dotnet/SqlClient/pull/3841)) + +- Added app context switch `Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner` to let the client ignore server-provided failover partner info in Basic Availability Groups. + ([#3625](https://github.com/dotnet/SqlClient/pull/3625)) + +- Enabled User Agent Feature Extension (opt-in via `Switch.Microsoft.Data.SqlClient.EnableUserAgent`). + ([#3606](https://github.com/dotnet/SqlClient/pull/3606)) + +### Changed + +#### Deprecation of `SqlAuthenticationMethod.ActiveDirectoryPassword` + +*What Changed:* + +- `SqlAuthenticationMethod.ActiveDirectoryPassword` (the ROPC flow) is now marked `[Obsolete]` and will generate compiler warnings. This aligns with Microsoft's move toward [mandatory multifactor authentication](https://learn.microsoft.com/entra/identity/authentication/concept-mandatory-multifactor-authentication). + ([#3671](https://github.com/dotnet/SqlClient/pull/3671)) + +*Who Benefits:* + +- Teams moving toward stronger, passwordless or MFA-compliant authentication. + +*Impact:* + +- If you use `Authentication=Active Directory Password`, migrate to a supported alternative: + +| Scenario | Recommended Authentication | +|----------|---------------------------| +| Interactive / desktop apps | `Active Directory Interactive` | +| Service-to-service | `Active Directory Service Principal` | +| Azure-hosted workloads | `Active Directory Managed Identity` | +| Developer / CI environments | `Active Directory Default` | + +- See [Connect to Azure SQL with Microsoft Entra authentication](https://learn.microsoft.com/sql/connect/ado-net/sql/azure-active-directory-authentication) for more information. + +#### Breaking Changes + +- Reverted public visibility of internal interop enums (`IoControlCodeAccess` and `IoControlTransferType`) that were accidentally made public during the project merge. + ([#3900](https://github.com/dotnet/SqlClient/pull/3900)) + +#### Other Changes + +- Removed `Constrained Execution Region` error handling blocks and associated `SqlConnection` cleanup. + ([#3535](https://github.com/dotnet/SqlClient/pull/3535)) + +- Performance improvements across SqlStatistics timing, Always Encrypted scenarios, and connection opening: + ([#3609](https://github.com/dotnet/SqlClient/pull/3609), + [#3612](https://github.com/dotnet/SqlClient/pull/3612), + [#3732](https://github.com/dotnet/SqlClient/pull/3732), + [#3660](https://github.com/dotnet/SqlClient/pull/3660), + [#3791](https://github.com/dotnet/SqlClient/pull/3791), + [#3772](https://github.com/dotnet/SqlClient/pull/3772), + [#3554](https://github.com/dotnet/SqlClient/pull/3554)) + +- Allow `SqlBulkCopy` to operate on hidden columns. + ([#3590](https://github.com/dotnet/SqlClient/pull/3590)) + +- Updated UserAgent feature to use a pipe-delimited format, replacing the previous JSON format. + ([#3826](https://github.com/dotnet/SqlClient/pull/3826)) + +- Minor improvements to Managed SNI tracing to capture continuation events and errors. + ([#3859](https://github.com/dotnet/SqlClient/pull/3859)) + +### Fixed + +- Fixed a connection performance regression where SPN generation was triggered for non-integrated authentication modes (e.g., SQL authentication) on the native SNI path. + ([#3929](https://github.com/dotnet/SqlClient/pull/3929)) + +- Fixed `ExecuteScalar` to propagate errors when the server sends data followed by an error token. + ([#3912](https://github.com/dotnet/SqlClient/pull/3912)) + +- Fixed `NullReferenceException` in `SqlDataAdapter` when processing batch scenarios. + ([#3857](https://github.com/dotnet/SqlClient/pull/3857)) + +- Fixed reading of multiple app context switches from a single `AppContextSwitchOverrides` configuration field. + ([#3960](https://github.com/dotnet/SqlClient/pull/3960)) + +- Fixed an edge case in `TdsParserStateObject.TryReadPlpBytes` where zero-length reads returned `null` instead of an empty array. + ([#3872](https://github.com/dotnet/SqlClient/pull/3872)) + +- Fixed issue where extra connection deactivation was occurring. + ([#3758](https://github.com/dotnet/SqlClient/pull/3758)) + +- Fixed debug assertion in connection pool (no impact to production code). + ([#3587](https://github.com/dotnet/SqlClient/pull/3587)) + +- Prevented uninitialized performance counters escaping `CreatePerformanceCounters`. + ([#3623](https://github.com/dotnet/SqlClient/pull/3623)) + +- Fixed `SetProvider` to return immediately if user-defined authentication provider found. + ([#3620](https://github.com/dotnet/SqlClient/pull/3620)) + +- Fixed connection pool concurrency issue. + ([#3632](https://github.com/dotnet/SqlClient/pull/3632)) + + +## Contributors + +We thank the following public contributors. Their efforts toward this project are very much appreciated. + +- [edwardneal](https://github.com/edwardneal) +- [ErikEJ](https://github.com/ErikEJ) +- [frankbuckley](https://github.com/frankbuckley) +- [MatthiasHuygelen](https://github.com/MatthiasHuygelen) +- [ShreyaLaxminarayan](https://github.com/ShreyaLaxminarayan) +- [tetolv](https://github.com/tetolv) +- [twsouthwick](https://github.com/twsouthwick) +- [Wraith2](https://github.com/Wraith2) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64, Windows ARM64) +- .NET 8.0+ (Windows x86, Windows x64, Windows ARM, Windows ARM64, Linux, macOS) + +### Dependencies + +#### .NET 9.0 + +- Microsoft.Bcl.Cryptography 9.0.13 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.13 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.13 +- System.Security.Cryptography.Pkcs 9.0.13 + +#### .NET 8.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET Standard 2.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 + +#### .NET Framework 4.6.2+ + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- System.Buffers 4.6.1 +- System.Diagnostics.DiagnosticSource 10.0.3 +- System.Memory 4.6.3 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 +- System.ValueTuple 4.6.2 diff --git a/release-notes/7.0/README.md b/release-notes/7.0/README.md index e8bfcc25f3..70cfdc3835 100644 --- a/release-notes/7.0/README.md +++ b/release-notes/7.0/README.md @@ -4,6 +4,7 @@ The following Microsoft.Data.SqlClient 7.0 releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2026-03-17 | 7.0.0 | [Release Notes](7.0.0.md) | | 2026-03-05 | 7.0.0-preview4.26064.3 | [Release Notes](7.0.0-preview4.md) | | 2025-12-08 | 7.0.0-preview3.25342.7 | [Release Notes](7.0.0-preview3.md) | | 2025-10-16 | 7.0.0-preview2.25289.6 | [Release Notes](7.0.0-preview2.md) | diff --git a/release-notes/Extensions/Abstractions/1.0/1.0.0.md b/release-notes/Extensions/Abstractions/1.0/1.0.0.md new file mode 100644 index 0000000000..82157224ff --- /dev/null +++ b/release-notes/Extensions/Abstractions/1.0/1.0.0.md @@ -0,0 +1,30 @@ +# Release Notes + +## Stable Release 1.0.0 - 2026-03-17 + +This is the first stable release of `Microsoft.Data.SqlClient.Extensions.Abstractions`, providing shared types and interfaces between the core `Microsoft.Data.SqlClient` driver and its extension packages (e.g., `Microsoft.Data.SqlClient.Extensions.Azure`). + +## Changes Since [1.0.0-preview1](1.0.0-preview1.md) + +No changes to this package since preview1. + +## Cumulative Changes (First Stable Release) + +This is the first stable release of this package. The following summarizes all changes across the preview cycle. + +### Added + +- Shared abstraction types for the SqlClient extensions model. + ([#3626](https://github.com/dotnet/SqlClient/pull/3626), + [#3628](https://github.com/dotnet/SqlClient/pull/3628), + [#3967](https://github.com/dotnet/SqlClient/pull/3967)) + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 diff --git a/release-notes/Extensions/Abstractions/1.0/README.md b/release-notes/Extensions/Abstractions/1.0/README.md index 2e91111742..347ae77335 100644 --- a/release-notes/Extensions/Abstractions/1.0/README.md +++ b/release-notes/Extensions/Abstractions/1.0/README.md @@ -5,4 +5,5 @@ The following `Microsoft.Data.SqlClient.Extensions.Abstractions` | Release Date | Description | Notes | | :-- | :-- | :--: | +| 2026-03-17 | 1.0.0 | [Release Notes](1.0.0.md) | | 2026-03-05 | 1.0.0-preview1.26064.3 | [Release Notes](1.0.0-preview1.md) | diff --git a/release-notes/Extensions/Azure/1.0/1.0.0.md b/release-notes/Extensions/Azure/1.0/1.0.0.md new file mode 100644 index 0000000000..c371669493 --- /dev/null +++ b/release-notes/Extensions/Azure/1.0/1.0.0.md @@ -0,0 +1,66 @@ +# Release Notes + +## Stable Release 1.0.0 - 2026-03-17 + +This is the first stable release of `Microsoft.Data.SqlClient.Extensions.Azure`, providing Microsoft Entra ID (Azure Active Directory) authentication support for Microsoft.Data.SqlClient. This package was extracted from the core driver to address the [most requested feature](https://github.com/dotnet/SqlClient/issues/1108) — making the core package lighter by removing Azure dependencies. + +## Changes Since [1.0.0-preview1](1.0.0-preview1.md) + +### Changed + +- Renamed dependency from `Microsoft.Data.SqlClient.Extensions.Logging` to `Microsoft.Data.SqlClient.Internal.Logging`. + ([#4038](https://github.com/dotnet/SqlClient/pull/4038)) + +- Updated Dependencies + ([#4045](https://github.com/dotnet/SqlClient/pull/4045)): + - Updated `Azure.Core` to v1.51.1 + - Updated `Azure.Identity` to v1.18.0 + - Updated `Microsoft.Identity.Client` to v4.83.0 + +## Cumulative Changes (First Stable Release) + +This is the first stable release of this package. The following summarizes all changes across the preview cycle. + +### Added + +- Entra ID authentication provider (`ActiveDirectoryAuthenticationProvider`) supporting: + - `ActiveDirectoryInteractive` + - `ActiveDirectoryDefault` + - `ActiveDirectoryManagedIdentity` + - `ActiveDirectoryServicePrincipal` + - `ActiveDirectoryDeviceCodeFlow` + - `ActiveDirectoryWorkloadIdentity` + - `ActiveDirectoryPassword` (deprecated) + ([#3680](https://github.com/dotnet/SqlClient/pull/3680), + [#3902](https://github.com/dotnet/SqlClient/pull/3902), + [#3904](https://github.com/dotnet/SqlClient/pull/3904), + [#3908](https://github.com/dotnet/SqlClient/pull/3908), + [#3917](https://github.com/dotnet/SqlClient/pull/3917), + [#3982](https://github.com/dotnet/SqlClient/pull/3982), + [#3978](https://github.com/dotnet/SqlClient/pull/3978), + [#3986](https://github.com/dotnet/SqlClient/pull/3986)) + +## Target Platform Support + +- .NET Standard 2.0 +- .NET Framework 4.6.2+ + +### Dependencies + +#### .NET Standard 2.0 + +- Azure.Core 1.51.1 +- Azure.Identity 1.18.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.83.0 + +#### .NET Framework 4.6.2+ + +- Azure.Core 1.51.1 +- Azure.Identity 1.18.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.83.0 diff --git a/release-notes/Extensions/Azure/1.0/README.md b/release-notes/Extensions/Azure/1.0/README.md index 19dc060723..dd0030f110 100644 --- a/release-notes/Extensions/Azure/1.0/README.md +++ b/release-notes/Extensions/Azure/1.0/README.md @@ -5,4 +5,5 @@ The following `Microsoft.Data.SqlClient.Extensions.Azure` | Release Date | Description | Notes | | :-- | :-- | :--: | +| 2026-03-17 | 1.0.0 | [Release Notes](1.0.0.md) | | 2026-03-05 | 1.0.0-preview1.26064.3 | [Release Notes](1.0.0-preview1.md) | diff --git a/release-notes/Extensions/Logging/1.0/README.md b/release-notes/Extensions/Logging/1.0/README.md index b31406665b..f5cbea90ea 100644 --- a/release-notes/Extensions/Logging/1.0/README.md +++ b/release-notes/Extensions/Logging/1.0/README.md @@ -1,5 +1,8 @@ # Microsoft.Data.SqlClient.Extensions.Logging 1.0 Releases +> **Note:** This package has been renamed to `Microsoft.Data.SqlClient.Internal.Logging` starting +> with the 1.0.0 stable release. See [Internal/Logging release notes](../../../Internal/Logging/1.0/1.0.0.md). + > **Note:** This package is for internal use by other Microsoft.Data.SqlClient packages only > and should not be referenced directly by application code. diff --git a/release-notes/Internal/Logging/1.0/1.0.0.md b/release-notes/Internal/Logging/1.0/1.0.0.md new file mode 100644 index 0000000000..5cbf4d886b --- /dev/null +++ b/release-notes/Internal/Logging/1.0/1.0.0.md @@ -0,0 +1,37 @@ +# Release Notes + +## Stable Release 1.0.0 - 2026-03-17 + +> **Note:** This package is for internal use by other Microsoft.Data.SqlClient packages only +> and should not be referenced directly by application code. + +This is the first stable release of `Microsoft.Data.SqlClient.Internal.Logging`, providing shared ETW EventSource tracing infrastructure for the Microsoft.Data.SqlClient package family. + +## Changes Since [1.0.0-preview1](../../../Extensions/Logging/1.0/1.0.0-preview1.md) + +### Changed + +- Renamed from `Microsoft.Data.SqlClient.Extensions.Logging` to `Microsoft.Data.SqlClient.Internal.Logging` to clarify that this package is for internal use only. + ([#4038](https://github.com/dotnet/SqlClient/pull/4038)) + +## Cumulative Changes (First Stable Release) + +This is the first stable release of this package. The following summarizes all changes across the preview cycle. + +### Added + +- Shared ETW EventSource tracing infrastructure for internal use by `Microsoft.Data.SqlClient` and its extension packages. + ([#3626](https://github.com/dotnet/SqlClient/pull/3626), + [#3628](https://github.com/dotnet/SqlClient/pull/3628), + [#3967](https://github.com/dotnet/SqlClient/pull/3967)) + + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- None diff --git a/release-notes/Internal/Logging/1.0/README.md b/release-notes/Internal/Logging/1.0/README.md new file mode 100644 index 0000000000..15978c4843 --- /dev/null +++ b/release-notes/Internal/Logging/1.0/README.md @@ -0,0 +1,11 @@ +# Microsoft.Data.SqlClient.Internal.Logging 1.0 Releases + +> **Note:** This package is for internal use by other Microsoft.Data.SqlClient packages only +> and should not be referenced directly by application code. + +The following `Microsoft.Data.SqlClient.Internal.Logging` +1.0 releases have been shipped: + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2026-03-17 | 1.0.0 | [Release Notes](1.0.0.md) | diff --git a/release-notes/README.md b/release-notes/README.md index 4e2c67bc33..a511a589bc 100644 --- a/release-notes/README.md +++ b/release-notes/README.md @@ -1,6 +1,6 @@ # Microsoft.Data.SqlClient Release Notes -The latest stable release is [Microsoft.Data.SqlClient 6.1](6.1). +The latest stable release is [Microsoft.Data.SqlClient 7.0](7.0). ## Release Information @@ -22,7 +22,7 @@ The latest stable release is [Microsoft.Data.SqlClient 6.1](6.1). # Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider Release Notes The latest stable release is -[Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 6.1](add-ons/AzureKeyVaultProvider/6.1). +[Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 7.0](add-ons/AzureKeyVaultProvider/7.0). ## Release Information @@ -54,14 +54,15 @@ The latest release is - [Microsoft.Data.SqlClient.Extensions.Azure 1.0](Extensions/Azure/1.0) -# Microsoft.Data.SqlClient.Extensions.Logging Release Notes + +# Microsoft.Data.SqlClient.Internal.Logging Release Notes The latest release is -[Microsoft.Data.SqlClient.Extensions.Logging 1.0](Extensions/Logging/1.0). +[Microsoft.Data.SqlClient.Internal.Logging 1.0](Internal/Logging/1.0). ## Release Information -- [Microsoft.Data.SqlClient.Extensions.Logging 1.0](Extensions/Logging/1.0) +- [Microsoft.Data.SqlClient.Internal.Logging 1.0](Internal/Logging/1.0) # Microsoft.SqlServer.Server Release Notes diff --git a/release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.0.md b/release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.0.md new file mode 100644 index 0000000000..6a6f31054b --- /dev/null +++ b/release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.0.md @@ -0,0 +1,54 @@ +# Release Notes + +## Stable Release 7.0.0 - 2026-03-17 + +## Changes Since [7.0.0-preview1](7.0.0-preview1.md) + +### Changed + +- Changed AKV Provider to target .NET Standard 2.0 instead of multi-targeting .NET Framework and .NET Core. Since the package contains no framework-specific code, this simplifies the package and reduces its footprint. + ([#4036](https://github.com/dotnet/SqlClient/pull/4036)) + +- Renamed dependency from `Microsoft.Data.SqlClient.Extensions.Logging` to `Microsoft.Data.SqlClient.Internal.Logging`. + ([#4038](https://github.com/dotnet/SqlClient/pull/4038)) + +- Updated Dependencies + ([#4045](https://github.com/dotnet/SqlClient/pull/4045)): + - Updated `Azure.Core` to v1.51.1 + - Updated `Azure.Security.KeyVault.Keys` to v4.9.0 + +## Cumulative Changes Since [6.1](../6.1/README.md) + +This section summarizes all changes across the 7.0 preview cycle for users upgrading from the latest 6.1 stable release. + +### Changed + +- Changed AKV Provider to target .NET Standard 2.0 instead of multi-targeting .NET Framework and .NET Core. Since the package contains no framework-specific code, this simplifies the package and reduces its footprint. + ([#4036](https://github.com/dotnet/SqlClient/pull/4036)) + +- Performance improvements for all built-in `SqlColumnEncryptionKeyStoreProvider` implementations, including `SqlColumnEncryptionAzureKeyVaultProvider`. + ([#3554](https://github.com/dotnet/SqlClient/pull/3554)) + +- Added dependency on `Microsoft.Data.SqlClient.Internal.Logging` to align with the new extensions infrastructure. + ([#3626](https://github.com/dotnet/SqlClient/pull/3626), + [#3628](https://github.com/dotnet/SqlClient/pull/3628), + [#3967](https://github.com/dotnet/SqlClient/pull/3967), + [#4038](https://github.com/dotnet/SqlClient/pull/4038)) + +- Updated Dependencies: + - Updated `Azure.Core` to v1.51.1 + - Updated `Azure.Security.KeyVault.Keys` to v4.9.0 + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- Azure.Core 1.51.1 +- Azure.Security.KeyVault.Keys 4.9.0 +- Microsoft.Data.SqlClient (>= 7.0.0) +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Extensions.Caching.Memory 8.0.1 diff --git a/release-notes/add-ons/AzureKeyVaultProvider/7.0/README.md b/release-notes/add-ons/AzureKeyVaultProvider/7.0/README.md index c63a0bc71f..22593ef05c 100644 --- a/release-notes/add-ons/AzureKeyVaultProvider/7.0/README.md +++ b/release-notes/add-ons/AzureKeyVaultProvider/7.0/README.md @@ -5,4 +5,5 @@ The following `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` | Release Date | Description | Notes | | :-- | :-- | :--: | +| 2026-03-17 | 7.0.0 | [Release Notes](7.0.0.md) | | 2026-03-05 | 7.0.0-preview1.26064.3 | [Release Notes](7.0.0-preview1.md) | From 86492a1fa139b001efbfb349a971acc30d601db5 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 18 Mar 2026 09:20:05 -0700 Subject: [PATCH 002/298] Validate milestone assignment on pull requests. (#4055) * Validate milestone assignment on pull requests. * Update json checks to avoid jq. --- .github/workflows/check-milestone.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/check-milestone.yml diff --git a/.github/workflows/check-milestone.yml b/.github/workflows/check-milestone.yml new file mode 100644 index 0000000000..da4681fd7b --- /dev/null +++ b/.github/workflows/check-milestone.yml @@ -0,0 +1,24 @@ +name: Check Milestone + +on: + pull_request: + types: [opened, edited, synchronize, milestoned, demilestoned] + +jobs: + check-milestone: + name: Validate milestone + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - name: Check milestone is set + if: github.event.pull_request.milestone == null + run: | + echo "::error::This PR does not have a milestone set. Please assign a milestone before merging." + exit 1 + + - name: Check milestone is open + if: github.event.pull_request.milestone != null && github.event.pull_request.milestone.state != 'open' + run: | + echo "::error::Milestone '${{ github.event.pull_request.milestone.title }}' is ${{ github.event.pull_request.milestone.state }}. Please assign an open milestone." + exit 1 From 07a9280494d62a9a2381a1215abb6587c0a4379d Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 19 Mar 2026 16:27:39 -0500 Subject: [PATCH 003/298] Merge | Pack Mds Target, Wire PR/CI Pipelines to Build2.proj (#4033) * In progress work for pack target ... kinda working * Add config to genapi path. Remove unnecessary dependencies from genapi project * Fix generation of docs in ref project * Dump reference type specific builds Fix xml documentation file generation for implementation project * Generating a package file works!!! * Resync pipelines folder in solution * Add an assembly build number argument? ... it doesn't work * Wiring up build numbers through build2.proj * Build number argument * Maybe wiring it up?!? * Reinstate the assemblybuildnumber property * Build all of MDS, once * PR comments from copilot * Specify test results folder for CI builds * TargetFramework => TestFramework * Fixing a couple more comments * Couple more comments from copilot * downgrade back to 7.0.0 :) * I dunno, fixing some stuff, I guess. * Fix indenting * Back out changes to official pipelines * A couple more comments from CoPilot * Generate AKV documentation file during CI build ... idk why this is only a problem *now* * Comments from Copilot ... I'd love it if it could give the same comments across commits. --- build2.proj | 272 +++++++++++++----- .../templates/steps/ci-project-build-step.yml | 104 +------ .../templates/steps/run-all-tests-step.yml | 248 ++++++++-------- .../sqlclient-pr-package-ref-pipeline.yml | 1 + .../sqlclient-pr-project-ref-pipeline.yml | 1 + src/Microsoft.Data.SqlClient.sln | 87 +++--- .../MdsVersions.props | 35 +++ .../Microsoft.Data.SqlClient.csproj | 12 +- .../ref/Microsoft.Data.SqlClient.cs | 10 +- .../ref/Microsoft.Data.SqlClient.csproj | 17 +- .../src/Microsoft.Data.SqlClient.csproj | 20 +- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 16 +- .../src/Microsoft/Data/SqlDbTypeExtensions.cs | 6 +- .../src/Microsoft/Data/SqlTypes/SqlJson.cs | 22 +- .../src/Microsoft/Data/SqlTypes/SqlVector.cs | 20 +- tools/intellisense/TrimDocs.ps1 | 5 +- tools/props/Versions.props | 33 +-- tools/specs/Microsoft.Data.SqlClient.nuspec | 204 ++++++------- tools/targets/GenerateThisAssemblyCs.targets | 3 + 19 files changed, 615 insertions(+), 501 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/MdsVersions.props diff --git a/build2.proj b/build2.proj index a5fb6b89a2..b9b08f9a4a 100644 --- a/build2.proj +++ b/build2.proj @@ -6,6 +6,25 @@ + + + + + -p:BuildNumber=$(BuildNumber) + + - + + + + nuget @@ -97,6 +136,19 @@ -p:ReferenceType=Package + + + + -p:SigningKeyPath="$(SigningKeyPath)" + + - true - + true + --collect "Code coverage" --settings "$(RepoRoot)src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/CodeCoverage.runsettings" - + - $(RepoRoot)src/Microsoft.Data.SqlClient/ + $(RepoRoot)src/Microsoft.Data.SqlClient/ + $(RepoRoot)artifacts/Microsoft.Data.SqlClient/ + + + $(MdsSrcRoot)src/Microsoft.Data.SqlClient.csproj + $(MdsSrcRoot)ref/Microsoft.Data.SqlClient.csproj + $(MdsSrcRoot)notsupported/Microsoft.Data.SqlClient.csproj + + + $(MdsSrcRoot)tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj + $(MdsSrcRoot)tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj + $(MdsSrcRoot)tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj $(RepoRoot)tools/specs/Microsoft.Data.SqlClient.nuspec $(RepoRoot)tools/GenAPI/Microsoft.DotNet.GenAPI/ $(GenApiPath)Microsoft.DotNet.GenAPI.csproj - - - $(MdsRoot)tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj - $(MdsRoot)tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj - $(MdsRoot)notsupported/Microsoft.Data.SqlClient.csproj - $(MdsRoot)src/Microsoft.Data.SqlClient.csproj - $(MdsRoot)ref/Microsoft.Data.SqlClient.csproj - $(MdsRoot)tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -211,98 +266,163 @@ --> - - "$(DotNetPath)dotnet" build "$(GenApiProjectPath)" + + "$(DotnetPath)dotnet" build "$(GenApiProjectPath)" -p:Configuration=$(Configuration) - + - $([System.Text.RegularExpressions.Regex]::Replace($(DotNetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + - + - - "$(DotNetPath)dotnet" build "$(MdsNotSupportedProjectPath)" + + "$(DotnetPath)dotnet" build "$(MdsNotSupportedProjectPath)" -p:Configuration=$(Configuration) -p:GenApiPath="@(GenApiArtifactPath->'%(FullPath)')" - + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(PacakgeVersionMdsArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + - $([System.Text.RegularExpressions.Regex]::Replace($(DotNetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + - - $(DotNetPath)dotnet build $(MdsRefProjectPath) + + "$(DotnetPath)dotnet" build $(MdsRefProjectPath) -p:Configuration=$(Configuration) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(PacakgeVersionMdsArgument) + + $(ReferenceTypeArgument) $(PackageVersionAbstractionsArgument) - + - $([System.Text.RegularExpressions.Regex]::Replace($(DotNetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + - - $(DotNetPath)dotnet build $(MdsProjectPath) + + "$(DotnetPath)dotnet" build $(MdsProjectPath) -p:Configuration=$(Configuration) -p:TargetOs=Unix + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(PackageVersionMdsArgument) $(ReferenceTypeArgument) $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) - + - $([System.Text.RegularExpressions.Regex]::Replace($(DotNetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + - - $(DotNetPath)dotnet build $(MdsProjectPath) + + "$(DotnetPath)dotnet" build $(MdsProjectPath) -p:Configuration=$(Configuration) -p:TargetOs=Windows_NT + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(PackageVersionMdsArgument) $(ReferenceTypeArgument) $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) - + - $([System.Text.RegularExpressions.Regex]::Replace($(DotNetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + - - - + + + + + + + + + git rev-parse HEAD + + + $([System.Text.RegularExpressions.Regex]::Replace($(GitCommand), "\s+", " ")) + + + + + + + + $([System.Text.RegularExpressions.Regex]::Replace($(CommitId), "\s", "")) + + "$(NugetPath)" pack "$(MdsNuspecPath)" + -Symbols + -SymbolPackageFormat snupkg + -Version "$(PackageVersionMds)" + -OutputDirectory "$(MdsArtifactRoot)/$(ReferenceType)-$(Configuration)" + -properties "COMMITID=$(CommitId);Configuration=$(Configuration);ReferenceType=$(ReferenceType);AbstractionsPackageVersion=$(PackageVersionAbstractions);LoggingPackageVersion=$(PackageVersionLogging)" + + + $([System.Text.RegularExpressions.Regex]::Replace($(NuGetCommand), "\s+", " ")) + + + @@ -316,11 +436,11 @@ MdsFunctional-$(OS) $(LogFilePrefix)-$(TestFramework) - - $(DotNetPath)dotnet test "$(MdsFunctionalTestProjectPath)" + + "$(DotnetPath)dotnet" test "$(MdsFunctionalTestProjectPath)" -p:Configuration=$(Configuration) $(TestBlameArgument) - $(TestCollectArgument) + $(TestCodeCoverageArgument) $(TestFiltersArgument) $(TestFrameworkArgument) --results-directory "$(TestResultsFolderPath)" @@ -331,12 +451,12 @@ $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) $(PackageVersionMdsArgument) - + - $([System.Text.RegularExpressions.Regex]::Replace($(DotNetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + @@ -346,11 +466,11 @@ $(LogFilePrefix)-$(TestFramework) $(LogFilePrefix)-$(TestSet) - - $(DotNetPath)dotnet test "$(MdsManualTestProjectPath)" + + "$(DotnetPath)dotnet" test "$(MdsManualTestProjectPath)" -p:Configuration=$(Configuration) $(TestBlameArgument) - $(TestCollectArgument) + $(TestCodeCoverageArgument) $(TestFiltersArgument) $(TestFrameworkArgument) $(TestSetArgument) @@ -362,12 +482,12 @@ $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) $(PackageVersionMdsArgument) - + - $([System.Text.RegularExpressions.Regex]::Replace($(DotNetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + @@ -380,21 +500,21 @@ MdsUnit-$(OS) $(LogFilePrefix)-$(TestFramework) - - $(DotNetPath)dotnet test "$(MdsUnitTestProjectPath)" + + "$(DotnetPath)dotnet" test "$(MdsUnitTestProjectPath)" -p:Configuration=$(Configuration) $(TestBlameArgument) - $(TestCollectArgument) + $(TestCodeCoverageArgument) $(TestFiltersArgument) $(TestFrameworkArgument) --results-directory "$(TestResultsFolderPath)" --logger:"trx;LogFilePrefix=$(LogFilePrefix)" - + - $([System.Text.RegularExpressions.Regex]::Replace($(DotNetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + 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 e496aa2b71..d43222bfd1 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -71,61 +71,25 @@ parameters: default: $(akvPackageVersion) steps: -- ${{ if or(eq(parameters.operatingSystem, 'Windows'), eq(parameters.operatingSystem, 'deferedToRuntime')) }}: + # Build MDS - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - task: MSBuild@1 - displayName: 'Restore [Win]' + displayName: 'Build Driver' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) inputs: - solution: build.proj + solution: build2.proj msbuildArchitecture: x64 - msbuildArguments: >- - -t:restore + platform: '${{ parameters.platform }}' + configuration: '${{ parameters.buildConfiguration }}' + msbuildArguments: + -t:BuildMds -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - retryCountOnTaskFailure: 1 - - - ${{ if eq(parameters.build, 'allNoDocs') }}: - - task: MSBuild@1 - displayName: 'Build Driver (no docs) [Win]' - condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) - inputs: - solution: build.proj - msbuildArchitecture: x64 - platform: '${{ parameters.platform }}' - configuration: '${{ parameters.buildConfiguration }}' - msbuildArguments: >- - -t:BuildAllConfigurations - -p:ReferenceType=${{ parameters.referenceType }} - -p:GenerateNuget=false - -p:GenerateDocumentationFile=false - -p:BuildNumber=${{ parameters.buildNumber }} - -p:AssemblyBuildNumber=${{ parameters.assemblyBuildNumber }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - - - ${{ 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.buildConfiguration }}' - msbuildArguments: - -t:BuildAllConfigurations - -p:ReferenceType=${{ parameters.referenceType }} - -p:GenerateNuget=false - -p:BuildNumber=${{ parameters.buildNumber }} - -p:AssemblyBuildNumber=${{ parameters.assemblyBuildNumber }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:BuildNumber=${{ parameters.buildNumber }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + # Build AKV Provider - ${{ if or(eq(parameters.build, 'AkvProvider'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - task: MSBuild@1 displayName: 'Build AKV Provider' @@ -136,53 +100,10 @@ steps: platform: '${{ parameters.platform }}' configuration: '${{ parameters.buildConfiguration }}' msbuildArguments: >- - -t:BuildAkvProvider - -p:ReferenceType=${{ parameters.referenceType }} - -p:GenerateNuget=false - -p:BuildNumber=${{ parameters.buildNumber }} - -p:AssemblyBuildNumber=${{ parameters.assemblyBuildNumber }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:AkvPackageVersion=${{ parameters.akvPackageVersion }} - -- ${{ if or(eq(parameters.operatingSystem, 'Linux'), eq(parameters.operatingSystem, 'MacOS'), eq(parameters.operatingSystem, 'deferedToRuntime')) }}: - - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - - task: DotNetCoreCLI@2 - displayName: 'Build SqlClient [${{ parameters.operatingSystem }}]' - condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) - inputs: - command: custom - projects: build.proj - custom: build - arguments: >- - -t:BuildSqlClient - -p:ReferenceType=${{ parameters.referenceType }} - -p:TestEnabled=true - -p:GenerateNuget=false - -p:GenerateDocumentationFile=false - -p:Configuration=${{ parameters.buildConfiguration }} - -p:BuildNumber=${{ parameters.buildNumber }} - -p:AssemblyBuildNumber=${{ parameters.assemblyBuildNumber }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - verbosityRestore: Detailed - verbosityPack: Detailed - - - ${{ if or(eq(parameters.build, 'AkvProvider'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - - task: DotNetCoreCLI@2 - displayName: 'Build AKV Provider [${{ parameters.operatingSystem }}]' - condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) - inputs: - command: custom - projects: build.proj - custom: build - arguments: >- -t:BuildAkvProvider -p:ReferenceType=${{ parameters.referenceType }} -p:TestEnabled=true -p:GenerateNuget=false - -p:GenerateDocumentationFile=false -p:Configuration=${{ parameters.buildConfiguration }} -p:BuildNumber=${{ parameters.buildNumber }} -p:AssemblyBuildNumber=${{ parameters.assemblyBuildNumber }} @@ -192,3 +113,4 @@ steps: -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} verbosityRestore: Detailed verbosityPack: Detailed + 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 b1d0261abc..2c407f7d1e 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -81,168 +81,176 @@ steps: displayName: 'Run Unit Tests ${{parameters.msbuildArchitecture }}' condition: succeededOrFailed() inputs: - solution: build.proj + solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:RunUnitTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsUnit + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} + -p:TestResultsFolderPath=TestResults ${{ else }}: # x86 msbuildArguments: >- - -t:RunUnitTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsUnit + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} + -p:TestResultsFolderPath=TestResults - task: MSBuild@1 displayName: 'Run Flaky Unit Tests ${{parameters.msbuildArchitecture }}' condition: succeededOrFailed() inputs: - solution: build.proj + solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:RunUnitTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsUnit + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false ${{ else }}: # x86 msbuildArguments: >- - -t:RunUnitTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsUnit + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false continueOnError: true - task: MSBuild@1 displayName: 'Run Functional Tests ${{parameters.msbuildArchitecture }}' condition: succeededOrFailed() inputs: - solution: build.proj + solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:RunFunctionalTests - -p:TF=${{ parameters.targetFramework }} - -p:TestSet=${{ parameters.testSet }} + -t:TestMdsFunctional + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:TestResultsFolderPath=TestResults ${{ else }}: # x86 msbuildArguments: >- - -t:RunFunctionalTests - -p:TF=${{ parameters.targetFramework }} - -p:TestSet=${{ parameters.testSet }} + -t:TestMdsFunctional + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} + -p:TestResultsFolderPath=TestResults - task: MSBuild@1 condition: succeededOrFailed() displayName: 'Run Flaky Functional Tests ${{parameters.msbuildArchitecture }}' inputs: - solution: build.proj + solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:RunFunctionalTests - -p:TF=${{ parameters.targetFramework }} - -p:TestSet=${{ parameters.testSet }} + -t:TestMdsFunctional + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false ${{ else }}: # x86 msbuildArguments: >- - -t:RunFunctionalTests - -p:TF=${{ parameters.targetFramework }} - -p:TestSet=${{ parameters.testSet }} + -t:TestMdsFunctional + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false continueOnError: true - task: MSBuild@1 condition: succeededOrFailed() displayName: 'Run Manual Tests ${{parameters.msbuildArchitecture }}' inputs: - solution: build.proj + solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:RunManualTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsManual + -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:TestResultsFolderPath=TestResults ${{ else }}: # x86 msbuildArguments: >- - -t:RunManualTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsManual + -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} + -p:TestResultsFolderPath=TestResults retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} - task: MSBuild@1 condition: succeededOrFailed() displayName: 'Run Flaky Manual Tests ${{parameters.msbuildArchitecture }}' inputs: - solution: build.proj + solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:RunManualTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsManual + -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false ${{ else }}: # x86 msbuildArguments: >- - -t:RunManualTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsManual + -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false continueOnError: true - ${{ else }}: # Linux or macOS @@ -252,18 +260,17 @@ steps: condition: succeededOrFailed() inputs: command: custom - projects: build.proj + projects: build2.proj custom: msbuild arguments: >- - -t:RunUnitTests - -p:TF=${{ parameters.targetFramework }} - -p:TestSet=${{ parameters.testSet }} + -t:TestMdsUnit + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:Platform=${{ parameters.platform }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} + -p:TestResultsFolderPath=TestResults verbosityRestore: Detailed verbosityPack: Detailed @@ -272,20 +279,19 @@ steps: condition: succeededOrFailed() inputs: command: custom - projects: build.proj + projects: build2.proj custom: msbuild arguments: >- - -t:RunUnitTests - -p:TF=${{ parameters.targetFramework }} - -p:TestSet=${{ parameters.testSet }} + -t:TestMdsUnit + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:Platform=${{ parameters.platform }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false verbosityRestore: Detailed verbosityPack: Detailed continueOnError: true @@ -295,18 +301,17 @@ steps: condition: succeededOrFailed() inputs: command: custom - projects: build.proj + projects: build2.proj custom: msbuild arguments: >- - -t:RunFunctionalTests - -p:TF=${{ parameters.targetFramework }} - -p:TestSet=${{ parameters.testSet }} + -t:TestMdsFunctional + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:Platform=${{ parameters.platform }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} + -p:TestResultsFolderPath=TestResults verbosityRestore: Detailed verbosityPack: Detailed @@ -315,20 +320,19 @@ steps: displayName: 'Run Flaky Functional Tests' inputs: command: custom - projects: build.proj + projects: build2.proj custom: msbuild arguments: >- - -t:RunFunctionalTests - -p:TF=${{ parameters.targetFramework }} - -p:TestSet=${{ parameters.testSet }} + -t:TestMdsFunctional + -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:Platform=${{ parameters.platform }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false verbosityRestore: Detailed verbosityPack: Detailed continueOnError: true @@ -338,18 +342,18 @@ steps: condition: succeededOrFailed() inputs: command: custom - projects: build.proj + projects: build2.proj custom: msbuild arguments: >- - -t:RunManualTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsManual + -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:platform=${{ parameters.platform }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} + -p:TestResultsFolderPath=TestResults verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} @@ -359,20 +363,20 @@ steps: condition: succeededOrFailed() inputs: command: custom - projects: build.proj + projects: build2.proj custom: msbuild arguments: >- - -t:RunManualTests - -p:TF=${{ parameters.targetFramework }} + -t:TestMdsManual + -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:Platform=${{ parameters.platform }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} - -p:Filter="category=flaky" - -p:CollectCodeCoverage=false + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false verbosityRestore: Detailed verbosityPack: Detailed continueOnError: true diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index 663532b905..a575d2dd6a 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -44,6 +44,7 @@ pr: - tools/* - azurepipelines-coverage.yml - build.proj + - build2.proj - Directory.Packages.props - dotnet-tools.json - global.json diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index 868b3010ff..dd8f34e58b 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -44,6 +44,7 @@ pr: - tools/* - azurepipelines-coverage.yml - build.proj + - build2.proj - Directory.Packages.props - dotnet-tools.json - global.json diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 0a80e8f0ca..b25ed21444 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -220,11 +220,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eng", "eng", "{4600328C-C13 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 ..\eng\pipelines\sqlclient-pr-package-ref-pipeline.yml = ..\eng\pipelines\sqlclient-pr-package-ref-pipeline.yml ..\eng\pipelines\sqlclient-pr-project-ref-pipeline.yml = ..\eng\pipelines\sqlclient-pr-project-ref-pipeline.yml ..\eng\pipelines\stress-tests-pipeline.yml = ..\eng\pipelines\stress-tests-pipeline.yml @@ -236,12 +234,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{ 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}" @@ -251,22 +246,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stages", "stages", "{01FA47 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-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-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\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 @@ -274,27 +264,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{EABE3A3E 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}" ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\jobs\build-akv-official-job.yml = ..\eng\pipelines\jobs\build-akv-official-job.yml ..\eng\pipelines\jobs\pack-abstractions-package-ci-job.yml = ..\eng\pipelines\jobs\pack-abstractions-package-ci-job.yml ..\eng\pipelines\jobs\pack-azure-package-ci-job.yml = ..\eng\pipelines\jobs\pack-azure-package-ci-job.yml + ..\eng\pipelines\jobs\pack-logging-package-ci-job.yml = ..\eng\pipelines\jobs\pack-logging-package-ci-job.yml ..\eng\pipelines\jobs\stress-tests-ci-job.yml = ..\eng\pipelines\jobs\stress-tests-ci-job.yml ..\eng\pipelines\jobs\test-abstractions-package-ci-job.yml = ..\eng\pipelines\jobs\test-abstractions-package-ci-job.yml ..\eng\pipelines\jobs\test-azure-package-ci-job.yml = ..\eng\pipelines\jobs\test-azure-package-ci-job.yml @@ -302,16 +279,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jobs", "jobs", "{09352F1D-8 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-dll-signing-step.yml = ..\eng\pipelines\steps\compound-esrp-dll-signing-step.yml - ..\eng\pipelines\steps\compound-esrp-nuget-signing-step.yml = ..\eng\pipelines\steps\compound-esrp-nuget-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\install-dotnet.yml = ..\eng\pipelines\steps\install-dotnet.yml ..\eng\pipelines\steps\install-dotnet-arm64.ps1 = ..\eng\pipelines\steps\install-dotnet-arm64.ps1 - ..\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}" @@ -363,6 +332,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.Internal", "Microsoft.Data.SqlClient.Internal", "{C1D2E3F4-A5B6-7C8D-9E0F-1A2B3C4D5E6F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient", "{7289C27E-D7DF-2C71-84B4-151F3A162493}" + ProjectSection(SolutionItems) = preProject + Microsoft.Data.SqlClient\MdsVersions.props = Microsoft.Data.SqlClient\MdsVersions.props + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7E0602AC-7F0A-362A-D734-0FDDFCC600B5}" EndProject @@ -372,6 +344,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stages", "stages", "{E9D12A ProjectSection(SolutionItems) = preProject ..\eng\pipelines\stages\build-abstractions-package-ci-stage.yml = ..\eng\pipelines\stages\build-abstractions-package-ci-stage.yml ..\eng\pipelines\stages\build-azure-package-ci-stage.yml = ..\eng\pipelines\stages\build-azure-package-ci-stage.yml + ..\eng\pipelines\stages\build-logging-package-ci-stage.yml = ..\eng\pipelines\stages\build-logging-package-ci-stage.yml + ..\eng\pipelines\stages\build-sqlclient-package-ci-stage.yml = ..\eng\pipelines\stages\build-sqlclient-package-ci-stage.yml + ..\eng\pipelines\stages\generate-secrets-ci-stage.yml = ..\eng\pipelines\stages\generate-secrets-ci-stage.yml ..\eng\pipelines\stages\stress-tests-ci-stage.yml = ..\eng\pipelines\stages\stress-tests-ci-stage.yml EndProjectSection EndProject @@ -391,6 +366,48 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.GenAPI", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Cci.Extensions", "..\tools\GenAPI\Microsoft.Cci.Extensions\Microsoft.Cci.Extensions.csproj", "{2F900B8A-EA19-4274-9AE9-3E6A59856FCC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "onebranch", "onebranch", "{5EC4C165-25BB-4346-9DCC-5EFEEAC00C02}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\onebranch\sqlclient-non-official.yml = ..\eng\pipelines\onebranch\sqlclient-non-official.yml + ..\eng\pipelines\onebranch\sqlclient-official.yml = ..\eng\pipelines\onebranch\sqlclient-official.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jobs", "jobs", "{B3061B34-2A1C-49B9-A3A9-D4D37B4704B2}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\onebranch\jobs\build-signed-csproj-package-job.yml = ..\eng\pipelines\onebranch\jobs\build-signed-csproj-package-job.yml + ..\eng\pipelines\onebranch\jobs\build-signed-sqlclient-package-job.yml = ..\eng\pipelines\onebranch\jobs\build-signed-sqlclient-package-job.yml + ..\eng\pipelines\onebranch\jobs\publish-nuget-package-job.yml = ..\eng\pipelines\onebranch\jobs\publish-nuget-package-job.yml + ..\eng\pipelines\onebranch\jobs\validate-signed-package-job.yml = ..\eng\pipelines\onebranch\jobs\validate-signed-package-job.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stages", "stages", "{3654906C-6852-48AF-BC7E-B3A62F3E9DB9}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\onebranch\stages\build-stages.yml = ..\eng\pipelines\onebranch\stages\build-stages.yml + ..\eng\pipelines\onebranch\stages\release-stages.yml = ..\eng\pipelines\onebranch\stages\release-stages.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{45B9E5C3-DED5-4DB6-B1E6-4EAC4E68BA83}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\onebranch\steps\build-all-configurations-signed-dlls-step.yml = ..\eng\pipelines\onebranch\steps\build-all-configurations-signed-dlls-step.yml + ..\eng\pipelines\onebranch\steps\code-analyze-step.yml = ..\eng\pipelines\onebranch\steps\code-analyze-step.yml + ..\eng\pipelines\onebranch\steps\compound-build-csproj-step.yml = ..\eng\pipelines\onebranch\steps\compound-build-csproj-step.yml + ..\eng\pipelines\onebranch\steps\compound-esrp-dll-signing-step.yml = ..\eng\pipelines\onebranch\steps\compound-esrp-dll-signing-step.yml + ..\eng\pipelines\onebranch\steps\compound-esrp-nuget-signing-step.yml = ..\eng\pipelines\onebranch\steps\compound-esrp-nuget-signing-step.yml + ..\eng\pipelines\onebranch\steps\compound-nuget-pack-step.yml = ..\eng\pipelines\onebranch\steps\compound-nuget-pack-step.yml + ..\eng\pipelines\onebranch\steps\compound-pack-csproj-step.yml = ..\eng\pipelines\onebranch\steps\compound-pack-csproj-step.yml + ..\eng\pipelines\onebranch\steps\compound-publish-symbols-step.yml = ..\eng\pipelines\onebranch\steps\compound-publish-symbols-step.yml + ..\eng\pipelines\onebranch\steps\esrp-code-signing-step.yml = ..\eng\pipelines\onebranch\steps\esrp-code-signing-step.yml + ..\eng\pipelines\onebranch\steps\publish-symbols-step.yml = ..\eng\pipelines\onebranch\steps\publish-symbols-step.yml + ..\eng\pipelines\onebranch\steps\script-output-environment-variables-step.yml = ..\eng\pipelines\onebranch\steps\script-output-environment-variables-step.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "variables", "variables", "{E849E6FB-1AB0-4922-A060-0062EA669FD0}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\onebranch\variables\common-variables.yml = ..\eng\pipelines\onebranch\variables\common-variables.yml + ..\eng\pipelines\onebranch\variables\onebranch-variables.yml = ..\eng\pipelines\onebranch\variables\onebranch-variables.yml + ..\eng\pipelines\onebranch\variables\sqlclient-validation-variables.yml = ..\eng\pipelines\onebranch\variables\sqlclient-validation-variables.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -883,7 +900,6 @@ Global {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} @@ -918,6 +934,11 @@ Global {1DB299CE-95EA-4566-84DD-171768758291} = {351BE847-A0BF-450C-A5BC-8337AFA49EAA} {583E2B51-A90B-4F34-AD8B-4061504E855E} = {351BE847-A0BF-450C-A5BC-8337AFA49EAA} {2F900B8A-EA19-4274-9AE9-3E6A59856FCC} = {351BE847-A0BF-450C-A5BC-8337AFA49EAA} + {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} + {B3061B34-2A1C-49B9-A3A9-D4D37B4704B2} = {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} + {3654906C-6852-48AF-BC7E-B3A62F3E9DB9} = {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} + {45B9E5C3-DED5-4DB6-B1E6-4EAC4E68BA83} = {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} + {E849E6FB-1AB0-4922-A060-0062EA669FD0} = {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431} diff --git a/src/Microsoft.Data.SqlClient/MdsVersions.props b/src/Microsoft.Data.SqlClient/MdsVersions.props new file mode 100644 index 0000000000..e50e813f66 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/MdsVersions.props @@ -0,0 +1,35 @@ + + + + 7.0.0 + + 0 + + + $(MdsVersionDefault).$(BuildNumber.Split('.')[0]) + $(MdsPackageVersion.Split('-')[0]) + + + $(MdsVersionDefault).$(BuildNumber)-dev + + diff --git a/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj index 065e10dcd3..6f5fc44f59 100644 --- a/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj @@ -67,6 +67,14 @@ $(NoWarn);CS0618 + + + + $(MdsAssemblyVersion) + $(MdsAssemblyVersion) + $(MdsPackageVersion) + + - $(RepoRoot)artifacts/$(AssemblyName).notsupported/$(Configuration)/ + $(RepoRoot)artifacts/$(AssemblyName).notsupported/$(ReferenceType)-$(Configuration)/ @@ -86,7 +94,7 @@ --> $(RepoRoot)src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj - $(RepoRoot)artifacts/Microsoft.Data.SqlClient.ref/$(Configuration)/$(TargetFramework)/Microsoft.Data.SqlClient.dll + $(RepoRoot)artifacts/Microsoft.Data.SqlClient.ref/$(ReferenceType)-$(Configuration)/$(TargetFramework)/Microsoft.Data.SqlClient.dll public sealed class SqlConfigurableRetryFactory { - /// + /// public static System.Collections.ObjectModel.ReadOnlyCollection BaselineTransientErrors { get { throw null; } } /// public static SqlRetryLogicBaseProvider CreateExponentialRetryProvider(SqlRetryLogicOption retryLogicOption) { throw null; } @@ -885,7 +885,7 @@ public SqlConnection(string connectionString, Microsoft.Data.SqlClient.SqlCreden [System.ComponentModel.BrowsableAttribute(false)] [System.ComponentModel.DesignerSerializationVisibilityAttribute(0)] public Microsoft.Data.SqlClient.SqlCredential Credential { get { throw null; } set { } } - /// + /// public SspiContextProvider SspiContextProvider { get { throw null; } set { } } /// [System.ComponentModel.DesignerSerializationVisibilityAttribute(0)] @@ -1359,7 +1359,7 @@ public SqlDataAdapter(string selectCommandText, string selectConnectionString) { public event Microsoft.Data.SqlClient.SqlRowUpdatedEventHandler RowUpdated { add { } remove { } } /// public event Microsoft.Data.SqlClient.SqlRowUpdatingEventHandler RowUpdating { add { } remove { } } - /// + /// protected override void OnRowUpdated(System.Data.Common.RowUpdatedEventArgs value) { } /// protected override void OnRowUpdating(System.Data.Common.RowUpdatingEventArgs value) { } @@ -2181,7 +2181,7 @@ public sealed class SspiAuthenticationParameters { /// public SspiAuthenticationParameters( - string serverName, + string serverName, string resource, string userId = null, string databaseName = null, @@ -2201,4 +2201,4 @@ public SspiAuthenticationParameters( /// public string Password { get { throw null; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index 447fb01182..dbdf7e9338 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -2,6 +2,15 @@ Microsoft.Data.SqlClient net462;net8.0;net9.0;netstandard2.0 + true + + + + + + $(MdsAssemblyVersion) + $(MdsAssemblyVersion) + $(MdsPackageVersion) @@ -24,7 +33,7 @@ is possible but also requires specifying the IntermediateOutputPath. So while it would be nice to have a flatter directory structure, it's more hassle than its worth. --> - $(ArtifactPath)$(AssemblyName).ref/$(Configuration)/ + $(ArtifactPath)$(AssemblyName).ref/$(ReferenceType)-$(Configuration)/ @@ -35,7 +44,9 @@ have the full documentation. --> - + powershell.exe pwsh @@ -43,7 +54,7 @@ $(PowerShellCommand) -NonInteractive -ExecutionPolicy Unrestricted - -Command "$(RepoRoot)tools\intellisense\TrimDocs.ps1 -inputFile '$(OutputPath)\Microsoft.Data.SqlClient.xml' -outputFile '$(OutputPath)\Microsoft.Data.SqlClient.xml'" + -Command "$(RepoRoot)tools\intellisense\TrimDocs.ps1 -inputFile '$(DocumentationFile)' -outputFile '$(DocumentationFile)'" diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index d0713280e6..49f5677535 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -1,11 +1,21 @@ - + Microsoft.Data.SqlClient Debug;Release; - true - + true + + true + + + + + + $(MdsAssemblyVersion) + $(MdsAssemblyVersion) + $(MdsPackageVersion) + @@ -62,7 +72,7 @@ - $(ArtifactPath)$(AssemblyName)/$(Configuration)/$(NormalizedTargetOs)/ + $(ArtifactPath)$(AssemblyName)/$(ReferenceType)-$(Configuration)/$(NormalizedTargetOs)/ @@ -124,7 +134,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 0d6203c08d..8806df9432 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -845,7 +845,7 @@ private TdsOperationStatus TryCleanPartialRead() private void CleanPartialReadReliable() { AssertReaderState(requireData: true, permitAsync: false); - + TdsOperationStatus result = TryCleanPartialRead(); Debug.Assert(result == TdsOperationStatus.Done, "Should not pend on sync call"); Debug.Assert(!_sharedState._dataReady, "_dataReady should be cleared"); @@ -1054,7 +1054,7 @@ private TdsOperationStatus TryCloseInternal(bool closeReader) { Connection.RemoveWeakReference(this); // This doesn't catch everything -- the connection may be closed, but it prevents dead readers from clogging the collection } - + // IsClosed may be true if CloseReaderFromConnection was called - in which case, the session has already been closed if (!wasClosed && stateObj != null) { @@ -1073,7 +1073,7 @@ private TdsOperationStatus TryCloseInternal(bool closeReader) } } // @TODO: CER Exception Handling was removed here (see GH#3581) - + // 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 @@ -1658,7 +1658,7 @@ private TdsOperationStatus TryGetBytesInternal(int i, long dataIndex, byte[] buf { remaining = 0; TdsOperationStatus result; - + int cbytes = 0; AssertReaderState(requireData: true, permitAsync: true, columnIndex: i, enforceSequentialAccess: true); @@ -2598,7 +2598,7 @@ virtual public SqlXml GetSqlXml(int i) return sx; } - /// + /// virtual public SqlJson GetSqlJson(int i) { ReadColumn(i); @@ -2606,7 +2606,7 @@ virtual public SqlJson GetSqlJson(int i) return json; } - /// + /// virtual public SqlVector GetSqlVector(int i) where T : unmanaged { if (typeof(T) != typeof(float)) @@ -3139,7 +3139,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met 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 + // 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 @@ -3909,7 +3909,7 @@ private TdsOperationStatus TryReadColumnHeader(int i) { throw SQL.InvalidRead(); } - + return TryReadColumnInternal(i, readHeaderOnly: true); // @TODO: CER Exception Handling was removed here (see GH#3581) } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs index 96244fb7a8..0b6780f900 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs @@ -6,16 +6,16 @@ namespace Microsoft.Data { - /// + /// public static class SqlDbTypeExtensions { - /// + /// #if NET9_0_OR_GREATER 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 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 8d0ee74cc6..0c1047ee6d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs @@ -12,18 +12,18 @@ namespace Microsoft.Data.SqlTypes { - /// + /// public class SqlJson : INullable { // Our serialized JSON string, or null. private readonly string? _jsonString = null; - /// + /// public SqlJson() { } - /// + /// #if NET public SqlJson([StringSyntax(StringSyntaxAttribute.Json)] string? jsonString) #else @@ -45,8 +45,8 @@ public SqlJson(string? jsonString) _jsonString = jsonString; } - /// - public SqlJson(JsonDocument? jsonDoc) + /// + public SqlJson(JsonDocument? jsonDoc) { if (jsonDoc == null) { @@ -57,15 +57,15 @@ public SqlJson(JsonDocument? jsonDoc) _jsonString = jsonDoc.RootElement.GetRawText(); } - /// + /// public bool IsNull => _jsonString is null; - /// + /// public static SqlJson Null => new(); - /// - public string Value - { + /// + public string Value + { get { if (IsNull) @@ -77,7 +77,7 @@ public string Value } } - /// + /// public override string? ToString() { 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 index cf04ff8636..14e790e977 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs @@ -15,7 +15,7 @@ namespace Microsoft.Data.SqlTypes; -/// +/// public readonly struct SqlVector : INullable, ISqlVector where T : unmanaged { @@ -55,10 +55,10 @@ private SqlVector(int length) Memory = new(); } - /// + /// public static SqlVector CreateNull(int length) => new(length); - /// + /// public SqlVector(ReadOnlyMemory memory) { (_elementType, _elementSize) = GetTypeFieldsOrThrow(); @@ -101,16 +101,16 @@ internal string GetString() #region Properties - /// + /// public bool IsNull { get; } - /// + /// public static SqlVector? Null => null; - /// + /// public int Length { get; } - - /// + + /// public ReadOnlyMemory Memory { get; } #endregion @@ -206,7 +206,7 @@ private byte[] MakeTdsBytes(ReadOnlyMemory values) // 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); @@ -237,6 +237,6 @@ private T[] MakeArray() return MemoryMarshal.Cast(dataSpan).ToArray(); #endif } - + #endregion } diff --git a/tools/intellisense/TrimDocs.ps1 b/tools/intellisense/TrimDocs.ps1 index c5e7eeb893..b09b8fc9d7 100644 --- a/tools/intellisense/TrimDocs.ps1 +++ b/tools/intellisense/TrimDocs.ps1 @@ -7,10 +7,11 @@ param ( [string]$outputFile="" ) +$ErrorActionPreference = 'Stop' + # Validate inputFile exists if (-not (Test-Path $inputFile)) { - Write-Host "XML File not found: $inputFile" - exit + throw "XML File not found: $inputFile" } [xml]$xml = Get-Content $inputFile diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 739055dbf0..858b41628b 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -29,38 +29,7 @@ - - - 7.0.0 - - $(MdsVersionDefault).$(BuildNumber)-dev - - - - - $(MdsVersionDefault).$(AssemblyBuildNumber) - - - 7.0.0.0 - - $(AssemblyFileVersion) - - - $(MdsPackageVersion) - + diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index cbea2140c7..2e80556c98 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -123,103 +123,111 @@ command line to indicate where the DLLs are. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/targets/GenerateThisAssemblyCs.targets b/tools/targets/GenerateThisAssemblyCs.targets index dc12b40322..6dfd670c5c 100644 --- a/tools/targets/GenerateThisAssemblyCs.targets +++ b/tools/targets/GenerateThisAssemblyCs.targets @@ -8,6 +8,9 @@ + + $(AssemblyVersion) + System From e65124a251a3a8805abf8aac1bf03d18928f0a0c Mon Sep 17 00:00:00 2001 From: samsharma2700 Date: Fri, 20 Mar 2026 09:29:02 -0700 Subject: [PATCH 004/298] Scope NuGet package signing to specific packages (#4058) --- .../onebranch/jobs/build-signed-csproj-package-job.yml | 1 + .../jobs/build-signed-sqlclient-package-job.yml | 1 + .../onebranch/steps/compound-esrp-nuget-signing-step.yml | 9 +++++++-- eng/pipelines/onebranch/steps/esrp-code-signing-step.yml | 9 ++++++--- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml index 809690a886..01d9b61da1 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml @@ -191,6 +191,7 @@ jobs: authSignCertName: ${{ parameters.authSignCertName }} esrpClientId: ${{ parameters.esrpClientId }} esrpConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} + pattern: '${{ parameters.packageFullName }}.*nupkg' # Publish symbols to servers - ${{ if eq(parameters.publishSymbols, true) }}: diff --git a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml index a422a5351c..98f5c811aa 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml @@ -108,6 +108,7 @@ jobs: - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self parameters: artifactType: pkg + nupkgPattern: 'Microsoft.Data.SqlClient.$(mdsPackageVersion).*nupkg' # Copy signed DLLs and PDBs to APIScan folders. - task: CopyFiles@2 diff --git a/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml b/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml index 34e903465f..e86964cf3d 100644 --- a/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml +++ b/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml @@ -30,6 +30,11 @@ parameters: - name: esrpClientId type: string + # Glob pattern to match NuGet packages for scanning and signing. + - name: pattern + type: string + default: '*.*nupkg' + steps: # See: https://aka.ms/esrp.scantask - task: EsrpMalwareScanning@6 @@ -41,7 +46,7 @@ steps: ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' EsrpClientId: '${{ parameters.esrpClientId }}' FolderPath: '$(PACK_OUTPUT)' - Pattern: '*.*nupkg' + Pattern: '${{ parameters.pattern }}' UseMSIAuthentication: true VerboseLogin: 1 @@ -56,7 +61,7 @@ steps: AuthAKVName: '${{ parameters.authAkvName }}' AuthSignCertName: '${{ parameters.authSignCertName }}' FolderPath: '$(PACK_OUTPUT)' - Pattern: '*.*nupkg' + Pattern: '${{ parameters.pattern }}' signConfigType: 'inlineSignParams' UseMSIAuthentication: true inlineOperation: | diff --git a/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml b/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml index 59322d67aa..83053a8d29 100644 --- a/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml +++ b/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml @@ -17,6 +17,10 @@ parameters: type: string default: 'Microsoft.Data.SqlClient*.dll' + - name: nupkgPattern + type: string + default: '*.*nupkg' + - name: artifactDirectory type: string default: $(PACK_OUTPUT) @@ -125,7 +129,7 @@ steps: EsrpClientId: '${{parameters.EsrpClientId }}' UseMSIAuthentication: true FolderPath: '${{parameters.artifactDirectory }}' - Pattern: '*.*nupkg' + Pattern: '${{ parameters.nupkgPattern }}' CleanupTempStorage: 1 VerboseLogin: 1 @@ -133,7 +137,6 @@ steps: - task: EsrpCodeSigning@6 displayName: 'ESRP CodeSigning Nuget Package' inputs: - inputs: ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' @@ -142,7 +145,7 @@ steps: AuthAKVName: '${{parameters.AuthAKVName }}' AuthSignCertName: '${{parameters.AuthSignCertName }}' FolderPath: '${{parameters.artifactDirectory }}' - Pattern: '*.*nupkg' + Pattern: '${{ parameters.nupkgPattern }}' signConfigType: inlineSignParams inlineOperation: | [ From 40db478f1bced18c184ad1b23acef6490fd63d6d Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:44:30 -0300 Subject: [PATCH 005/298] Migrate to slnx solution file format (#4070) --- .devcontainer/devcontainer.json | 2 +- .github/workflows/codeql.yml | 2 +- src/Microsoft.Data.SqlClient.sln | 946 ------------------------------ src/Microsoft.Data.SqlClient.slnx | 335 +++++++++++ 4 files changed, 337 insertions(+), 948 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient.sln create mode 100644 src/Microsoft.Data.SqlClient.slnx diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c424e37834..db5f1c5da5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,7 @@ "ms-mssql.mssql" ], "settings": { - "dotnet.defaultSolution": "src/Microsoft.Data.SqlClient.sln" + "dotnet.defaultSolution": "src/Microsoft.Data.SqlClient.slnx" } } }, diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 64584746df..464c0401f0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -94,7 +94,7 @@ jobs: - name: Run manual build steps if: matrix.build-mode == 'manual' shell: bash - run: dotnet build src/Microsoft.Data.SqlClient.sln + run: dotnet build src/Microsoft.Data.SqlClient.slnx - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln deleted file mode 100644 index b25ed21444..0000000000 --- a/src/Microsoft.Data.SqlClient.sln +++ /dev/null @@ -1,946 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31912.275 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\netfx\src\Microsoft.Data.SqlClient.csproj", "{407890AC-9876-4FEF-A6F1-F36A876BAADE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0CC4817A-12F3-4357-912C-09315FAAD008}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TDS.EndPoint", "Microsoft.Data.SqlClient\tests\tools\TDS\TDS.EndPoint\TDS.EndPoint.csproj", "{1FF891B4-D3DE-4CCE-887C-CB48F5351A45}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TDS.Servers", "Microsoft.Data.SqlClient\tests\tools\TDS\TDS.Servers\TDS.Servers.csproj", "{978063D3-FBB5-4E10-8C45-67C90BE1B931}" -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.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 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netcore", "netcore", "{28E5EFE6-C9DD-4FF9-9FEC-532F72DFFA6E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netfx", "netfx", "{3FDD425C-FE01-4B56-863E-1FCDD0677CF5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Address", "Microsoft.Data.SqlClient\tests\ManualTests\SQL\UdtTest\UDTs\Address\Address.csproj", "{D1392B54-998A-4F27-BC17-4CE149117BCC}" - ProjectSection(ProjectDependencies) = postProject - {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} - {407890AC-9876-4FEF-A6F1-F36A876BAADE} = {407890AC-9876-4FEF-A6F1-F36A876BAADE} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.ManualTests", "Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTests.csproj", "{45DB5F86-7AE3-45C6-870D-F9357B66BDB5}" - ProjectSection(ProjectDependencies) = postProject - {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Circle", "Microsoft.Data.SqlClient\tests\ManualTests\SQL\UdtTest\UDTs\Circle\Circle.csproj", "{6C88F00F-9597-43AD-9E5F-9B344DA3B16F}" - ProjectSection(ProjectDependencies) = postProject - {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shapes", "Microsoft.Data.SqlClient\tests\ManualTests\SQL\UdtTest\UDTs\Shapes\Shapes.csproj", "{B73A7063-37C3-415D-AD53-BB3DA20ABD6E}" - ProjectSection(ProjectDependencies) = postProject - {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utf8String", "Microsoft.Data.SqlClient\tests\ManualTests\SQL\UdtTest\UDTs\Utf8String\Utf8String.csproj", "{E0A6BB21-574B-43D9-890D-6E1144F2EE9E}" - ProjectSection(ProjectDependencies) = postProject - {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{A2E7E470-5EFF-4828-B55E-FCBA3650F51C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\netcore\ref\Microsoft.Data.SqlClient.csproj", "{1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{771F3F1E-7A68-4A9D-ADA8-A24F1D5BE71D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\netfx\ref\Microsoft.Data.SqlClient.csproj", "{412BCCC8-19F6-489A-B594-E9A506816155}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider", "Microsoft.Data.SqlClient\add-ons\AzureKeyVaultProvider\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj", "{9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}" - ProjectSection(ProjectDependencies) = postProject - {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B} = {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B} - {412BCCC8-19F6-489A-B594-E9A506816155} = {412BCCC8-19F6-489A-B594-E9A506816155} - EndProjectSection -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}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data", "Microsoft.Data", "{908C7DD3-C999-40A6-9433-9F5ACA7C36F5}" - ProjectSection(SolutionItems) = preProject - ..\doc\snippets\Microsoft.Data\OperationAbortedException.xml = ..\doc\snippets\Microsoft.Data\OperationAbortedException.xml - ..\doc\snippets\Microsoft.Data\SqlDbTypeExtensions.xml = ..\doc\snippets\Microsoft.Data\SqlDbTypeExtensions.xml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.Sql", "Microsoft.Data.Sql", "{0CE216CE-8072-4985-B248-61F0D0BE9C2E}" - ProjectSection(SolutionItems) = preProject - ..\doc\snippets\Microsoft.Data.Sql\SqlDataSourceEnumerator.xml = ..\doc\snippets\Microsoft.Data.Sql\SqlDataSourceEnumerator.xml - ..\doc\snippets\Microsoft.Data.Sql\SqlNotificationRequest.xml = ..\doc\snippets\Microsoft.Data.Sql\SqlNotificationRequest.xml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient", "{C05F4FFE-6A14-4409-AA0A-10630BE4F1EE}" - ProjectSection(SolutionItems) = preProject - ..\doc\snippets\Microsoft.Data.SqlClient\ActiveDirectoryAuthenticationProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\ActiveDirectoryAuthenticationProvider.xml - ..\doc\snippets\Microsoft.Data.SqlClient\ApplicationIntent.xml = ..\doc\snippets\Microsoft.Data.SqlClient\ApplicationIntent.xml - ..\doc\snippets\Microsoft.Data.SqlClient\OnChangeEventHandler.xml = ..\doc\snippets\Microsoft.Data.SqlClient\OnChangeEventHandler.xml - ..\doc\snippets\Microsoft.Data.SqlClient\PoolBlockingPeriod.xml = ..\doc\snippets\Microsoft.Data.SqlClient\PoolBlockingPeriod.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SortOrder.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SortOrder.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationInitializer.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationInitializer.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationMethod.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationMethod.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationParameters.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationParameters.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationProvider.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationToken.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlAuthenticationToken.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatch.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatch.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatchCommand.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatchCommand.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatchCommandCollection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBatchCommandCollection.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopy.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopy.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnMapping.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnMapping.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnMappingCollection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnMappingCollection.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnOrderHint.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnOrderHint.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnOrderHintCollection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyColumnOrderHintCollection.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyOptions.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlBulkCopyOptions.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientDiagnostic.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientDiagnostic.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientFactory.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientFactory.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientLogger.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientLogger.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientMetaDataCollectionNames.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientMetaDataCollectionNames.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientPermission.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientPermission.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientPermissionAttribute.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlClientPermissionAttribute.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionCertificateStoreProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionCertificateStoreProvider.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionCngProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionCngProvider.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionCspProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionCspProvider.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionEnclaveProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionEnclaveProvider.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionKeyStoreProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlColumnEncryptionKeyStoreProvider.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlCommand.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlCommand.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlCommandBuilder.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlCommandBuilder.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlCommandColumnEncryptionSetting.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlCommandColumnEncryptionSetting.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlConfigurableRetryFactory.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConfigurableRetryFactory.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnection.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionAttestationProtocol.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionAttestationProtocol.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionColumnEncryptionSetting.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionColumnEncryptionSetting.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionEncryptOption.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionEncryptOption.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionIPAddressPreference.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionIPAddressPreference.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionOverrides.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionOverrides.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionStringBuilder.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlConnectionStringBuilder.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlCredential.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlCredential.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlDataAdapter.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlDataAdapter.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlDataReader.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlDataReader.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlDependency.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlDependency.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlEnclaveAttestationParameters.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlEnclaveAttestationParameters.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlEnclaveSession.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlEnclaveSession.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlError.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlError.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlErrorCollection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlErrorCollection.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlException.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlException.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlInfoMessageEventArgs.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlInfoMessageEventArgs.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlInfoMessageEventHandler.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlInfoMessageEventHandler.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlNotificationEventArgs.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlNotificationEventArgs.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlNotificationInfo.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlNotificationInfo.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlNotificationSource.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlNotificationSource.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlNotificationType.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlNotificationType.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlParameter.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlParameter.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlParameterCollection.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlParameterCollection.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryingEventArgs.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryingEventArgs.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryIntervalBaseEnumerator.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryIntervalBaseEnumerator.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryLogicBase.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryLogicBase.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryLogicBaseProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryLogicBaseProvider.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryLogicOption.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRetryLogicOption.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowsCopiedEventArgs.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowsCopiedEventArgs.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowsCopiedEventHandler.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowsCopiedEventHandler.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatedEventArgs.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatedEventArgs.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatedEventHandler.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatedEventHandler.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatingEventArgs.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatingEventArgs.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatingEventHandler.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlRowUpdatingEventHandler.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SqlTransaction.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SqlTransaction.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SspiAuthenticationParameters.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SspiAuthenticationParameters.xml - ..\doc\snippets\Microsoft.Data.SqlClient\SspiContextProvider.xml = ..\doc\snippets\Microsoft.Data.SqlClient\SspiContextProvider.xml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.DataClassification", "Microsoft.Data.SqlClient.DataClassification", "{5D1F0032-7B0D-4FB6-A969-FCFB25C9EA1D}" - ProjectSection(SolutionItems) = preProject - ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\ColumnSensitivity.xml = ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\ColumnSensitivity.xml - ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\InformationType.xml = ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\InformationType.xml - ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\Label.xml = ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\Label.xml - ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\SensitivityClassification.xml = ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\SensitivityClassification.xml - ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\SensitivityProperty.xml = ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\SensitivityProperty.xml - ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\SensitivityRank.xml = ..\doc\snippets\Microsoft.Data.SqlClient.DataClassification\SensitivityRank.xml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.Server", "Microsoft.Data.SqlClient.Server", "{650EB7FA-EB0D-4F8E-AB2C-161C3AD8E363}" - ProjectSection(SolutionItems) = preProject - ..\doc\snippets\Microsoft.Data.SqlClient.Server\SqlDataRecord.xml = ..\doc\snippets\Microsoft.Data.SqlClient.Server\SqlDataRecord.xml - ..\doc\snippets\Microsoft.Data.SqlClient.Server\SqlMetaData.xml = ..\doc\snippets\Microsoft.Data.SqlClient.Server\SqlMetaData.xml - ..\doc\snippets\Microsoft.Data.SqlClient.Server\TriggerAction.xml = ..\doc\snippets\Microsoft.Data.SqlClient.Server\TriggerAction.xml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlTypes", "Microsoft.Data.SqlTypes", "{5A7600BD-AED8-44AB-8F2A-7CB33A8D9C02}" - 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("{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}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomRetryLogicProvider", "Microsoft.Data.SqlClient\tests\CustomConfigurableRetryLogic\CustomRetryLogicProvider.csproj", "{B499E477-C9B1-4087-A5CF-5C762D90E433}" - ProjectSection(ProjectDependencies) = postProject - {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} - EndProjectSection -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}" - ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlServer.Server", "Microsoft.SqlServer.Server\Microsoft.SqlServer.Server.csproj", "{A314812A-7820-4565-A2A8-ABBE391C11E4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.SqlServer.Server", "Microsoft.SqlServer.Server", "{869A9BCC-D303-4365-9BF7-958CD6387916}" - ProjectSection(SolutionItems) = preProject - ..\doc\snippets\Microsoft.SqlServer.Server\DataAccessKind.xml = ..\doc\snippets\Microsoft.SqlServer.Server\DataAccessKind.xml - ..\doc\snippets\Microsoft.SqlServer.Server\Format.xml = ..\doc\snippets\Microsoft.SqlServer.Server\Format.xml - ..\doc\snippets\Microsoft.SqlServer.Server\IBinarySerialize.xml = ..\doc\snippets\Microsoft.SqlServer.Server\IBinarySerialize.xml - ..\doc\snippets\Microsoft.SqlServer.Server\InvalidUdtException.xml = ..\doc\snippets\Microsoft.SqlServer.Server\InvalidUdtException.xml - ..\doc\snippets\Microsoft.SqlServer.Server\SqlFacetAttribute.xml = ..\doc\snippets\Microsoft.SqlServer.Server\SqlFacetAttribute.xml - ..\doc\snippets\Microsoft.SqlServer.Server\SqlFunctionAttribute.xml = ..\doc\snippets\Microsoft.SqlServer.Server\SqlFunctionAttribute.xml - ..\doc\snippets\Microsoft.SqlServer.Server\SqlMethodAttribute.xml = ..\doc\snippets\Microsoft.SqlServer.Server\SqlMethodAttribute.xml - ..\doc\snippets\Microsoft.SqlServer.Server\SqlUserDefinedAggregateAttribute.xml = ..\doc\snippets\Microsoft.SqlServer.Server\SqlUserDefinedAggregateAttribute.xml - ..\doc\snippets\Microsoft.SqlServer.Server\SqlUserDefinedTypeAttribute.xml = ..\doc\snippets\Microsoft.SqlServer.Server\SqlUserDefinedTypeAttribute.xml - ..\doc\snippets\Microsoft.SqlServer.Server\SystemDataAccessKind.xml = ..\doc\snippets\Microsoft.SqlServer.Server\SystemDataAccessKind.xml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestClr", "TestClr", "{CDE508A5-F5D0-4A59-A4EF-978833830727}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eng", "eng", "{4600328C-C134-499F-AAE2-964E8AD5472C}" - ProjectSection(SolutionItems) = preProject - ..\build.proj = ..\build.proj - ..\build2.proj = ..\build2.proj - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{4CAE9195-4F1A-4D48-854C-1C9FBC512C66}" - ProjectSection(SolutionItems) = preProject - ..\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\sqlclient-pr-package-ref-pipeline.yml = ..\eng\pipelines\sqlclient-pr-package-ref-pipeline.yml - ..\eng\pipelines\sqlclient-pr-project-ref-pipeline.yml = ..\eng\pipelines\sqlclient-pr-project-ref-pipeline.yml - ..\eng\pipelines\stress-tests-pipeline.yml = ..\eng\pipelines\stress-tests-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\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 - 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-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-project-build-step.yml = ..\eng\pipelines\common\templates\steps\ci-project-build-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\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-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 - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libraries", "libraries", "{75BAE755-3A1F-41F2-9176-9F8FF9FEE2DD}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\libraries\ci-build-variables.yml = ..\eng\pipelines\libraries\ci-build-variables.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jobs", "jobs", "{09352F1D-878F-4F55-8AA2-6E47F1AD37D5}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\jobs\pack-abstractions-package-ci-job.yml = ..\eng\pipelines\jobs\pack-abstractions-package-ci-job.yml - ..\eng\pipelines\jobs\pack-azure-package-ci-job.yml = ..\eng\pipelines\jobs\pack-azure-package-ci-job.yml - ..\eng\pipelines\jobs\pack-logging-package-ci-job.yml = ..\eng\pipelines\jobs\pack-logging-package-ci-job.yml - ..\eng\pipelines\jobs\stress-tests-ci-job.yml = ..\eng\pipelines\jobs\stress-tests-ci-job.yml - ..\eng\pipelines\jobs\test-abstractions-package-ci-job.yml = ..\eng\pipelines\jobs\test-abstractions-package-ci-job.yml - ..\eng\pipelines\jobs\test-azure-package-ci-job.yml = ..\eng\pipelines\jobs\test-azure-package-ci-job.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{AD738BD4-6A02-4B88-8F93-FBBBA49A74C8}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\steps\install-dotnet.yml = ..\eng\pipelines\steps\install-dotnet.yml - ..\eng\pipelines\steps\install-dotnet-arm64.ps1 = ..\eng\pipelines\steps\install-dotnet-arm64.ps1 - 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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stress", "Stress", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IMonitorLoader", "Microsoft.Data.SqlClient\tests\StressTests\IMonitorLoader\IMonitorLoader.csproj", "{1A29B520-D16A-35F2-5CAC-64573C86E63D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlClient.Stress.Common", "Microsoft.Data.SqlClient\tests\StressTests\SqlClient.Stress.Common\SqlClient.Stress.Common.csproj", "{2AA12D54-540B-E515-CB82-80D691C9DCF1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlClient.Stress.Framework", "Microsoft.Data.SqlClient\tests\StressTests\SqlClient.Stress.Framework\SqlClient.Stress.Framework.csproj", "{92D9C6D6-6925-1AD1-69FA-485F83943BD2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlClient.Stress.Runner", "Microsoft.Data.SqlClient\tests\StressTests\SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj", "{4A9C11F4-9577-ABEC-C070-83A194746D9B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlClient.Stress.Tests", "Microsoft.Data.SqlClient\tests\StressTests\SqlClient.Stress.Tests\SqlClient.Stress.Tests.csproj", "{FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.Samples", "..\doc\samples\Microsoft.Data.SqlClient.Samples.csproj", "{C09B9D2F-E463-BEBD-34E4-E8F2C201A277}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.TestCommon", "Microsoft.Data.SqlClient\tests\Common\Microsoft.Data.SqlClient.TestCommon.csproj", "{3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.Extensions", "Microsoft.Data.SqlClient.Extensions", "{19F1F1E5-3013-7660-661A-2A15F7D606C1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Abstractions", "Abstractions", "{556B486E-F9B0-7EA9-6A25-DA560C312761}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{210228A5-979A-DE06-EE1F-B35C65E1583C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions", "Microsoft.Data.SqlClient.Extensions\Abstractions\src\Abstractions.csproj", "{089582DC-FC8E-4DDE-99AC-E31BF95175B0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{59667E4C-0BD2-9F48-FB50-9E55DD8B1011}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions.Test", "Microsoft.Data.SqlClient.Extensions\Abstractions\test\Abstractions.Test.csproj", "{21F71821-AC58-43A1-A0B1-A7DB5FA892D3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Azure", "Azure", "{A20114E1-82D8-903A-C389-726EB4FD943F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0D2F834B-6D91-18D0-3F09-672D448751BD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "Microsoft.Data.SqlClient.Extensions\Azure\src\Azure.csproj", "{DCD79241-612B-4081-A8CC-BD7A4ABC1662}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5AF52CDD-DF78-3712-7516-5B49F94F9491}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Test", "Microsoft.Data.SqlClient.Extensions\Azure\test\Azure.Test.csproj", "{4B953573-C3CD-4845-896B-EA0A0B7A7B27}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logging", "Logging", "{B3A1F1C2-7D4E-4A5B-9C6D-8E2F0A1B3C4D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D4E5F6A7-1B2C-3D4E-5F6A-7B8C9D0E1F2A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Microsoft.Data.SqlClient.Internal\Logging\src\Logging.csproj", "{A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.Internal", "Microsoft.Data.SqlClient.Internal", "{C1D2E3F4-A5B6-7C8D-9E0F-1A2B3C4D5E6F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient", "{7289C27E-D7DF-2C71-84B4-151F3A162493}" - ProjectSection(SolutionItems) = preProject - Microsoft.Data.SqlClient\MdsVersions.props = Microsoft.Data.SqlClient\MdsVersions.props - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7E0602AC-7F0A-362A-D734-0FDDFCC600B5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2B71F605-037E-5629-6E23-0FA3C297446D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stages", "stages", "{E9D12AEC-2F11-4871-89BB-343BF1CAEA4C}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\stages\build-abstractions-package-ci-stage.yml = ..\eng\pipelines\stages\build-abstractions-package-ci-stage.yml - ..\eng\pipelines\stages\build-azure-package-ci-stage.yml = ..\eng\pipelines\stages\build-azure-package-ci-stage.yml - ..\eng\pipelines\stages\build-logging-package-ci-stage.yml = ..\eng\pipelines\stages\build-logging-package-ci-stage.yml - ..\eng\pipelines\stages\build-sqlclient-package-ci-stage.yml = ..\eng\pipelines\stages\build-sqlclient-package-ci-stage.yml - ..\eng\pipelines\stages\generate-secrets-ci-stage.yml = ..\eng\pipelines\stages\generate-secrets-ci-stage.yml - ..\eng\pipelines\stages\stress-tests-ci-stage.yml = ..\eng\pipelines\stages\stress-tests-ci-stage.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{020A7E7B-04C9-4326-985F-045B42CC2200}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\ref\Microsoft.Data.SqlClient.csproj", "{D433ED2D-5E47-4A4B-B94A-EC71482715C7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\src\Microsoft.Data.SqlClient.csproj", "{AA77C107-9A78-4A99-98BB-21FF7A1E0B01}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureAuthentication", "..\doc\apps\AzureAuthentication\AzureAuthentication.csproj", "{C3FE67C1-D288-45ED-A35C-08107396F8BB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "notsupported", "notsupported", "{351BE847-A0BF-450C-A5BC-8337AFA49EAA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\notsupported\Microsoft.Data.SqlClient.csproj", "{1DB299CE-95EA-4566-84DD-171768758291}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.GenAPI", "..\tools\GenAPI\Microsoft.DotNet.GenAPI\Microsoft.DotNet.GenAPI.csproj", "{583E2B51-A90B-4F34-AD8B-4061504E855E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Cci.Extensions", "..\tools\GenAPI\Microsoft.Cci.Extensions\Microsoft.Cci.Extensions.csproj", "{2F900B8A-EA19-4274-9AE9-3E6A59856FCC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "onebranch", "onebranch", "{5EC4C165-25BB-4346-9DCC-5EFEEAC00C02}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\onebranch\sqlclient-non-official.yml = ..\eng\pipelines\onebranch\sqlclient-non-official.yml - ..\eng\pipelines\onebranch\sqlclient-official.yml = ..\eng\pipelines\onebranch\sqlclient-official.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jobs", "jobs", "{B3061B34-2A1C-49B9-A3A9-D4D37B4704B2}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\onebranch\jobs\build-signed-csproj-package-job.yml = ..\eng\pipelines\onebranch\jobs\build-signed-csproj-package-job.yml - ..\eng\pipelines\onebranch\jobs\build-signed-sqlclient-package-job.yml = ..\eng\pipelines\onebranch\jobs\build-signed-sqlclient-package-job.yml - ..\eng\pipelines\onebranch\jobs\publish-nuget-package-job.yml = ..\eng\pipelines\onebranch\jobs\publish-nuget-package-job.yml - ..\eng\pipelines\onebranch\jobs\validate-signed-package-job.yml = ..\eng\pipelines\onebranch\jobs\validate-signed-package-job.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stages", "stages", "{3654906C-6852-48AF-BC7E-B3A62F3E9DB9}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\onebranch\stages\build-stages.yml = ..\eng\pipelines\onebranch\stages\build-stages.yml - ..\eng\pipelines\onebranch\stages\release-stages.yml = ..\eng\pipelines\onebranch\stages\release-stages.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{45B9E5C3-DED5-4DB6-B1E6-4EAC4E68BA83}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\onebranch\steps\build-all-configurations-signed-dlls-step.yml = ..\eng\pipelines\onebranch\steps\build-all-configurations-signed-dlls-step.yml - ..\eng\pipelines\onebranch\steps\code-analyze-step.yml = ..\eng\pipelines\onebranch\steps\code-analyze-step.yml - ..\eng\pipelines\onebranch\steps\compound-build-csproj-step.yml = ..\eng\pipelines\onebranch\steps\compound-build-csproj-step.yml - ..\eng\pipelines\onebranch\steps\compound-esrp-dll-signing-step.yml = ..\eng\pipelines\onebranch\steps\compound-esrp-dll-signing-step.yml - ..\eng\pipelines\onebranch\steps\compound-esrp-nuget-signing-step.yml = ..\eng\pipelines\onebranch\steps\compound-esrp-nuget-signing-step.yml - ..\eng\pipelines\onebranch\steps\compound-nuget-pack-step.yml = ..\eng\pipelines\onebranch\steps\compound-nuget-pack-step.yml - ..\eng\pipelines\onebranch\steps\compound-pack-csproj-step.yml = ..\eng\pipelines\onebranch\steps\compound-pack-csproj-step.yml - ..\eng\pipelines\onebranch\steps\compound-publish-symbols-step.yml = ..\eng\pipelines\onebranch\steps\compound-publish-symbols-step.yml - ..\eng\pipelines\onebranch\steps\esrp-code-signing-step.yml = ..\eng\pipelines\onebranch\steps\esrp-code-signing-step.yml - ..\eng\pipelines\onebranch\steps\publish-symbols-step.yml = ..\eng\pipelines\onebranch\steps\publish-symbols-step.yml - ..\eng\pipelines\onebranch\steps\script-output-environment-variables-step.yml = ..\eng\pipelines\onebranch\steps\script-output-environment-variables-step.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "variables", "variables", "{E849E6FB-1AB0-4922-A060-0062EA669FD0}" - ProjectSection(SolutionItems) = preProject - ..\eng\pipelines\onebranch\variables\common-variables.yml = ..\eng\pipelines\onebranch\variables\common-variables.yml - ..\eng\pipelines\onebranch\variables\onebranch-variables.yml = ..\eng\pipelines\onebranch\variables\onebranch-variables.yml - ..\eng\pipelines\onebranch\variables\sqlclient-validation-variables.yml = ..\eng\pipelines\onebranch\variables\sqlclient-validation-variables.yml - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Debug|x64.ActiveCfg = Debug|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Debug|x64.Build.0 = Debug|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Debug|x86.ActiveCfg = Debug|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Debug|x86.Build.0 = Debug|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Release|Any CPU.Build.0 = Release|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Release|x64.ActiveCfg = Release|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Release|x64.Build.0 = Release|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Release|x86.ActiveCfg = Release|Any CPU - {407890AC-9876-4FEF-A6F1-F36A876BAADE}.Release|x86.Build.0 = Release|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Debug|x64.ActiveCfg = Debug|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Debug|x64.Build.0 = Debug|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Debug|x86.ActiveCfg = Debug|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Debug|x86.Build.0 = Debug|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Release|Any CPU.Build.0 = Release|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Release|x64.ActiveCfg = Release|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Release|x64.Build.0 = Release|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Release|x86.ActiveCfg = Release|Any CPU - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45}.Release|x86.Build.0 = Release|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Debug|Any CPU.Build.0 = Debug|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Debug|x64.ActiveCfg = Debug|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Debug|x64.Build.0 = Debug|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Debug|x86.ActiveCfg = Debug|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Debug|x86.Build.0 = Debug|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Release|Any CPU.ActiveCfg = Release|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Release|Any CPU.Build.0 = Release|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Release|x64.ActiveCfg = Release|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Release|x64.Build.0 = Release|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Release|x86.ActiveCfg = Release|Any CPU - {978063D3-FBB5-4E10-8C45-67C90BE1B931}.Release|x86.Build.0 = Release|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Debug|x64.ActiveCfg = Debug|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Debug|x64.Build.0 = Debug|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Debug|x86.ActiveCfg = Debug|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Debug|x86.Build.0 = Debug|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Release|Any CPU.Build.0 = Release|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Release|x64.ActiveCfg = Release|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Release|x64.Build.0 = Release|Any CPU - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Release|x86.ActiveCfg = Release|Any CPU - {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|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|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 - {37431336-5307-4184-9356-C4B7E47DC714}.Debug|x64.Build.0 = Debug|Any CPU - {37431336-5307-4184-9356-C4B7E47DC714}.Debug|x86.ActiveCfg = Debug|Any CPU - {37431336-5307-4184-9356-C4B7E47DC714}.Debug|x86.Build.0 = Debug|Any CPU - {37431336-5307-4184-9356-C4B7E47DC714}.Release|Any CPU.ActiveCfg = Release|Any CPU - {37431336-5307-4184-9356-C4B7E47DC714}.Release|Any CPU.Build.0 = Release|Any CPU - {37431336-5307-4184-9356-C4B7E47DC714}.Release|x64.ActiveCfg = Release|Any CPU - {37431336-5307-4184-9356-C4B7E47DC714}.Release|x64.Build.0 = Release|Any CPU - {37431336-5307-4184-9356-C4B7E47DC714}.Release|x86.ActiveCfg = Release|Any CPU - {37431336-5307-4184-9356-C4B7E47DC714}.Release|x86.Build.0 = Release|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|x64.ActiveCfg = Debug|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|x64.Build.0 = Debug|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|x86.ActiveCfg = Debug|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Debug|x86.Build.0 = Debug|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Release|Any CPU.Build.0 = Release|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Release|x64.ActiveCfg = Release|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Release|x64.Build.0 = Release|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Release|x86.ActiveCfg = Release|Any CPU - {D1392B54-998A-4F27-BC17-4CE149117BCC}.Release|x86.Build.0 = Release|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Debug|x64.ActiveCfg = Debug|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Debug|x64.Build.0 = Debug|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Debug|x86.ActiveCfg = Debug|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Debug|x86.Build.0 = Debug|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Release|Any CPU.Build.0 = Release|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Release|x64.ActiveCfg = Release|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Release|x64.Build.0 = Release|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Release|x86.ActiveCfg = Release|Any CPU - {45DB5F86-7AE3-45C6-870D-F9357B66BDB5}.Release|x86.Build.0 = Release|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|x64.ActiveCfg = Debug|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|x64.Build.0 = Debug|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|x86.ActiveCfg = Debug|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Debug|x86.Build.0 = Debug|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Release|Any CPU.Build.0 = Release|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Release|x64.ActiveCfg = Release|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Release|x64.Build.0 = Release|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Release|x86.ActiveCfg = Release|Any CPU - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F}.Release|x86.Build.0 = Release|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|x64.ActiveCfg = Debug|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|x64.Build.0 = Debug|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|x86.ActiveCfg = Debug|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Debug|x86.Build.0 = Debug|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Release|Any CPU.Build.0 = Release|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Release|x64.ActiveCfg = Release|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Release|x64.Build.0 = Release|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Release|x86.ActiveCfg = Release|Any CPU - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E}.Release|x86.Build.0 = Release|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|x64.ActiveCfg = Debug|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|x64.Build.0 = Debug|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|x86.ActiveCfg = Debug|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Debug|x86.Build.0 = Debug|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Release|Any CPU.Build.0 = Release|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Release|x64.ActiveCfg = Release|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Release|x64.Build.0 = Release|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Release|x86.ActiveCfg = Release|Any CPU - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E}.Release|x86.Build.0 = Release|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Debug|x64.ActiveCfg = Debug|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Debug|x64.Build.0 = Debug|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Debug|x86.ActiveCfg = Debug|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Debug|x86.Build.0 = Debug|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Release|Any CPU.Build.0 = Release|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Release|x64.ActiveCfg = Release|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Release|x64.Build.0 = Release|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Release|x86.ActiveCfg = Release|Any CPU - {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B}.Release|x86.Build.0 = Release|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Debug|Any CPU.Build.0 = Debug|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Debug|x64.ActiveCfg = Debug|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Debug|x64.Build.0 = Debug|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Debug|x86.ActiveCfg = Debug|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Debug|x86.Build.0 = Debug|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Release|Any CPU.ActiveCfg = Release|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Release|Any CPU.Build.0 = Release|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Release|x64.ActiveCfg = Release|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Release|x64.Build.0 = Release|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Release|x86.ActiveCfg = Release|Any CPU - {412BCCC8-19F6-489A-B594-E9A506816155}.Release|x86.Build.0 = Release|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Debug|x64.ActiveCfg = Debug|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Debug|x64.Build.0 = Debug|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Debug|x86.ActiveCfg = Debug|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Debug|x86.Build.0 = Debug|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|Any CPU.Build.0 = Release|Any CPU - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|x64.ActiveCfg = Release|Any CPU - {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 - {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 - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Debug|x64.Build.0 = Debug|Any CPU - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Debug|x86.ActiveCfg = Debug|Any CPU - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Debug|x86.Build.0 = Debug|Any CPU - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Release|Any CPU.Build.0 = Release|Any CPU - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Release|x64.ActiveCfg = Release|Any CPU - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Release|x64.Build.0 = Release|Any CPU - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Release|x86.ActiveCfg = Release|Any CPU - {89D6D382-9B36-43C9-A912-03802FDA8E36}.Release|x86.Build.0 = Release|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Debug|x64.ActiveCfg = Debug|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Debug|x64.Build.0 = Debug|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Debug|x86.ActiveCfg = Debug|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Debug|x86.Build.0 = Debug|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Release|Any CPU.Build.0 = Release|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Release|x64.ActiveCfg = Release|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Release|x64.Build.0 = Release|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Release|x86.ActiveCfg = Release|Any CPU - {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}.Release|x86.Build.0 = Release|Any CPU - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Debug|x64.ActiveCfg = Debug|x64 - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Debug|x64.Build.0 = Debug|x64 - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Debug|x86.ActiveCfg = Debug|x86 - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Debug|x86.Build.0 = Debug|x86 - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Release|Any CPU.Build.0 = Release|Any CPU - {B499E477-C9B1-4087-A5CF-5C762D90E433}.Release|x64.ActiveCfg = Release|x64 - {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 - {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 - {599A336B-2A5F-473D-8442-1223ED37C93E}.Debug|x64.Build.0 = Debug|x64 - {599A336B-2A5F-473D-8442-1223ED37C93E}.Debug|x86.ActiveCfg = Debug|x86 - {599A336B-2A5F-473D-8442-1223ED37C93E}.Debug|x86.Build.0 = Debug|x86 - {599A336B-2A5F-473D-8442-1223ED37C93E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {599A336B-2A5F-473D-8442-1223ED37C93E}.Release|Any CPU.Build.0 = Release|Any CPU - {599A336B-2A5F-473D-8442-1223ED37C93E}.Release|x64.ActiveCfg = Release|x64 - {599A336B-2A5F-473D-8442-1223ED37C93E}.Release|x64.Build.0 = Release|x64 - {599A336B-2A5F-473D-8442-1223ED37C93E}.Release|x86.ActiveCfg = Release|x86 - {599A336B-2A5F-473D-8442-1223ED37C93E}.Release|x86.Build.0 = Release|x86 - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Debug|x64.ActiveCfg = Debug|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Debug|x64.Build.0 = Debug|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Debug|x86.ActiveCfg = Debug|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Debug|x86.Build.0 = Debug|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Release|Any CPU.Build.0 = Release|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Release|x64.ActiveCfg = Release|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Release|x64.Build.0 = Release|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Release|x86.ActiveCfg = Release|Any CPU - {A314812A-7820-4565-A2A8-ABBE391C11E4}.Release|x86.Build.0 = 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 - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Debug|x64.ActiveCfg = Debug|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Debug|x64.Build.0 = Debug|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Debug|x86.ActiveCfg = Debug|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Debug|x86.Build.0 = Debug|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Release|Any CPU.Build.0 = Release|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Release|x64.ActiveCfg = Release|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Release|x64.Build.0 = Release|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Release|x86.ActiveCfg = Release|Any CPU - {1A29B520-D16A-35F2-5CAC-64573C86E63D}.Release|x86.Build.0 = Release|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Debug|x64.ActiveCfg = Debug|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Debug|x64.Build.0 = Debug|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Debug|x86.ActiveCfg = Debug|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Debug|x86.Build.0 = Debug|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Release|Any CPU.Build.0 = Release|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Release|x64.ActiveCfg = Release|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Release|x64.Build.0 = Release|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Release|x86.ActiveCfg = Release|Any CPU - {2AA12D54-540B-E515-CB82-80D691C9DCF1}.Release|x86.Build.0 = Release|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Debug|x64.ActiveCfg = Debug|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Debug|x64.Build.0 = Debug|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Debug|x86.ActiveCfg = Debug|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Debug|x86.Build.0 = Debug|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Release|Any CPU.Build.0 = Release|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Release|x64.ActiveCfg = Release|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Release|x64.Build.0 = Release|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Release|x86.ActiveCfg = Release|Any CPU - {92D9C6D6-6925-1AD1-69FA-485F83943BD2}.Release|x86.Build.0 = Release|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Debug|x64.ActiveCfg = Debug|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Debug|x64.Build.0 = Debug|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Debug|x86.ActiveCfg = Debug|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Debug|x86.Build.0 = Debug|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Release|Any CPU.Build.0 = Release|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Release|x64.ActiveCfg = Release|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Release|x64.Build.0 = Release|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Release|x86.ActiveCfg = Release|Any CPU - {4A9C11F4-9577-ABEC-C070-83A194746D9B}.Release|x86.Build.0 = Release|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Debug|x64.ActiveCfg = Debug|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Debug|x64.Build.0 = Debug|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Debug|x86.ActiveCfg = Debug|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Debug|x86.Build.0 = Debug|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|Any CPU.Build.0 = Release|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|x64.ActiveCfg = Release|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|x64.Build.0 = Release|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|x86.ActiveCfg = Release|Any CPU - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|x86.Build.0 = Release|Any CPU - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Debug|x64.ActiveCfg = Debug|x64 - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Debug|x64.Build.0 = Debug|x64 - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Debug|x86.ActiveCfg = Debug|x86 - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Debug|x86.Build.0 = Debug|x86 - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|Any CPU.Build.0 = Release|Any CPU - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|x64.ActiveCfg = Release|x64 - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|x64.Build.0 = Release|x64 - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|x86.ActiveCfg = Release|x86 - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|x86.Build.0 = Release|x86 - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Debug|x64.ActiveCfg = Debug|x64 - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Debug|x64.Build.0 = Debug|x64 - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Debug|x86.ActiveCfg = Debug|x86 - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Debug|x86.Build.0 = Debug|x86 - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Release|Any CPU.Build.0 = Release|Any CPU - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Release|x64.ActiveCfg = Release|x64 - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Release|x64.Build.0 = Release|x64 - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Release|x86.ActiveCfg = Release|x86 - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B}.Release|x86.Build.0 = Release|x86 - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Debug|x64.ActiveCfg = Debug|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Debug|x64.Build.0 = Debug|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Debug|x86.ActiveCfg = Debug|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Debug|x86.Build.0 = Debug|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Release|Any CPU.Build.0 = Release|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Release|x64.ActiveCfg = Release|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Release|x64.Build.0 = Release|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Release|x86.ActiveCfg = Release|Any CPU - {089582DC-FC8E-4DDE-99AC-E31BF95175B0}.Release|x86.Build.0 = Release|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Debug|x64.ActiveCfg = Debug|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Debug|x64.Build.0 = Debug|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Debug|x86.ActiveCfg = Debug|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Debug|x86.Build.0 = Debug|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Release|Any CPU.Build.0 = Release|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Release|x64.ActiveCfg = Release|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Release|x64.Build.0 = Release|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Release|x86.ActiveCfg = Release|Any CPU - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3}.Release|x86.Build.0 = Release|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Debug|x64.ActiveCfg = Debug|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Debug|x64.Build.0 = Debug|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Debug|x86.ActiveCfg = Debug|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Debug|x86.Build.0 = Debug|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Release|Any CPU.Build.0 = Release|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Release|x64.ActiveCfg = Release|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Release|x64.Build.0 = Release|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Release|x86.ActiveCfg = Release|Any CPU - {DCD79241-612B-4081-A8CC-BD7A4ABC1662}.Release|x86.Build.0 = Release|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Debug|x64.ActiveCfg = Debug|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Debug|x64.Build.0 = Debug|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Debug|x86.ActiveCfg = Debug|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Debug|x86.Build.0 = Debug|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Release|Any CPU.Build.0 = Release|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Release|x64.ActiveCfg = Release|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Release|x64.Build.0 = Release|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Release|x86.ActiveCfg = Release|Any CPU - {4B953573-C3CD-4845-896B-EA0A0B7A7B27}.Release|x86.Build.0 = Release|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Debug|x64.ActiveCfg = Debug|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Debug|x64.Build.0 = Debug|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Debug|x86.ActiveCfg = Debug|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Debug|x86.Build.0 = Debug|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Release|Any CPU.Build.0 = Release|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Release|x64.ActiveCfg = Release|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Release|x64.Build.0 = Release|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Release|x86.ActiveCfg = Release|Any CPU - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D}.Release|x86.Build.0 = Release|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Debug|x64.ActiveCfg = Debug|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Debug|x64.Build.0 = Debug|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Debug|x86.ActiveCfg = Debug|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Debug|x86.Build.0 = Debug|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Release|Any CPU.Build.0 = Release|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Release|x64.ActiveCfg = Release|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Release|x64.Build.0 = Release|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Release|x86.ActiveCfg = Release|Any CPU - {D433ED2D-5E47-4A4B-B94A-EC71482715C7}.Release|x86.Build.0 = Release|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Debug|x64.ActiveCfg = Debug|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Debug|x64.Build.0 = Debug|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Debug|x86.ActiveCfg = Debug|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Debug|x86.Build.0 = Debug|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Release|Any CPU.Build.0 = Release|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Release|x64.ActiveCfg = Release|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Release|x64.Build.0 = Release|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Release|x86.ActiveCfg = Release|Any CPU - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01}.Release|x86.Build.0 = Release|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Debug|x64.ActiveCfg = Debug|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Debug|x64.Build.0 = Debug|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Debug|x86.ActiveCfg = Debug|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Debug|x86.Build.0 = Debug|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Release|Any CPU.Build.0 = Release|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Release|x64.ActiveCfg = Release|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Release|x64.Build.0 = Release|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Release|x86.ActiveCfg = Release|Any CPU - {C3FE67C1-D288-45ED-A35C-08107396F8BB}.Release|x86.Build.0 = Release|Any CPU - {1DB299CE-95EA-4566-84DD-171768758291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1DB299CE-95EA-4566-84DD-171768758291}.Debug|x64.ActiveCfg = Debug|Any CPU - {1DB299CE-95EA-4566-84DD-171768758291}.Debug|x86.ActiveCfg = Debug|Any CPU - {1DB299CE-95EA-4566-84DD-171768758291}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1DB299CE-95EA-4566-84DD-171768758291}.Release|x64.ActiveCfg = Release|Any CPU - {1DB299CE-95EA-4566-84DD-171768758291}.Release|x86.ActiveCfg = Release|Any CPU - {583E2B51-A90B-4F34-AD8B-4061504E855E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {583E2B51-A90B-4F34-AD8B-4061504E855E}.Debug|x64.ActiveCfg = Debug|Any CPU - {583E2B51-A90B-4F34-AD8B-4061504E855E}.Debug|x86.ActiveCfg = Debug|Any CPU - {583E2B51-A90B-4F34-AD8B-4061504E855E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {583E2B51-A90B-4F34-AD8B-4061504E855E}.Release|x64.ActiveCfg = Release|Any CPU - {583E2B51-A90B-4F34-AD8B-4061504E855E}.Release|x86.ActiveCfg = Release|Any CPU - {2F900B8A-EA19-4274-9AE9-3E6A59856FCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F900B8A-EA19-4274-9AE9-3E6A59856FCC}.Debug|x64.ActiveCfg = Debug|Any CPU - {2F900B8A-EA19-4274-9AE9-3E6A59856FCC}.Debug|x86.ActiveCfg = Debug|Any CPU - {2F900B8A-EA19-4274-9AE9-3E6A59856FCC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F900B8A-EA19-4274-9AE9-3E6A59856FCC}.Release|x64.ActiveCfg = Release|Any CPU - {2F900B8A-EA19-4274-9AE9-3E6A59856FCC}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {407890AC-9876-4FEF-A6F1-F36A876BAADE} = {3FDD425C-FE01-4B56-863E-1FCDD0677CF5} - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45} = {0CC4817A-12F3-4357-912C-09315FAAD008} - {978063D3-FBB5-4E10-8C45-67C90BE1B931} = {0CC4817A-12F3-4357-912C-09315FAAD008} - {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} - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E} = {CDE508A5-F5D0-4A59-A4EF-978833830727} - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E} = {CDE508A5-F5D0-4A59-A4EF-978833830727} - {A2E7E470-5EFF-4828-B55E-FCBA3650F51C} = {28E5EFE6-C9DD-4FF9-9FEC-532F72DFFA6E} - {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} = {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} - {C05F4FFE-6A14-4409-AA0A-10630BE4F1EE} = {71F356DC-DFA3-4163-8BFE-D268722CE189} - {5D1F0032-7B0D-4FB6-A969-FCFB25C9EA1D} = {71F356DC-DFA3-4163-8BFE-D268722CE189} - {650EB7FA-EB0D-4F8E-AB2C-161C3AD8E363} = {71F356DC-DFA3-4163-8BFE-D268722CE189} - {5A7600BD-AED8-44AB-8F2A-7CB33A8D9C02} = {71F356DC-DFA3-4163-8BFE-D268722CE189} - {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} - {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} - {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} - {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} - {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {0CC4817A-12F3-4357-912C-09315FAAD008} - {1A29B520-D16A-35F2-5CAC-64573C86E63D} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {2AA12D54-540B-E515-CB82-80D691C9DCF1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {92D9C6D6-6925-1AD1-69FA-485F83943BD2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {4A9C11F4-9577-ABEC-C070-83A194746D9B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {C09B9D2F-E463-BEBD-34E4-E8F2C201A277} = {ED952CF7-84DF-437A-B066-F516E9BE1C2C} - {3FF03FA9-E3C3-49E3-9DCB-C703A5B0278B} = {0CC4817A-12F3-4357-912C-09315FAAD008} - {556B486E-F9B0-7EA9-6A25-DA560C312761} = {19F1F1E5-3013-7660-661A-2A15F7D606C1} - {210228A5-979A-DE06-EE1F-B35C65E1583C} = {556B486E-F9B0-7EA9-6A25-DA560C312761} - {089582DC-FC8E-4DDE-99AC-E31BF95175B0} = {210228A5-979A-DE06-EE1F-B35C65E1583C} - {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} = {556B486E-F9B0-7EA9-6A25-DA560C312761} - {21F71821-AC58-43A1-A0B1-A7DB5FA892D3} = {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} - {A20114E1-82D8-903A-C389-726EB4FD943F} = {19F1F1E5-3013-7660-661A-2A15F7D606C1} - {0D2F834B-6D91-18D0-3F09-672D448751BD} = {A20114E1-82D8-903A-C389-726EB4FD943F} - {DCD79241-612B-4081-A8CC-BD7A4ABC1662} = {0D2F834B-6D91-18D0-3F09-672D448751BD} - {5AF52CDD-DF78-3712-7516-5B49F94F9491} = {A20114E1-82D8-903A-C389-726EB4FD943F} - {4B953573-C3CD-4845-896B-EA0A0B7A7B27} = {5AF52CDD-DF78-3712-7516-5B49F94F9491} - {B3A1F1C2-7D4E-4A5B-9C6D-8E2F0A1B3C4D} = {C1D2E3F4-A5B6-7C8D-9E0F-1A2B3C4D5E6F} - {D4E5F6A7-1B2C-3D4E-5F6A-7B8C9D0E1F2A} = {B3A1F1C2-7D4E-4A5B-9C6D-8E2F0A1B3C4D} - {A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D} = {D4E5F6A7-1B2C-3D4E-5F6A-7B8C9D0E1F2A} - {7E0602AC-7F0A-362A-D734-0FDDFCC600B5} = {7289C27E-D7DF-2C71-84B4-151F3A162493} - {2B71F605-037E-5629-6E23-0FA3C297446D} = {7289C27E-D7DF-2C71-84B4-151F3A162493} - {E9D12AEC-2F11-4871-89BB-343BF1CAEA4C} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} - {020A7E7B-04C9-4326-985F-045B42CC2200} = {7289C27E-D7DF-2C71-84B4-151F3A162493} - {D433ED2D-5E47-4A4B-B94A-EC71482715C7} = {020A7E7B-04C9-4326-985F-045B42CC2200} - {AA77C107-9A78-4A99-98BB-21FF7A1E0B01} = {2B71F605-037E-5629-6E23-0FA3C297446D} - {351BE847-A0BF-450C-A5BC-8337AFA49EAA} = {7289C27E-D7DF-2C71-84B4-151F3A162493} - {1DB299CE-95EA-4566-84DD-171768758291} = {351BE847-A0BF-450C-A5BC-8337AFA49EAA} - {583E2B51-A90B-4F34-AD8B-4061504E855E} = {351BE847-A0BF-450C-A5BC-8337AFA49EAA} - {2F900B8A-EA19-4274-9AE9-3E6A59856FCC} = {351BE847-A0BF-450C-A5BC-8337AFA49EAA} - {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} - {B3061B34-2A1C-49B9-A3A9-D4D37B4704B2} = {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} - {3654906C-6852-48AF-BC7E-B3A62F3E9DB9} = {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} - {45B9E5C3-DED5-4DB6-B1E6-4EAC4E68BA83} = {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} - {E849E6FB-1AB0-4922-A060-0062EA669FD0} = {5EC4C165-25BB-4346-9DCC-5EFEEAC00C02} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431} - EndGlobalSection -EndGlobal diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx new file mode 100644 index 0000000000..0452cba60c --- /dev/null +++ b/src/Microsoft.Data.SqlClient.slnx @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 60d4b92f2c1ae510f0ab6b206f1a3af5688b4a92 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Mon, 23 Mar 2026 11:46:34 -0700 Subject: [PATCH 006/298] Add missing package reference to System.Data.Common (#4063) --- Directory.Packages.props | 1 + .../netfx/ref/Microsoft.Data.SqlClient.csproj | 1 + src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj | 1 + tools/specs/Microsoft.Data.SqlClient.nuspec | 1 + 4 files changed, 4 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index d043543e78..b1a858bf72 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -94,6 +94,7 @@ + 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 6765e009db..208181ffb6 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -43,6 +43,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 49f5677535..0f1c3c2d40 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -127,6 +127,7 @@ + diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 2e80556c98..75aa9e786d 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -44,6 +44,7 @@ + From f0e6f6c9af361f876f68080c2d4562872cda01e7 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:26:35 -0700 Subject: [PATCH 007/298] Eng | Disable ESRP steps on non-official pipelines (#4077) * Disable ESRP steps on non-official pipelines * Update copilot instructions * Skip Signature verification for non-official builds * Address comments * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * touch-ups --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ado-pipelines.instructions.md | 292 +++----- .../onebranch-pipeline-design.instructions.md | 659 ++++-------------- .../jobs/build-signed-csproj-package-job.yml | 51 +- .../build-signed-sqlclient-package-job.yml | 245 +++---- .../jobs/validate-signed-package-job.yml | 483 ++++++------- .../onebranch/sqlclient-non-official.yml | 53 +- .../onebranch/sqlclient-official.yml | 53 +- .../onebranch/stages/build-stages.yml | 274 ++++---- .../compound-esrp-nuget-signing-step.yml | 36 +- .../steps/esrp-code-signing-step.yml | 232 +++--- 10 files changed, 973 insertions(+), 1405 deletions(-) diff --git a/.github/instructions/ado-pipelines.instructions.md b/.github/instructions/ado-pipelines.instructions.md index 82b8046dee..db053130e0 100644 --- a/.github/instructions/ado-pipelines.instructions.md +++ b/.github/instructions/ado-pipelines.instructions.md @@ -1,195 +1,121 @@ --- applyTo: "eng/pipelines/**/*.yml" --- -# Azure DevOps Pipelines Guide - -## Overview - -This repository uses Azure DevOps Pipelines for CI/CD. The pipeline configurations are located in `eng/pipelines/`. - -**ADO Organization**: sqlclientdrivers -**ADO Project**: ADO.NET - -## Pipeline Structure - -``` -eng/pipelines/ -├── abstractions/ # Abstractions package pipelines -├── azure/ # Azure package pipelines -├── common/ # Shared templates -│ └── templates/ -│ ├── jobs/ # Reusable job templates -│ ├── stages/ # Reusable stage templates -│ └── steps/ # Reusable step templates -├── jobs/ # Top-level job definitions -├── libraries/ # Shared variable definitions -├── stages/ # Stage definitions -├── steps/ # Step definitions -├── variables/ # Variable templates -├── akv-official-pipeline.yml # AKV provider official/signing build -├── dotnet-sqlclient-ci-core.yml # Core CI pipeline (reusable) -├── dotnet-sqlclient-ci-package-reference-pipeline.yml # CI with package references -├── dotnet-sqlclient-ci-project-reference-pipeline.yml # CI with project references -├── dotnet-sqlclient-signing-pipeline.yml # Package signing pipeline -├── sqlclient-pr-package-ref-pipeline.yml # PR validation (package ref) -├── sqlclient-pr-project-ref-pipeline.yml # PR validation (project ref) -└── stress-tests-pipeline.yml # Stress testing -``` - -## Main Pipelines - -### CI Core Pipeline (`dotnet-sqlclient-ci-core.yml`) -Reusable core CI pipeline consumed by both project-reference and package-reference CI pipelines. Configurable parameters: - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `targetFrameworks` | Windows test frameworks | `[net462, net8.0, net9.0, net10.0]` | -| `targetFrameworksUnix` | Unix test frameworks | `[net8.0, net9.0, net10.0]` | -| `referenceType` | Project or Package reference | Required | -| `buildConfiguration` | Debug or Release | Required | -| `useManagedSNI` | Test with managed SNI | `[false, true]` | -| `testJobTimeout` | Test job timeout (minutes) | Required | -| `runAlwaysEncryptedTests` | Include AE tests | `true` | -| `enableStressTests` | Include stress test stage | `false` | - -### CI Reference Pipelines -- `dotnet-sqlclient-ci-project-reference-pipeline.yml` — Full CI using project references (builds from source) -- `dotnet-sqlclient-ci-package-reference-pipeline.yml` — Full CI using package references (tests against published NuGet packages) - -### PR Validation Pipelines -- `sqlclient-pr-project-ref-pipeline.yml` — PR validation with project references -- `sqlclient-pr-package-ref-pipeline.yml` — PR validation with package references - -These pipelines trigger on pull requests and run a subset of the full CI matrix to provide fast feedback. - -### Official/Signing Pipeline (`dotnet-sqlclient-signing-pipeline.yml`) -Signs and publishes NuGet packages. Used for official releases. Requires secure service connections and key vault access for code signing. - -### AKV Official Pipeline (`akv-official-pipeline.yml`) -Builds and signs the `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` add-on package separately from the main driver. Uses 1ES pipeline templates for compliance. - -### Stress Tests Pipeline (`stress-tests-pipeline.yml`) -Optional pipeline for long-running stress and endurance testing. Enabled via `enableStressTests` parameter in CI core. - -## Build Stages - -1. **build_abstractions_package_stage**: Build and pack abstractions -2. **build_sqlclient_package_stage**: Build main driver and AKV packages -3. **build_azure_package_stage**: Build Azure extensions package -4. **stress_tests_stage**: Optional stress testing -5. **run_tests_stage**: Execute all test suites +# Azure DevOps CI/CD Pipeline Guidelines + +## Purpose + +Rules and conventions for editing the Azure DevOps CI/CD pipelines that build, test, and validate Microsoft.Data.SqlClient. These pipelines live under `eng/pipelines/` (excluding `onebranch/`, which is covered by separate instructions). + +**ADO Organization**: sqlclientdrivers | **ADO Project**: ADO.NET + +## Pipeline Layout + +Two categories of pipelines exist in this repository: + +- **CI/PR pipelines** (`eng/pipelines/`) — Build, test, and validate on every push/PR +- **OneBranch pipelines** (`eng/pipelines/onebranch/`) — Official signing/release builds (separate instructions file) + +Top-level CI/PR pipeline files: +- `dotnet-sqlclient-ci-core.yml` — Reusable core template; all CI and PR pipelines extend this +- `dotnet-sqlclient-ci-package-reference-pipeline.yml` — CI with Package references (Release) +- `dotnet-sqlclient-ci-project-reference-pipeline.yml` — CI with Project references (Release) +- `sqlclient-pr-package-ref-pipeline.yml` — PR validation with Package references +- `sqlclient-pr-project-ref-pipeline.yml` — PR validation with Project references +- `stress-tests-pipeline.yml` — Stress tests triggered after successful CI-Package runs + +Reusable templates are organized under: +- `common/templates/jobs/` — Job templates (`ci-build-nugets-job`, `ci-code-coverage-job`, `ci-run-tests-job`) +- `common/templates/stages/` — Stage templates (`ci-run-tests-stage`) +- `common/templates/steps/` — Step templates (build, test, config, publish) +- `jobs/` — Package-specific CI jobs (pack/test Abstractions, Azure, Logging, stress) +- `stages/` — Package-specific CI stages (build Logging → Abstractions → SqlClient → Azure → verify → stress) +- `libraries/` — Shared variables (`ci-build-variables.yml`) +- `steps/` — SDK install steps + +## CI Core Template + +`dotnet-sqlclient-ci-core.yml` is the central orchestrator. All CI and PR pipelines extend it with different parameters. + +Key parameters: +- `referenceType` (required) — `Package` or `Project`; controls how sibling packages are referenced +- `buildConfiguration` (required) — `Debug` or `Release` +- `testJobTimeout` (required) — test job timeout in minutes +- `targetFrameworks` — Windows test TFMs; default `[net462, net8.0, net9.0, net10.0]` +- `targetFrameworksUnix` — Unix test TFMs; default `[net8.0, net9.0, net10.0]` +- `testSets` — test partitions; default `[1, 2, 3]` +- `useManagedSNI` — SNI variants to test; default `[false, true]` +- `runAlwaysEncryptedTests` — include AE test set; default `true` +- `enableStressTests` — enable stress test stage; default `false` +- `debug` — enable debug output; default `false` +- `dotnetVerbosity` — MSBuild verbosity; default `normal` + +## Build Stage Order + +Stages execute in dependency order (Package reference mode requires artifacts from prior stages): +1. `generate_secrets` — Generate test secrets +2. `build_logging_package_stage` — Build Logging package +3. `build_abstractions_package_stage` — Build Abstractions (depends on Logging) +4. `build_sqlclient_package_stage` — Build SqlClient + AKV Provider (depends on Abstractions + Logging) +5. `build_azure_package_stage` — Build Azure extensions (depends on Abstractions + Logging + SqlClient) +6. `verify_nuget_packages_stage` — Verify NuGet package metadata +7. `stress_tests_stage` — Optional stress tests +8. `ci_run_tests_stage` — Run MDS and AKV test suites + +When adding a new build stage, respect the dependency graph and pass artifact names/versions to downstream stages. + +## PR vs CI Pipeline Differences + +PR pipelines: +- Trigger on PRs to `dev/*`, `feat/*`, `main`; exclude `eng/pipelines/onebranch/*` paths +- Use reduced TFM matrix: `[net462, net8.0, net9.0]` (excludes net10.0) +- Timeout: 90 minutes +- Package-ref PR disables Always Encrypted tests in Debug config + +CI pipelines: +- Trigger on push to `main` (GitHub) and `internal/main` (ADO) with `batch: true` +- Scheduled weekday builds (see individual pipeline files for cron times) +- Full TFM matrix including net10.0 ## Test Configuration -### Test Sets -Tests are divided into sets for parallelization: -- `TestSet=1` — First partition of tests -- `TestSet=2` — Second partition -- `TestSet=3` — Third partition -- `TestSet=AE` — Always Encrypted tests - -### Test Filters -Tests use category-based filtering. The default filter excludes both `failing` and `flaky` tests: -``` -category!=failing&category!=flaky -``` - -Category values: -- `nonnetfxtests` — Excluded on .NET Framework -- `nonnetcoreapptests` — Excluded on .NET Core -- `nonwindowstests` — Excluded on Windows -- `nonlinuxtests` — Excluded on Linux -- `failing` — Known permanent failures (excluded from all runs) -- `flaky` — Intermittently failing tests (quarantined, run separately) - -### Flaky Test Quarantine in Pipelines -Quarantined tests (`[Trait("Category", "flaky")]`) run in **separate pipeline steps** after the main test steps. This ensures: -- Main test runs are **not blocked** by intermittent failures -- Flaky tests are still **monitored** for regression or resolution -- Code coverage is **not collected** for flaky test runs -- Results appear in pipeline output for visibility - -The quarantine steps are configured in: -- `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/run-all-tests-step.yml` - -### Test Timeout -All test runs use `--blame-hang-timeout 10m` (configured in `build.proj`). Tests exceeding 10 minutes are killed and reported as failures. - -### SNI Testing -The `useManagedSNI` parameter controls testing with: -- Native SNI (`false`) - Windows native library -- Managed SNI (`true`) - Cross-platform managed implementation +Test partitioning: +- Tests split into `TestSet=1`, `TestSet=2`, `TestSet=3` for parallelization +- `TestSet=AE` — Always Encrypted tests (controlled by `runAlwaysEncryptedTests`) -## Variables - -### Build Variables (`ci-build-variables.yml`) -Common build configuration: -- Package versions -- Build paths -- Signing configuration - -### Runtime Variables -Set via pipeline parameters or UI: -- `Configuration` - Debug/Release -- `Platform` - AnyCPU/x86/x64 -- `TF` - Target framework - -## Creating Pipeline Changes - -### Adding New Test Categories -1. Add category attribute to tests: `[Category("newcategory")]` -2. Update filter expressions in test job templates -3. Document category purpose in test documentation - -### Adding New Pipeline Parameters -1. Define parameter in appropriate `.yml` file -2. Add to parameter passing in calling templates -3. Document in this file - -### Modifying Build Steps -1. Changes should be made in template files for reusability -2. Test changes locally when possible -3. Submit as PR - validation will run +Test filters — default excludes `failing` and `flaky` categories: +- `failing` — known permanent failures, always excluded +- `flaky` — intermittent failures, quarantined in separate pipeline steps +- `nonnetfxtests` / `nonnetcoreapptests` — platform-specific exclusions +- `nonwindowstests` / `nonlinuxtests` — OS-specific exclusions -## Best Practices +Flaky test quarantine: +- Quarantined tests (`[Trait("Category", "flaky")]`) run in separate steps after main tests +- Main test runs are not blocked by flaky failures +- No code coverage collected for flaky runs +- Configured in `common/templates/steps/build-and-run-tests-netcore-step.yml`, `build-and-run-tests-netfx-step.yml`, and `run-all-tests-step.yml` -### Template Design -- Use templates for reusable definitions -- Pass parameters explicitly (avoid global variables) -- Use descriptive stage/job/step names +SNI testing — `useManagedSNI` controls testing with native SNI (`false`) or managed SNI (`true`) -### Variable Management -- Use template variables for shared values -- Use pipeline parameters for per-run configuration -- Avoid hardcoding versions (use Directory.Packages.props) +Test timeout — `--blame-hang-timeout 10m` (configured in `build.proj`); tests exceeding 10 minutes are killed -### Test Infrastructure -- Ensure tests are properly categorized -- Handle test configuration files properly -- Use test matrix for cross-platform coverage - -## Troubleshooting - -### Common Issues -1. **Test failures due to missing config**: Ensure `config.json` exists -2. **Platform-specific failures**: Check platform exclusion categories -3. **Timeout issues**: Increase `testJobTimeout` parameter - -### Debugging Pipelines -- Enable debug mode via `debug: true` parameter -- Use `dotnetVerbosity: diagnostic` for detailed output -- Check build logs in Azure DevOps - -## Security Considerations - -- Pipelines use service connections for artifact publishing -- Signing uses secure key vault integration -- Sensitive configuration should use pipeline secrets -- Never commit credentials in pipeline files - -## Related Documentation +## Variables -- [BUILDGUIDE.md](../../BUILDGUIDE.md) - Local build instructions -- [Azure DevOps Documentation](https://learn.microsoft.com/azure/devops/pipelines/) +- All CI build variables centralized in `libraries/ci-build-variables.yml` +- Package versions use `-ci` suffix (e.g., `7.0.0.$(Build.BuildNumber)-ci`) +- `assemblyBuildNumber` derived from first segment of `Build.BuildNumber` (16-bit safe) +- `localFeedPath` = `$(Build.SourcesDirectory)/packages` — local NuGet feed for inter-package deps +- `packagePath` = `$(Build.SourcesDirectory)/output` — NuGet pack output + +## Conventions When Editing Pipelines + +- Always use templates for reusable logic — do not inline complex steps +- Pass parameters explicitly; avoid relying on global variables +- Use descriptive stage/job/step display names +- When adding parameters, define them in the core template and thread through calling pipelines +- When adding test categories, update filter expressions in test step templates +- PR pipelines should run a minimal matrix for fast feedback +- Test changes via PR pipeline first — validation runs automatically +- Enable `debug: true` and `dotnetVerbosity: diagnostic` for troubleshooting +- Never commit credentials or secrets in pipeline files +- Signing and release are handled by OneBranch pipelines — not these CI/PR pipelines diff --git a/.github/instructions/onebranch-pipeline-design.instructions.md b/.github/instructions/onebranch-pipeline-design.instructions.md index 1c848e2493..f2319ee5b2 100644 --- a/.github/instructions/onebranch-pipeline-design.instructions.md +++ b/.github/instructions/onebranch-pipeline-design.instructions.md @@ -1,534 +1,139 @@ --- applyTo: "eng/pipelines/**/*.yml" --- -# Multi-Product Azure DevOps Pipeline in dotnet/sqlclient — Design Specification +# OneBranch Pipeline Guidelines -## 1. Overview +## Purpose -This document describes the design of the unified Azure DevOps YAML pipeline that builds, signs, packages, and optionally releases six NuGet packages with interdependencies. The pipeline uses **stages** and **jobs** to maximize parallelism while respecting dependency order. It comprises five stages: three build stages, a validation stage, and an on-demand release stage. +Rules and conventions for editing the OneBranch Azure DevOps YAML pipelines that build, sign, package, and release six NuGet packages with interdependencies. -Two pipeline variants exist from the same stage/job structure: +## Pipeline Variants + +- `sqlclient-official.yml` — Official pipeline; uses `OneBranch.Official.CrossPlat.yml`; CI trigger on `internal/main` + daily schedule at 04:30 UTC +- `sqlclient-non-official.yml` — Non-Official pipeline; uses `OneBranch.NonOfficial.CrossPlat.yml`; manual only (`pr: none`, `trigger: none`) +- Both live under `eng/pipelines/onebranch/` and extend OneBranch governed templates +- Never parameterize the OneBranch template name — hardcode it per pipeline for PRC compliance +- Official pipeline must never be run on PRs or dev branches. -| Pipeline | Template | Trigger | Purpose | -|----------|----------|---------|---------| -| `dotnet-sqlclient-official-pipeline.yml` | `OneBranch.Official.CrossPlat.yml` | CI + scheduled | Production-signed builds | -| `dotnet-sqlclient-non-official-pipeline.yml` | `OneBranch.NonOfficial.CrossPlat.yml` | Manual only | Validation / test builds (release in dry-run mode) | - -Both pipelines use the **OneBranch (1ES) governed template** infrastructure and share identical stage definitions, job templates, and variable chains. - ---- - -## 2. Products and Dependencies - -| # | Package | Dependencies | -|---|---------|-------------| -| 1 | `Microsoft.SqlServer.Server` | — | -| 2 | `Microsoft.Data.SqlClient.Internal.Logging` | — | -| 3 | `Microsoft.Data.SqlClient.Extensions.Abstractions` | `Internal.Logging` | -| 4 | `Microsoft.Data.SqlClient` | `Internal.Logging`, `Extensions.Abstractions` | -| 5 | `Microsoft.Data.SqlClient.Extensions.Azure` | `Extensions.Abstractions`, `Internal.Logging` | -| 6 | `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` | `SqlClient`, `Internal.Logging` | - ---- - -## 3. Pipeline Flow — Sequence Diagram - -```mermaid -sequenceDiagram - participant T as Trigger / User - participant P as Pipeline Orchestrator - participant B1a as Job: Build Internal.Logging - participant B1c as Job: Build SqlServer.Server - participant B1b as Job: Build Extensions.Abstractions - participant B2a as Job: Build SqlClient - participant B2b as Job: Build Extensions.Azure - participant V as Job: Validate MDS Package - participant B3 as Job: Build AKV Provider - participant R as Stage: Release - - Note over T,R: ══════ BUILD & SIGN PHASE ══════ - - T->>P: Pipeline triggered (CI / Scheduled / Manual) - - Note over P: Stage 1 — build_independent (parallel, no deps) - - par Stage 1 jobs (parallel) - P->>B1a: Build DLLs → ESRP sign DLLs → Pack → ESRP sign NuGet (Logging) - B1a-->>P: ✅ Signed .nupkg - and - P->>B1c: Build + ESRP sign + pack SqlServer.Server - B1c-->>P: ✅ Signed .nupkg - end - - Note over P: Stage 2 — build_abstractions (dependsOn: build_independent) - - P->>B1b: Build DLLs → ESRP sign DLLs → Pack → ESRP sign NuGet (Abstractions) - Note right of B1b: Downloads: Internal.Logging artifact - B1b-->>P: ✅ Signed .nupkg - - Note over P: Stage 3 — build_dependent (dependsOn: build_abstractions) - - par Stage 3 jobs (parallel) - P->>B2a: Build + ESRP sign + pack SqlClient - Note right of B2a: Downloads: Internal.Logging,
Extensions.Abstractions artifacts - B2a-->>P: ✅ Signed .nupkg + .snupkg - and - P->>B2b: Build DLLs → ESRP sign DLLs → Pack → ESRP sign NuGet (Azure) - Note right of B2b: Downloads: Extensions.Abstractions,
Internal.Logging artifacts - B2b-->>P: ✅ Signed .nupkg - end - - Note over P: Validation + Stage 4 (both dependsOn: build_dependent, run in parallel) - - par Validation and Stage 4 (parallel) - P->>V: Validate signed MDS package - V-->>P: ✅ Package validation passed - and - P->>B3: Build + ESRP sign + pack AKV Provider - Note right of B3: Downloads: SqlClient,
Internal.Logging artifacts - B3-->>P: ✅ Signed .nupkg - end - - Note over T,R: ══════ RELEASE PHASE (on-demand) ══════ - - alt At least one release parameter is true - P->>R: Stage: release (dependsOn: conditional on build stages) - Note right of R: ADO Environment Approval
(NuGet-Production environment) - R-->>P: ✅ Approved - Note right of R: Publish selected packages
via NuGetCommand@2 - R-->>P: ✅ Published to NuGet - else No release parameters set - Note over P: Release stage skipped - end - - Note over T,R: Pipeline complete 🎉 -``` - ---- - -## 4. Stage Design - -### 4.1 Build Phase - -The build phase runs automatically on every CI trigger, scheduled run, or manual queue. It is divided into four build stages plus a validation stage, based on the dependency graph. - -#### Stage 1 — `build_independent`: Independent Packages (no dependencies) - -| Job Template | Package | Build Target | Condition | -|--------------|---------|--------------|-----------| -| `build-signed-csproj-package-job.yml` | `Microsoft.Data.SqlClient.Internal.Logging` | `BuildLogging` / `PackLogging` | `buildAKVProvider OR buildSqlClient` | -| `build-signed-csproj-package-job.yml` | `Microsoft.SqlServer.Server` | `PackSqlServer` | `buildSqlServerServer` | - -- **`dependsOn`**: none -- **Parallelism**: Jobs run in parallel (depending on which are enabled) -- **Conditional builds**: Each job is wrapped with compile-time `${{ if }}` conditionals based on build parameters -- csproj-based jobs (`build-signed-csproj-package-job.yml`) perform: **Build DLLs → ESRP DLL signing → NuGet pack (NoBuild=true) → ESRP NuGet signing** → publish artifact - -#### Stage 2 — `build_abstractions`: Abstractions Package (depends on Stage 1) - -| Job Template | Package | Build Target | Artifact Dependencies | -|--------------|---------|--------------|----------------------| -| `build-signed-csproj-package-job.yml` | `Microsoft.Data.SqlClient.Extensions.Abstractions` | `BuildAbstractions` / `PackAbstractions` | `Internal.Logging` | - -- **Stage condition**: `buildSqlClient = true` (entire stage is excluded when false) -- **`dependsOn`**: `build_independent` -- Downloads `Microsoft.Data.SqlClient.Internal.Logging.nupkg` (from Stage 1) pipeline artifact - -#### Stage 3 — `build_dependent`: Core Packages (depend on Stage 2) - -| Job Template | Package | Build Target | Artifact Dependencies | -|--------------|---------|--------------|----------------------| -| `build-signed-package-job.yml` | `Microsoft.Data.SqlClient` | *(nuspec-based)* | `Internal.Logging`, `Extensions.Abstractions` | -| `build-signed-csproj-package-job.yml` | `Microsoft.Data.SqlClient.Extensions.Azure` | `BuildAzure` / `PackAzure` | `Extensions.Abstractions`, `Internal.Logging` | - -- **Stage condition**: `buildSqlClient = true` (entire stage is excluded when false) -- **`dependsOn`**: `build_abstractions` -- **Parallelism**: Both jobs run in parallel -- The MDS (SqlClient) job also publishes symbol packages (`.snupkg`) when `publishSymbols` is true -- All jobs configure APIScan with job-level `ob_sdl_apiscan_*` variables targeting package-specific folders - -#### Stage 4 — `build_addons`: Add-on Packages (depend on Stage 3) - -| Job Template | Package | Artifact Dependencies | -|--------------|---------|----------------------| -| `build-akv-official-job.yml` | `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` | `SqlClient`, `Internal.Logging` | - -- **Stage condition**: `buildAKVProvider AND buildSqlClient` (both must be true) -- **`dependsOn`**: `build_dependent` -- Downloads `Microsoft.Data.SqlClient.nupkg` (from Stage 3) and `Microsoft.Data.SqlClient.Internal.Logging.nupkg` (from Stage 1) pipeline artifacts -- Uses separate ESRP signing credentials (`Signing`-prefixed variables from `esrp-variables-v2` group) - -### 4.2 Validation Stage — `mds_package_validation` - -Validates the signed MDS (SqlClient) package after Stage 3 completes. - -- **Stage condition**: `buildSqlClient = true` -- **`dependsOn`**: `build_dependent` -- Runs in parallel with Stage 4 (`build_addons`) -- Uses `validate-signed-package-job.yml` template -- Downloads the `drop_build_dependent_build_signed_package` artifact and validates against `CurrentNetFxVersion` (default: `net462`) - -### 4.3 Release Phase — `release` - -The release stage is gated and only executes on demand when at least one release parameter is set to `true` at queue time. - -- **`dependsOn`**: Conditional based on which build stages are enabled: - - `build_independent` (when releasing SqlServer.Server or Logging) - - `build_abstractions` (when releasing Abstractions) - - `build_dependent`, `mds_package_validation` (when `buildSqlClient = true`) - - `build_addons` (when `buildAKVProvider AND buildSqlClient`) -- **Gate**: ADO Environment approvals (official pipeline only): - - Official: `NuGet-Production` environment with configured approvals - - Non-Official: `NuGet-DryRun` environment (no approvals, validation only) -- **Package selection**: Controlled by 6 runtime boolean parameters (see Section 5.2) -- **Stage condition**: The entire stage is skipped unless at least one release parameter is `true`: - ```yaml - - ${{ if or(parameters.releaseSqlServerServer, parameters.releaseLogging, ...) }}: - - stage: release - ``` -- **Publish jobs**: Each package has a conditional publish job that is included at compile time only when its parameter is `true`: - ```yaml - - ${{ if eq(parameters.releaseXxx, true) }}: - - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self - ``` -- **Environment variables**: Stage sets `ob_release_usedeploymentjob: true` for OneBranch integration: - - Official: `ob_release_environment: 'NuGet-Production'` - - Non-Official: `ob_release_environment: 'NuGet-DryRun'` - -#### Artifact → Publish Job Mapping - -| Package | Artifact Name | Publish Job | -|---------|---------------|-------------| -| `Microsoft.SqlServer.Server` | `drop_build_independent_build_package_SqlServer` | `publish_SqlServer_Server` | -| `Microsoft.Data.SqlClient.Internal.Logging` | `drop_build_independent_build_package_Logging` | `publish_Logging` | -| `Microsoft.Data.SqlClient.Extensions.Abstractions` | `drop_build_abstractions_build_package_Abstractions` | `publish_Abstractions` | -| `Microsoft.Data.SqlClient` | `drop_build_dependent_build_package_SqlClient` | `publish_SqlClient` | -| `Microsoft.Data.SqlClient.Extensions.Azure` | `drop_build_dependent_build_package_Azure` | `publish_Extensions_Azure` | -| `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` | `drop_build_addons_buildSignedAkvPackage` | `publish_AKVProvider` | - -Each publish job uses the reusable `publish-nuget-package-job.yml` template, which downloads the artifact and pushes `.nupkg`/`.snupkg` files via `NuGetCommand@2` with an external feed service connection. - -#### Dry-Run Mode - -Two ADO environments control release behavior: - -| Environment | Pipeline | Behavior | -|------------|----------|----------| -| `NuGet-DryRun` | Non-Official | Validation only — packages are never pushed | -| `NuGet-Production` | Official | Real releases with approval gate | - -**Non-official pipeline**: Always runs in dry-run mode. There is no `releaseDryRun` parameter — `dryRun: true` is hardcoded in every publish job. This prevents accidental publication from validation builds. - -**Official pipeline**: Exposes a `releaseDryRun` parameter (default: `true` for safety). When enabled, the template downloads artifacts and lists the `.nupkg`/`.snupkg` files that *would* be published but skips the actual `NuGetCommand@2` push. Set `releaseDryRun: false` to perform real pushes after final validation. - ---- - -## 5. Runtime Parameters - -### 5.1 Build Parameters - -The pipeline exposes the following parameters at queue time: - -```yaml -parameters: - - name: debug - displayName: 'Enable debug output' - type: boolean - default: false - - - name: publishSymbols - displayName: 'Publish symbols' - type: boolean - default: false - - - name: CurrentNetFxVersion - displayName: 'Lowest supported .NET Framework version (MDS validation)' - type: string - default: 'net462' - - - name: isPreview - displayName: 'Is this a preview build?' - type: boolean - default: false - - - name: testJobTimeout - displayName: 'Test job timeout (in minutes)' - type: number - default: 60 - - # Build parameters — control which packages to build - - name: buildSqlServerServer - displayName: 'Build Microsoft.SqlServer.Server' - type: boolean - default: true - - - name: buildSqlClient - displayName: 'Build Microsoft.Data.SqlClient and Extensions' - type: boolean - default: true - - - name: buildAKVProvider - displayName: 'Build Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' - type: boolean - default: true -``` - -The `isPreview` parameter controls version resolution — when `true`, each package uses its preview version (e.g., `loggingPackagePreviewVersion`) instead of the GA version (e.g., `loggingPackageVersion`). All versions are defined in the centralized `libraries/common-variables.yml`. - -The build parameters enable selective package building: -- `buildSqlServerServer` — controls SqlServer.Server build job -- `buildSqlClient` — controls MDS, Extensions.Azure, Abstractions, Logging (when AKV is disabled), and validation stages -- `buildAKVProvider` — controls AKV Provider build (also requires `buildSqlClient=true`) and Logging (when SqlClient is disabled) - -When set to `false`, the respective jobs/stages are excluded at compile-time using `${{ if }}` conditionals. This allows faster pipeline runs when only certain packages need to be built. - -### 5.2 Release Parameters - -Six boolean parameters control selective package release. All default to `false` so the release stage is skipped on normal CI/scheduled builds: - -```yaml -parameters: - - name: releaseSqlServerServer - displayName: 'Release Microsoft.SqlServer.Server' - type: boolean - default: false - - - name: releaseLogging - displayName: 'Release Microsoft.Data.SqlClient.Internal.Logging' - type: boolean - default: false - - - name: releaseAbstractions - displayName: 'Release Microsoft.Data.SqlClient.Extensions.Abstractions' - type: boolean - default: false - - - name: releaseSqlClient - displayName: 'Release Microsoft.Data.SqlClient' - type: boolean - default: false - - - name: releaseAzure - displayName: 'Release Microsoft.Data.SqlClient.Extensions.Azure' - type: boolean - default: false - - - name: releaseAKVProvider - displayName: 'Release Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' - type: boolean - default: false -``` - -#### Dry-Run Parameter (Official Pipeline Only) - -The **official pipeline** includes a `releaseDryRun` parameter that defaults to `true` for safety: - -```yaml - - name: releaseDryRun - displayName: 'Release Dry Run (do not push to NuGet)' - type: boolean - default: true # safety default — must explicitly disable for real releases -``` - -When `releaseDryRun: true`, publish jobs download artifacts and list packages but skip actual NuGet push. Set to `false` for production releases. - -> **Note**: The non-official pipeline does **not** expose this parameter — dry-run mode is hardcoded and cannot be disabled. - ---- - -## 6. Variable & Version Management - -### 6.1 Variable Chain - -Variables are defined in a layered template chain. All variable groups live inside the templates — none are declared inline at the pipeline level: - -``` -dotnet-sqlclient-official-pipeline.yml - └─ libraries/variables.yml - └─ libraries/build-variables.yml - ├─ group: 'Release Variables' - ├─ group: 'Symbols publishing' ← SymbolsPublishServer, SymbolsPublishTokenUri, etc. - └─ libraries/common-variables.yml - ├─ group: 'ESRP Federated Creds (AME)' ← ESRP signing credentials - ├─ SymbolServer / SymbolTokenUri aliases ← mapped from Symbols publishing group - └─ all package versions, paths, build variables -``` - -### 6.2 Package Version Variables - -All package versions are centralized in `libraries/common-variables.yml`: - -| Package | GA Version Var | Preview Version Var | Assembly Version Var | -|---------|---------------|--------------------|--------------------| -| Logging | `loggingPackageVersion` | `loggingPackagePreviewVersion` | `loggingAssemblyFileVersion` | -| Abstractions | `abstractionsPackageVersion` | `abstractionsPackagePreviewVersion` | `abstractionsAssemblyFileVersion` | -| SqlServer.Server | `sqlServerPackageVersion` | `sqlServerPackagePreviewVersion` | `sqlServerAssemblyFileVersion` | -| SqlClient (MDS) | `mdsPackageVersion` | `mdsPackagePreviewVersion` | `mdsAssemblyFileVersion` | -| Extensions.Azure | `azurePackageVersion` | `azurePackagePreviewVersion` | `azureAssemblyFileVersion` | -| AKV Provider | `akvPackageVersion` | `akvPackagePreviewVersion` | `akvAssemblyFileVersion` | - -The pipeline resolves `effective*Version` variables at compile time based on the `isPreview` parameter. - -### 6.3 Release & Symbol Variables - -| Variable | Defined In | Purpose | -|----------|-----------|---------| -| `NuGetServiceConnection` | `libraries/common-variables.yml` | External NuGet service connection name for `NuGetCommand@2` push | -| `SymbolServer` | `libraries/common-variables.yml` (alias) | Alias for `$(SymbolsPublishServer)` — used by MDS `publish-symbols-step.yml` | -| `SymbolTokenUri` | `libraries/common-variables.yml` (alias) | Alias for `$(SymbolsPublishTokenUri)` — used by MDS `publish-symbols-step.yml` | - -### 6.4 Variable Groups - -| Group | Included In | Purpose | -|-------|------------|---------| -| `Release Variables` | `build-variables.yml` | Release-specific configuration | -| `Symbols publishing` | `build-variables.yml` | Symbol publishing credentials (`SymbolsAzureSubscription`, `SymbolsPublishServer`, `SymbolsPublishTokenUri`, `SymbolsUploadAccount`, `SymbolsPublishProjectName`) | -| `ESRP Federated Creds (AME)` | `common-variables.yml` | Federated identity for ESRP signing (`ESRPConnectedServiceName`, `ESRPClientId`, `AppRegistrationClientId`, `AppRegistrationTenantId`, `AuthAKVName`, `AuthSignCertName`) | - ---- - -## 7. Code Signing (ESRP) - -All packages are signed using **ESRP (Enterprise Security Release Pipeline)** with federated identity authentication. - -### Signing Flow (per job) - -#### csproj-based Extension Packages (Logging, Abstractions, Azure) -1. **Build DLLs only** — `build.proj` target (e.g., `BuildLogging`) compiles assemblies without creating NuGet packages -2. **ESRP DLL signing** — Assemblies are signed with Authenticode certificates via ESRP -3. **NuGet pack** — `build.proj` pack target (e.g., `PackLogging`) creates `.nupkg` from signed DLLs using `NoBuild=true` -4. **ESRP NuGet signing** — The `.nupkg` files are signed with NuGet certificates via ESRP - -This workflow ensures the NuGet package contains **signed DLLs** rather than signing the NuGet package around unsigned assemblies. - -#### nuspec-based Packages (SqlServer.Server, SqlClient, AKV Provider) -1. **Build + pack** — MSBuild creates both assemblies and NuGet packages -2. **ESRP DLL signing** — Assemblies are signed with Authenticode certificates via ESRP -3. **ESRP NuGet signing** — The `.nupkg` files are signed with NuGet certificates via ESRP - -### Credential Model - -- Extension packages (Logging, Abstractions, Azure, SqlServer.Server, SqlClient) use the primary ESRP credentials from the `ESRP Federated Creds (AME)` variable group (loaded via `common-variables.yml`) -- AKV Provider uses separate `Signing`-prefixed credential parameters that are passed explicitly to the `build-akv-official-job.yml` template -- All credentials are sourced from Azure Key Vault and federated identity — no secrets stored in pipeline YAML - ---- - -## 8. SDL & Compliance (OneBranch) - -Both pipelines use **OneBranch governed templates** for 1ES compliance. The SDL configuration differs between Official and Non-Official: - -| SDL Tool | Official | Non-Official | Purpose | -|----------|----------|--------------|---------| -| **TSA** | ✅ `enabled: true` | ❌ `enabled: false` | Uploads SDL results to TSA for downstream analysis | -| **ApiScan** | ✅ `enabled: true`, `break: true` | ✅ `enabled: true`, `break: true` | Scans APIs for compliance issues | -| **CodeQL** | ✅ (non-preview) | ✅ (non-preview) | Static analysis for security vulnerabilities | -| **SBOM** | ✅ (non-preview) | ✅ (non-preview) | Software Bill of Materials generation | -| **Policheck** | ✅ `break: true` | ✅ `break: true` | Scans for policy-violating content | -| **BinSkim** | ✅ (async, non-preview) | ✅ (async, non-preview) | Binary security analysis | -| **CredScan** | ✅ (async, non-preview) | ✅ (async, non-preview) | Credential leak detection | -| **Roslyn** | ✅ (async, non-preview) | ✅ (async, non-preview) | Roslyn-based security analyzers | -| **Armory** | ✅ `break: true` | ✅ `break: true` | Additional security scanning | - -### APIScan Configuration - -APIScan is configured at **both pipeline level and job level**: - -**Pipeline-level** (`globalSdl:apiscan:`): Sets default configuration inherited by all jobs. This is configured for MDS (Microsoft.Data.SqlClient) as the primary product. - -**Job-level** (`ob_sdl_apiscan_*` variables): Each build job overrides the pipeline defaults with package-specific settings: - -| Variable | Purpose | -|----------|---------| -| `ob_sdl_apiscan_enabled` | Enable/disable APIScan for this job (`true`) | -| `ob_sdl_apiscan_softwareFolder` | Path to signed DLLs for scanning | -| `ob_sdl_apiscan_symbolsFolder` | Path to PDBs for scanning | -| `ob_sdl_apiscan_softwarename` | Package name (e.g., `Microsoft.Data.SqlClient.Internal.Logging`) | -| `ob_sdl_apiscan_versionNumber` | Assembly file version | - -Each job copies its signed DLLs and PDBs to a package-specific folder under `$(Build.SourcesDirectory)/apiScan//` after ESRP DLL signing, ensuring APIScan analyzes the correct signed binaries for each package. - -> **PRC Compliance**: The Official pipeline hardcodes `OneBranch.Official.CrossPlat.yml` (not parameterized) to satisfy Production Readiness Check static verification requirements. - ---- - -## 9. Artifact Strategy - -- Each build job publishes its output as a **pipeline artifact** managed by OneBranch's `ob_outputDirectory` convention. -- Artifact names follow the OneBranch auto-generated pattern: `drop__` (e.g., `drop_build_dependent_build_package_SqlClient`). -- Downstream stages use `DownloadPipelineArtifact@2` to pull required packages into a local directory. -- A local NuGet source is configured at build time pointing to the downloaded artifacts directory so `dotnet restore` resolves internal dependencies. - ---- - -## 10. Trigger Configuration - -### Official Pipeline (`dotnet-sqlclient-official-pipeline.yml`) - -```yaml -trigger: - branches: - include: - - internal/main - paths: - include: - - .azuredevops - - .config - - doc - - eng/pipelines - - src - - tools - - azurepipelines-coverage.yml - - build.proj - - NuGet.config - -schedules: - - cron: '30 4 * * Mon' # Weekly Sunday 9:30 PM (UTC-7) - branches: { include: [internal/main] } - always: true - - cron: '30 3 * * Mon-Fri' # Weekday 8:30 PM (UTC-7) - branches: { include: [internal/main] } -``` - -- **CI trigger**: Runs on pushes to `internal/main` when relevant paths change -- **Scheduled**: Weekly full build (Sundays) + weekday builds (Mon–Fri) -- **No PR trigger**: Official pipeline should not run on PRs (separate PR pipelines exist) - -### Non-Official Pipeline (`dotnet-sqlclient-non-official-pipeline.yml`) - -```yaml -trigger: none -pr: none -``` - -- **Manual only**: Queued on-demand for validation/test builds - ---- - -## 11. Infrastructure - -| Concern | Implementation | -|---------|---------------| -| **Pipeline template** | OneBranch governed templates (`OneBranch.Pipelines/GovernedTemplates`) | -| **Build agents** | OneBranch-managed Windows containers (`WindowsHostVersion: 1ESWindows2022`) | -| **.NET SDK** | Pinned via `global.json` (with `useGlobalJson: true` in install steps) | -| **Code signing** | ESRP v2 with federated identity (Azure Key Vault backed) | -| **Symbol publishing** | Optional, controlled by `publishSymbols` parameter; uses `Symbols publishing` variable group (aliases `SymbolServer`/`SymbolTokenUri` defined in `common-variables.yml`) | - ---- - -## 12. Key Design Decisions - -1. **Single pipeline, multiple stages** — avoids managing 6 separate pipelines while keeping clear separation of concerns. -2. **Official + Non-Official variants** — hardcoded OneBranch templates (no parameterized `oneBranchType`) for PRC compliance; Non-Official variant allows manual validation builds. -3. **Parallel jobs within stages** — minimizes total wall-clock time; only waits where dependencies demand it. -4. **Pipeline artifacts over Universal Packages** — faster, ephemeral, scoped to the run; appropriate for build-time dependency resolution. -5. **ESRP-based code signing** — all DLLs and NuGet packages are signed in-pipeline using ESRP with federated identity; no secrets in YAML. -6. **Centralized version management** — all 6 package versions (GA + preview) defined once in `libraries/common-variables.yml`; `isPreview` toggle selects the active set. -7. **Dependency-aware stage ordering** — ensures packages are always built after their dependencies, guaranteeing consistent, reproducible builds. -8. **Validation in parallel with Stage 3** — MDS package validation runs alongside AKV Provider build (both depend on Stage 2), reducing total pipeline duration. -9. **Selective on-demand release** — 6 boolean parameters control which packages are published; the release stage is entirely skipped when none are selected, keeping normal CI builds unaffected. -10. **ADO Environment approval gate** — two environments: `NuGet-Production` (official, with configured approvals) and `NuGet-DryRun` (non-official, validation only). Both use `ob_release_environment` for OneBranch integration. -11. **Compile-time conditional publish jobs** — `${{ if eq(parameters.releaseXxx, true) }}` template expansion ensures unselected publish jobs are excluded entirely from the pipeline run (not just skipped at runtime). -12. **Mandatory dry-run for non-official** — the non-official variant hardcodes `dryRun: true` (no parameter), preventing accidental publication. The official variant defaults `releaseDryRun: true` for safety but allows override for actual releases. -13. **Selective build parameters** — `buildSqlClient`, `buildSqlServerServer`, and `buildAKVProvider` allow building subsets of packages, with dependency-aware conditionals ensuring Logging builds when either SqlClient or AKV is needed. +## Package Dependency Order + +Respect this graph when modifying build stages: + +1. `Microsoft.SqlServer.Server` — no dependencies +2. `Microsoft.Data.SqlClient.Internal.Logging` — no dependencies +3. `Microsoft.Data.SqlClient.Extensions.Abstractions` — depends on Logging +4. `Microsoft.Data.SqlClient` — depends on Logging + Abstractions +5. `Microsoft.Data.SqlClient.Extensions.Azure` — depends on Abstractions + Logging +6. `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` — depends on SqlClient + Abstractions + Logging + +## Build Stages + +Defined in `stages/build-stages.yml`. Four build stages plus validation, ordered by dependency: + +- **`build_independent`** (Stage 1) — Logging and SqlServer.Server in parallel; no inter-package dependencies +- **`build_abstractions`** (Stage 2) — Abstractions; `dependsOn: build_independent`; downloads Logging artifact +- **`build_dependent`** (Stage 3) — SqlClient and Extensions.Azure in parallel; `dependsOn: build_abstractions`; downloads Abstractions + Logging artifacts +- **`build_addons`** (Stage 4) — AKV Provider; `dependsOn: build_dependent`; downloads SqlClient + Abstractions + Logging artifacts +- **`mds_package_validation`** — Validates signed SqlClient package; `dependsOn: build_dependent`; runs in parallel with Stage 4 + +Stage conditional rules: +- Wrap stages/jobs in `${{ if }}` compile-time conditionals based on build parameters +- `buildSqlClient` controls Stages 2, 3, validation, and Logging (when AKV is disabled) +- `buildAKVProvider AND buildSqlClient` controls Stage 4 +- `buildSqlServerServer` controls SqlServer.Server job in Stage 1 +- Logging builds when `buildAKVProvider OR buildSqlClient` is true + +## Job Templates + +- **`build-signed-csproj-package-job.yml`** — Generic job for csproj-based packages (Logging, SqlServer.Server, Abstractions, Azure, AKV Provider). Flow: Build DLLs → ESRP DLL signing → NuGet pack (`NoBuild=true`) → ESRP NuGet signing +- **`build-signed-sqlclient-package-job.yml`** — SqlClient-specific job (nuspec-based). Flow: Build all configurations → ESRP DLL signing (main + resource DLLs) → NuGet pack via nuspec → ESRP NuGet signing +- **`validate-signed-package-job.yml`** — Validates signed MDS package (signature, strong names, folder structure, target frameworks) +- **`publish-nuget-package-job.yml`** — Reusable release job using OneBranch `templateContext.type: releaseJob` with `inputs` for artifact download; pushes via `NuGetCommand@2` + +When adding a new csproj-based package: +- Use `build-signed-csproj-package-job.yml` with appropriate `packageName`, `packageFullName`, `versionProperties`, and `downloadArtifacts` +- Add build and pack targets to `build.proj` +- Add version variables to `variables/common-variables.yml` +- Add artifact name variable to `variables/onebranch-variables.yml` + +## Release Stage + +- Defined in `stages/release-stages.yml`; produces stage `release_production` (official) or `release_test` (non-official) via `stageNameSuffix` parameter +- Entire stage excluded at compile time when no release parameters are true +- `dependsOn` is conditional based on which release parameters are set +- `releaseToProduction` parameter controls NuGet target feed: + - `true` → service connection `ADO Nuget Org Connection` (NuGet Production) + - `false` → service connection `ADO Nuget Org Test Connection` (NuGet Test) +- Non-official pipeline always sets `releaseToProduction: false` +- Environment gating: + - Official: `ob_release_environment: Production`, `ob_deploymentjob_environment: NuGet-Production` + - Non-official: `ob_release_environment: Test`, `ob_deploymentjob_environment: NuGet-DryRun` +- Each publish job uses OneBranch deployment job syntax (`templateContext.type: releaseJob` with `inputs` for artifact download) + +## Parameters + +Build parameters (all boolean, default `true`): +- `debug` — enable debug output (default `false`) +- `isPreview` — use preview version numbers (default `false`) +- `publishSymbols` — publish symbols to servers (default `false`) +- `buildSqlServerServer` — build SqlServer.Server package +- `buildSqlClient` — build SqlClient, Extensions.Azure, Abstractions, and Logging +- `buildAKVProvider` — build AKV Provider (requires `buildSqlClient`) + +Release parameters (all boolean, default `false`): +- `releaseSqlServerServer`, `releaseLogging`, `releaseAbstractions`, `releaseSqlClient`, `releaseAzure`, `releaseAKVProvider` + +Official-only parameter: +- `releaseToProduction` — push to NuGet Production feed (default `false`) + +When `isPreview` is true, pipeline resolves `effective*Version` variables to preview versions; otherwise GA versions. All versions defined in `variables/common-variables.yml`. + +## Variables and Versions + +- Variable chain: pipeline YAML → `variables/onebranch-variables.yml` → `variables/common-variables.yml` +- All package versions (GA, preview, assembly file) centralized in `variables/common-variables.yml` +- `effective*Version` pipeline variables map to selected version set based on `isPreview` +- Artifact name variables defined in `variables/onebranch-variables.yml` following `drop__` pattern +- `assemblyBuildNumber` derived from first segment of `Build.BuildNumber` only (16-bit limit) +- When adding a new package, add GA version, preview version, and assembly file version entries + +Variable groups: +- `Release Variables` — release configuration (in `common-variables.yml`) +- `Symbols publishing` — symbol publishing credentials (in `common-variables.yml`) +- `ESRP Federated Creds (AME)` — ESRP signing credentials (in `common-variables.yml`) + +## Code Signing (ESRP) + +- Uses ESRP v6 tasks (`EsrpMalwareScanning@6`, `EsrpCodeSigning@6`) with MSI/federated identity authentication +- Signing only runs when `isOfficial: true` — non-official pipelines skip ESRP steps +- csproj-based packages: sign DLLs first → pack with `NoBuild=true` → sign NuGet package (ensures NuGet contains signed DLLs) +- SqlClient: sign DLLs (including resource DLLs) → nuspec pack → sign NuGet package +- DLL signing uses keyCode `CP-230012` (Authenticode); NuGet signing uses keyCode `CP-401405` +- All ESRP credentials come from variable groups — never hardcode secrets in YAML + +## SDL and Compliance + +- TSA: enabled only in official pipeline; disabled in non-official to avoid spurious alerts +- ApiScan: enabled in both; currently `break: false` pending package registration +- Each build job sets `ob_sdl_apiscan_*` variables pointing to `$(Build.SourcesDirectory)/apiScan//` +- CodeQL, SBOM, Policheck (`break: true`): enabled in both pipelines +- asyncSdl `enabled: false` in both; individual sub-tools (CredScan, BinSkim, Armory, Roslyn) configured underneath +- Policheck exclusions: `$(REPO_ROOT)\.config\PolicheckExclusions.xml` +- CredScan suppressions: `$(REPO_ROOT)/.config/CredScanSuppressions.json` + +## Artifact Conventions + +- `ob_outputDirectory` set to `$(PACK_OUTPUT)` (= `$(REPO_ROOT)/output`) — OneBranch auto-publishes this directory +- Artifact names follow `drop__` — defined in `variables/onebranch-variables.yml` +- Downstream jobs download artifacts via `DownloadPipelineArtifact@2` into `$(Build.SourcesDirectory)/packages` +- Downloaded packages serve as a local NuGet source for `dotnet restore` +- If stage or job names change, update artifact name variables in `onebranch-variables.yml` + +## Common Pitfalls + +- Do not use `PublishPipelineArtifacts` task — OneBranch auto-publishes from `ob_outputDirectory` +- Do not add `NuGetToolInstaller@1` in OneBranch containers — NuGet is pre-installed +- Variable templates are under `variables/` not `libraries/` +- Always test parameter changes in the non-official pipeline first +- When modifying stage names, update all `dependsOn` references and artifact name variables +- Release jobs must use `templateContext.type: releaseJob` with `inputs` for artifact download — deployment jobs do not auto-download artifacts diff --git a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml index 01d9b61da1..e257386918 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml @@ -70,6 +70,11 @@ parameters: - name: publishSymbols type: boolean + # True to enable ESRP malware scanning and code signing steps, which should not be + # run on non-official pipelines as they access production resources. + - name: isOfficial + type: boolean + # Values required by ESRP tasks. - name: esrpConnectedServiceName type: string @@ -144,18 +149,19 @@ jobs: buildConfiguration: ${{ parameters.buildConfiguration }} versionProperties: ${{ parameters.versionProperties }} - # ESRP sign the DLLs. - - template: /eng/pipelines/onebranch/steps/compound-esrp-dll-signing-step.yml@self - parameters: - appRegistrationClientId: ${{ parameters.appRegistrationClientId }} - appRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} - authAkvName: ${{ parameters.authAkvName }} - authSignCertName: ${{ parameters.authSignCertName }} - esrpClientId: ${{ parameters.esrpClientId }} - esrpConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} - pattern: ${{ parameters.packageFullName }}.dll - - # Copy signed DLLs and PDBs to APIScan folders. + - ${{ if eq(parameters.isOfficial, true) }}: + # ESRP sign the DLLs. + - template: /eng/pipelines/onebranch/steps/compound-esrp-dll-signing-step.yml@self + parameters: + appRegistrationClientId: ${{ parameters.appRegistrationClientId }} + appRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} + authAkvName: ${{ parameters.authAkvName }} + authSignCertName: ${{ parameters.authSignCertName }} + esrpClientId: ${{ parameters.esrpClientId }} + esrpConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} + pattern: ${{ parameters.packageFullName }}.dll + + # Copy signed/unsigned DLLs and PDBs to APIScan folders. - task: CopyFiles@2 displayName: Copy DLLs for APIScan inputs: @@ -182,16 +188,17 @@ jobs: buildConfiguration: ${{ parameters.buildConfiguration }} versionProperties: ${{ parameters.versionProperties }} - # ESRP sign the NuGet package. - - template: /eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml@self - parameters: - appRegistrationClientId: ${{ parameters.appRegistrationClientId }} - appRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} - authAkvName: ${{ parameters.authAkvName }} - authSignCertName: ${{ parameters.authSignCertName }} - esrpClientId: ${{ parameters.esrpClientId }} - esrpConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} - pattern: '${{ parameters.packageFullName }}.*nupkg' + - ${{ if eq(parameters.isOfficial, true) }}: + # ESRP sign the NuGet package. + - template: /eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml@self + parameters: + appRegistrationClientId: ${{ parameters.appRegistrationClientId }} + appRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} + authAkvName: ${{ parameters.authAkvName }} + authSignCertName: ${{ parameters.authSignCertName }} + esrpClientId: ${{ parameters.esrpClientId }} + esrpConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} + pattern: ${{ parameters.packageFullName }}.*nupkg # Publish symbols to servers - ${{ if eq(parameters.publishSymbols, true) }}: diff --git a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml index 98f5c811aa..f3033b07f0 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml @@ -16,123 +16,130 @@ parameters: - name: isPreview type: boolean + # True to enable ESRP malware scanning and code signing steps, which should not + # be run on non-official pipelines as they access production resources. + - name: isOfficial + type: boolean + jobs: -- job: build_package_SqlClient - displayName: 'Build Microsoft.Data.SqlClient' - pool: - type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs - - variables: - ob_outputDirectory: $(PACK_OUTPUT) - # APIScan configuration for this Extension package - ob_sdl_apiscan_enabled: true - ob_sdl_apiscan_softwareFolder: $(Build.SourcesDirectory)/apiScan/SqlClient/dlls - ob_sdl_apiscan_symbolsFolder: $(Build.SourcesDirectory)/apiScan/SqlClient/pdbs - ob_sdl_apiscan_softwarename: Microsoft.Data.SqlClient - ob_sdl_apiscan_versionNumber: $(assemblyBuildNumber) - - ${{ if parameters.isPreview }}: - abstractionsPackageVersion: $(abstractionsPackagePreviewVersion) - loggingPackageVersion: $(loggingPackagePreviewVersion) - mdsPackageVersion: $(mdsPackagePreviewVersion) - - steps: - - script: SET - displayName: 'Print Environment Variables' - - # Download the Abstractions and Logging packages from the previous stage into - # packages/ so that they're available via the local NuGet feed when restoring MDS. - # MDS depends on both Extensions.Abstractions and Internal.Logging. - - task: DownloadPipelineArtifact@2 - displayName: Download Abstractions Package - inputs: - artifactName: $(abstractionsArtifactsName) - targetPath: $(Build.SourcesDirectory)/packages - - - task: DownloadPipelineArtifact@2 - displayName: Download Logging Package - inputs: - artifactName: $(loggingArtifactsName) - targetPath: $(Build.SourcesDirectory)/packages - - # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self - - # Build our tooling, which is required by the analysis step below, but - # shouldn't be analyzed itself. - - task: MSBuild@1 - displayName: 'Build Tooling' - inputs: - solution: '**/build.proj' - configuration: Release - msbuildArguments: -t:BuildTools - - # Perform analysis before building, since this step will clobber build output. - - template: /eng/pipelines/onebranch/steps/code-analyze-step.yml@self - - # Build MDS, producing signed DLLs. - - template: /eng/pipelines/onebranch/steps/build-all-configurations-signed-dlls-step.yml@self - parameters: - # These variables are sourced from common-variables.yml. - abstractionsAssemblyFileVersion: $(abstractionsAssemblyFileVersion) - abstractionsPackageVersion: $(abstractionsPackageVersion) - loggingAssemblyFileVersion: $(loggingAssemblyFileVersion) - loggingPackageVersion: $(loggingPackageVersion) - mdsAssemblyFileVersion: $(mdsAssemblyFileVersion) - mdsPackageVersion: $(mdsPackageVersion) - - - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self - parameters: - artifactType: dll - sourceRoot: $(BUILD_OUTPUT) - dllPattern: 'Microsoft.Data.SqlClient.dll' - - - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self - parameters: - artifactType: dll - sourceRoot: $(BUILD_OUTPUT) - dllPattern: 'Microsoft.Data.SqlClient.resources.dll' - - - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self - parameters: - buildConfiguration: Release - displayName: 'Create MDS NuGet Package' - generateSymbolsPackage: true - installNuget: false - nuspecPath: $(nuspecPath) - outputDirectory: $(PACK_OUTPUT) - packageVersion: $(mdsPackageVersion) - properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion);LoggingPackageVersion=$(loggingPackageVersion)' - referenceType: Package - - - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self - parameters: - artifactType: pkg - nupkgPattern: 'Microsoft.Data.SqlClient.$(mdsPackageVersion).*nupkg' - - # Copy signed DLLs and PDBs to APIScan folders. - - task: CopyFiles@2 - displayName: Copy DLLs for APIScan - inputs: - SourceFolder: $(BUILD_OUTPUT)/Package/bin - Contents: '**/Microsoft.Data.SqlClient.dll' - TargetFolder: $(ob_sdl_apiscan_softwareFolder) - # We must preserve the folder structure since our C# projects may produce multiple - # identically named DLLs for different target frameworks (e.g. netstandard2.0, net5.0, - # etc.), and we need to keep those separate for APIScan to work correctly. - flattenFolders: false - - - task: CopyFiles@2 - displayName: Copy PDBs for APIScan - inputs: - SourceFolder: $(BUILD_OUTPUT)/Package/bin - Contents: '**/Microsoft.Data.SqlClient.pdb' - TargetFolder: $(ob_sdl_apiscan_symbolsFolder) - flattenFolders: false - - # Publish symbols to servers - - ${{ if eq(parameters.publishSymbols, true) }}: - - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self - parameters: - packageFullName: Microsoft.Data.SqlClient - packageVersion: $(mdsPackageVersion) + - job: build_package_SqlClient + displayName: "Build Microsoft.Data.SqlClient" + pool: + type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs + + variables: + ob_outputDirectory: $(PACK_OUTPUT) + # APIScan configuration for this Extension package + ob_sdl_apiscan_enabled: true + ob_sdl_apiscan_softwareFolder: $(Build.SourcesDirectory)/apiScan/SqlClient/dlls + ob_sdl_apiscan_symbolsFolder: $(Build.SourcesDirectory)/apiScan/SqlClient/pdbs + ob_sdl_apiscan_softwarename: Microsoft.Data.SqlClient + ob_sdl_apiscan_versionNumber: $(assemblyBuildNumber) + + ${{ if parameters.isPreview }}: + abstractionsPackageVersion: $(abstractionsPackagePreviewVersion) + loggingPackageVersion: $(loggingPackagePreviewVersion) + mdsPackageVersion: $(mdsPackagePreviewVersion) + + steps: + - script: SET + displayName: "Print Environment Variables" + + # Download the Abstractions and Logging packages from the previous stage into + # packages/ so that they're available via the local NuGet feed when restoring MDS. + # MDS depends on both Extensions.Abstractions and Internal.Logging. + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package + inputs: + artifactName: $(abstractionsArtifactsName) + targetPath: $(Build.SourcesDirectory)/packages + + - task: DownloadPipelineArtifact@2 + displayName: Download Logging Package + inputs: + artifactName: $(loggingArtifactsName) + targetPath: $(Build.SourcesDirectory)/packages + + # Install the .NET SDK. + - template: /eng/pipelines/steps/install-dotnet.yml@self + + # Build our tooling, which is required by the analysis step below, but + # shouldn't be analyzed itself. + - task: MSBuild@1 + displayName: "Build Tooling" + inputs: + solution: "**/build.proj" + configuration: Release + msbuildArguments: -t:BuildTools + + # Perform analysis before building, since this step will clobber build output. + - template: /eng/pipelines/onebranch/steps/code-analyze-step.yml@self + + # Build MDS, producing signed DLLs. + - template: /eng/pipelines/onebranch/steps/build-all-configurations-signed-dlls-step.yml@self + parameters: + # These variables are sourced from common-variables.yml. + abstractionsAssemblyFileVersion: $(abstractionsAssemblyFileVersion) + abstractionsPackageVersion: $(abstractionsPackageVersion) + loggingAssemblyFileVersion: $(loggingAssemblyFileVersion) + loggingPackageVersion: $(loggingPackageVersion) + mdsAssemblyFileVersion: $(mdsAssemblyFileVersion) + mdsPackageVersion: $(mdsPackageVersion) + + - ${{ if eq(parameters.isOfficial, true) }}: + - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self + parameters: + artifactType: dll + sourceRoot: $(BUILD_OUTPUT) + dllPattern: "Microsoft.Data.SqlClient.dll" + + - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self + parameters: + artifactType: dll + sourceRoot: $(BUILD_OUTPUT) + dllPattern: "Microsoft.Data.SqlClient.resources.dll" + + - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self + parameters: + buildConfiguration: Release + displayName: "Create MDS NuGet Package" + generateSymbolsPackage: true + installNuget: false + nuspecPath: $(nuspecPath) + outputDirectory: $(PACK_OUTPUT) + packageVersion: $(mdsPackageVersion) + properties: "AbstractionsPackageVersion=$(abstractionsPackageVersion);LoggingPackageVersion=$(loggingPackageVersion)" + referenceType: Package + + - ${{ if eq(parameters.isOfficial, true) }}: + - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self + parameters: + artifactType: pkg + nupkgPattern: "Microsoft.Data.SqlClient.$(mdsPackageVersion).*nupkg" + + # Copy signed DLLs and PDBs to APIScan folders. + - task: CopyFiles@2 + displayName: Copy DLLs for APIScan + inputs: + SourceFolder: $(BUILD_OUTPUT)/Package/bin + Contents: "**/Microsoft.Data.SqlClient.dll" + TargetFolder: $(ob_sdl_apiscan_softwareFolder) + # We must preserve the folder structure since our C# projects may produce multiple + # identically named DLLs for different target frameworks (e.g. netstandard2.0, net5.0, + # etc.), and we need to keep those separate for APIScan to work correctly. + flattenFolders: false + + - task: CopyFiles@2 + displayName: Copy PDBs for APIScan + inputs: + SourceFolder: $(BUILD_OUTPUT)/Package/bin + Contents: "**/Microsoft.Data.SqlClient.pdb" + TargetFolder: $(ob_sdl_apiscan_symbolsFolder) + flattenFolders: false + + # Publish symbols to servers + - ${{ if eq(parameters.publishSymbols, true) }}: + - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self + parameters: + packageFullName: Microsoft.Data.SqlClient + packageVersion: $(mdsPackageVersion) diff --git a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml index 656ea8f181..3b463d0b81 100644 --- a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml @@ -4,7 +4,6 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - # The name of the pipeline artifacts to download prior to building the tests. - name: artifactName type: string @@ -13,269 +12,277 @@ parameters: - name: isPreview type: boolean + # True if this build is an official build. This will be used to gate some checks + # that only apply to official builds, such as signature verification. + - name: isOfficial + type: boolean + jobs: -- job: validate_signed_package - displayName: 'Verify signed package' - - pool: - type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs - isCustom: true - name: ADO-1ES-Pool - vmImage: 'ADO-MMS22-SQL19' - - variables: # More settings at https://aka.ms/obpipelines/yaml/jobs - - template: /eng/pipelines/onebranch/variables/sqlclient-validation-variables.yml@self - - - name: pathToDownloadedNuget # path to the downloaded nuget files - value: $(Pipeline.Workspace)\${{parameters.artifactName }} - - - ${{ if parameters.isPreview }}: - - name: extractedNugetPath - value: $(extractedNugetRootPath).$(mdsPackagePreviewVersion) - - name: mdsPackageVersion - value: $(mdsPackagePreviewVersion) - - steps: - - script: SET - displayName: 'Print Environment Variables' - - - task: NuGetToolInstaller@1 - displayName: 'Use NuGet' - - - powershell: | - # Displays the paths of all the local cache directories - nuget locals all -List - - #Clears all files from all local cache directories - nuget locals all -Clear - displayName: 'Clear local cache' - - - download: current - artifact: ${{parameters.artifactName}} - patterns: '**/*.*nupkg' - displayName: 'Download NuGet Package' - - - powershell: | - # Install nuget package - Install-Package -Name "Microsoft.Data.SqlClient" -Destination "$(TempFolderName)" -Force -Source $(pathToDownloadedNuget) -SkipDependencies - - Write-Host "--------------------------------------------------" - Write-Host '$(TempFolderName)' - ls $(TempFolderName) - Write-Host "--------------------------------------------------" - displayName: 'Extract Nuget in temp folder' - - - powershell: | - Write-Host "--------------------------------------------------" - Write-Host "This will verify the artifact signature" -ForegroundColor Green - Write-Host "--------------------------------------------------" - - nuget verify -All $(pathToDownloadedNuget)\*.nupkg - nuget verify -All $(pathToDownloadedNuget)\*.snupkg - displayName: 'Verify nuget signature' - - - powershell: | - # Recursively find all .dll files in TempFolder (installed nuget folder) - # Microsoft.Data.SqlClient.dll and Microsoft.Data.SqlClient.resources.dll (in localized folders) should have strong name - $dllFiles = Get-ChildItem -Path $(TempFolderName) -Recurse -Filter *.dll - $badDlls = @() - foreach ($file in $dllFiles) - { - # Run sn.exe to verify the strong name on each dll - $result = & "C:\Program Files (x86)\Microsoft SDKs\Windows\*\bin\NETFX 4.8.1 Tools\sn.exe" -vf $file.FullName - Write-OutPut $result - - # if the dll is not valid, it would be delay signed or test-signed which is not meant for production - if($result[$result.Length-1] -notlike "* is valid") - { - $badDlls += $result[$result.Length-1] - } - } - if($badDlls.Count -gt 0) - { - Write-OutPut "Error: Invalid dlls are detected. Check the list below:" - foreach($dll in $badDlls) - { - Write-Output $dll - } - Exit -1 - } - Write-Host "Strong name has been verified for all dlls" - displayName: 'Verify assembly strong names' - - - powershell: | - # Checks the expected folder names such as lib, ref, runtimes - Get-ChildItem -Path $(extractedNugetPath) -Directory | select Name | foreach { - if('$(expectedFolderNames)'.contains($_.Name)){ - Write-Host expected folder name verfied: $_.Name - } - } - displayName: 'Check expected folder names' - - - powershell: | - # Checks the version of DotNetFramework and DotNet - $countErr = 0 - $countPass = 0 - $excludNamesFromRuntimeFolder = 'lib','win','unix' - - Get-ChildItem -Path $(extractedNugetPath) -Directory | foreach { - $parentname=$_.Name - Write-Host $_.FullName -ForegroundColor yellow - - if($_.Name -ne 'runtimes') { - Get-ChildItem -Path $_.FullName -Directory | select Name | foreach { - if('$(expectedDotnetVersions)'.Contains($_.Name)){ - Write-Host "`tExpected version verified in $parentname": $_.Name -ForegroundColor green - $countPass += 1 + - job: validate_nuget_package + displayName: "Validate NuGet package" + + pool: + type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs + isCustom: true + name: ADO-1ES-Pool + vmImage: "ADO-MMS22-SQL19" + + variables: # More settings at https://aka.ms/obpipelines/yaml/jobs + - template: /eng/pipelines/onebranch/variables/sqlclient-validation-variables.yml@self + + - name: pathToDownloadedNuget # path to the downloaded nuget files + value: $(Pipeline.Workspace)\${{parameters.artifactName }} + + - ${{ if parameters.isPreview }}: + - name: extractedNugetPath + value: $(extractedNugetRootPath).$(mdsPackagePreviewVersion) + - name: mdsPackageVersion + value: $(mdsPackagePreviewVersion) + + steps: + - script: SET + displayName: "Print Environment Variables" + + - task: NuGetToolInstaller@1 + displayName: "Use NuGet" + + - powershell: | + # Displays the paths of all the local cache directories + nuget locals all -List + + #Clears all files from all local cache directories + nuget locals all -Clear + displayName: "Clear local cache" + + - download: current + artifact: ${{parameters.artifactName}} + patterns: "**/*.*nupkg" + displayName: "Download NuGet Package" + + - powershell: | + # Install nuget package + Install-Package -Name "Microsoft.Data.SqlClient" -Destination "$(TempFolderName)" -Force -Source $(pathToDownloadedNuget) -SkipDependencies + + Write-Host "--------------------------------------------------" + Write-Host '$(TempFolderName)' + ls $(TempFolderName) + Write-Host "--------------------------------------------------" + displayName: "Extract Nuget in temp folder" + + - ${{ if eq(parameters.isOfficial, true) }}: + - powershell: | + Write-Host "--------------------------------------------------" + Write-Host "This will verify the artifact signature" -ForegroundColor Green + Write-Host "--------------------------------------------------" + + nuget verify -All $(pathToDownloadedNuget)\*.nupkg + nuget verify -All $(pathToDownloadedNuget)\*.snupkg + displayName: "Verify nuget signature" + + - powershell: | + # Recursively find all .dll files in TempFolder (installed nuget folder) + # Microsoft.Data.SqlClient.dll and Microsoft.Data.SqlClient.resources.dll (in localized folders) should have strong name + $dllFiles = Get-ChildItem -Path $(TempFolderName) -Recurse -Filter *.dll + $badDlls = @() + foreach ($file in $dllFiles) + { + # Run sn.exe to verify the strong name on each dll + $result = & "C:\Program Files (x86)\Microsoft SDKs\Windows\*\bin\NETFX 4.8.1 Tools\sn.exe" -vf $file.FullName + Write-OutPut $result + + # if the dll is not valid, it would be delay signed or test-signed which is not meant for production + if($result[$result.Length-1] -notlike "* is valid") + { + $badDlls += $result[$result.Length-1] } - else{ - Write-Host "`tUnexpected version detected in $parentname": $_.Name - $countErr += 1 + } + if($badDlls.Count -gt 0) + { + Write-OutPut "Error: Invalid dlls are detected. Check the list below:" + foreach($dll in $badDlls) + { + Write-Output $dll } + Exit -1 + } + Write-Host "Strong name has been verified for all dlls" + displayName: "Verify assembly strong names" + + - powershell: | + # Checks the expected folder names such as lib, ref, runtimes + Get-ChildItem -Path $(extractedNugetPath) -Directory | select Name | foreach { + if('$(expectedFolderNames)'.contains($_.Name)){ + Write-Host expected folder name verfied: $_.Name + } } - } + displayName: "Check expected folder names" + + - powershell: | + # Checks the version of DotNetFramework and DotNet + $countErr = 0 + $countPass = 0 + $excludNamesFromRuntimeFolder = 'lib','win','unix' + + Get-ChildItem -Path $(extractedNugetPath) -Directory | foreach { + $parentname=$_.Name + Write-Host $_.FullName -ForegroundColor yellow + + if($_.Name -ne 'runtimes') { + Get-ChildItem -Path $_.FullName -Directory | select Name | foreach { + if('$(expectedDotnetVersions)'.Contains($_.Name)){ + Write-Host "`tExpected version verified in $parentname": $_.Name -ForegroundColor green + $countPass += 1 + } + else{ + Write-Host "`tUnexpected version detected in $parentname": $_.Name + $countErr += 1 + } + } + } - elseif ($_.Name -eq 'runtimes'){ - Get-ChildItem -Depth 3 -Path $_.FullName -Exclude $excludNamesFromRuntimeFolder -Directory | foreach{ - if('$(expectedDotnetVersions)'.Contains($_.Name)){ - Write-Host "`tExpected version verfied in $parentname": $_.Name - $countPass += 1 + elseif ($_.Name -eq 'runtimes'){ + Get-ChildItem -Depth 3 -Path $_.FullName -Exclude $excludNamesFromRuntimeFolder -Directory | foreach{ + if('$(expectedDotnetVersions)'.Contains($_.Name)){ + Write-Host "`tExpected version verfied in $parentname": $_.Name + $countPass += 1 + } + else{ + Write-Host "`tUnexpected version detected": $_.Name -ForegroundColor Red + $countErr += 1 + } + } } else{ - Write-Host "`tUnexpected version detected": $_.Name -ForegroundColor Red - $countErr += 1 + Write-Host "`tUnknown folder " $_.Name -ForegroundColor Red + Exit -1 } } - } - else{ - Write-Host "`tUnknown folder " $_.Name -ForegroundColor Red - Exit -1 - } - } - - Write-Host "_______________" - Write-Host "Expected: $countPass" - Write-Host "Unexpected: $countErr" - Write-Host "_______________" - if ($countErr -ne 0) - { - Write-Host "Unexpected versions are detected!" -ForegroundColor Red - Exit -1 - } - displayName: 'Check Expected framework' - - - powershell: | - # list all the child items of created temp folder - - #Verify all DLLs unzipped match "expected" hierarchy - - foreach( $folderName in (Get-ChildItem -Path $(extractedNugetPath) -Directory).Name) - { - # List all Childerns of the Path - Get-ChildItem -Path $(extractedNugetPath)\$folderName -Recurse -File - $subFiles = Get-ChildItem -Path $(extractedNugetPath)\$folderName -Recurse -File - - foreach($file in $subFiles) - { - if($subFiles[0].Name -like "*.dll" ) + + Write-Host "_______________" + Write-Host "Expected: $countPass" + Write-Host "Unexpected: $countErr" + Write-Host "_______________" + if ($countErr -ne 0) { - Write-Host $subFiles[0].Name -ForegroundColor Green - Write-Host $subFiles[1].Name -ForegroundColor Green - if(($folderName -eq 'lib') -or ($folderName -eq 'ref')) + Write-Host "Unexpected versions are detected!" -ForegroundColor Red + Exit -1 + } + displayName: "Check Expected framework" + + - powershell: | + # list all the child items of created temp folder + + #Verify all DLLs unzipped match "expected" hierarchy + + foreach( $folderName in (Get-ChildItem -Path $(extractedNugetPath) -Directory).Name) + { + # List all Childerns of the Path + Get-ChildItem -Path $(extractedNugetPath)\$folderName -Recurse -File + $subFiles = Get-ChildItem -Path $(extractedNugetPath)\$folderName -Recurse -File + + foreach($file in $subFiles) { - if($subFiles[2].Name -like "*.dll") + if($subFiles[0].Name -like "*.dll" ) { - Write-Host $subFiles[2].Name -ForegroundColor Green + Write-Host $subFiles[0].Name -ForegroundColor Green + Write-Host $subFiles[1].Name -ForegroundColor Green + if(($folderName -eq 'lib') -or ($folderName -eq 'ref')) + { + if($subFiles[2].Name -like "*.dll") + { + Write-Host $subFiles[2].Name -ForegroundColor Green + } + else + { + $subFiles[2].Name + Write-Host "Expected file pattern for localization did not match to *.dll" -ForegroundColor Red + Exit -1 + } + } } else { - $subFiles[2].Name - Write-Host "Expected file pattern for localization did not match to *.dll" -ForegroundColor Red + $subFiles[0].Name + $subFiles[1].Name + Write-Host "Expected file pattern did not match to *.dll" -ForegroundColor Red Exit -1 } } } - else - { - $subFiles[0].Name - $subFiles[1].Name - Write-Host "Expected file pattern did not match to *.dll" -ForegroundColor Red - Exit -1 - } - } - } - displayName: 'Verify all DLLs unzipped match "expected" hierarchy' - - powershell: | - # Verify all dlls status are Valid - - $dlls = Get-ChildItem -Path $(extractedNugetPath) -Recurse -Include *.dll - foreach ($status in $dlls | Get-AuthenticodeSignature) - { - if ($status.Status -eq "Valid") + displayName: 'Verify all DLLs unzipped match "expected" hierarchy' + + - ${{ if eq(parameters.isOfficial, true) }}: + - powershell: | + # Verify all dlls status are Valid + + $dlls = Get-ChildItem -Path $(extractedNugetPath) -Recurse -Include *.dll + foreach ($status in $dlls | Get-AuthenticodeSignature) + { + if ($status.Status -eq "Valid") + { + Write-Host $status.Status $status.Path + } + else + { + Write-Host "dll status of '$status.Path' is not valid!" -ForegroundColor Red + $status + Exit -1 + } + } + displayName: "Verify all dlls status are Valid" + + - powershell: | + # This will check each DLL's ProductVersion and FileVersion against + # expected values. + $failed = 0 + + foreach ( $pVersion in Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo ) { - Write-Host $status.Status $status.Path + if ($pVersion.ProductVersion -Like '$(mdsPackageVersion)*') + { + Write-Host -ForegroundColor Green "Correct ProductVersion detected for $($pVersion.FileName): $($pVersion.ProductVersion)" + } + else + { + Write-Host -ForegroundColor Red "Wrong ProductVersion detected for $($pVersion.FileName); expected: $(mdsPackageVersion); found: $($pVersion.ProductVersion)" + $failed = 1 + } + + if ($pVersion.FileVersion -eq '$(mdsAssemblyFileVersion)') + { + Write-Host -ForegroundColor Green "Correct FileVersion detected for $($pVersion.FileName): $($pVersion.FileVersion)" + } + else + { + Write-Host -ForegroundColor Red "Wrong FileVersion detected for $($pVersion.FileName); expected $(mdsAssemblyFileVersion); found: $($pVersion.FileVersion)" + $failed = 1 + } } - else + + if ($failed -ne 0) { - Write-Host "dll status of '$status.Path' is not valid!" -ForegroundColor Red - $status Exit -1 } - } - displayName: 'Verify all dlls status are Valid' - - powershell: | - # This will check each DLL's ProductVersion and FileVersion against - # expected values. - $failed = 0 + Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object VersionInfo | Format-List + displayName: 'Verify "File Version" matches expected values for DLLs' - foreach ( $pVersion in Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo ) - { - if ($pVersion.ProductVersion -Like '$(mdsPackageVersion)*') - { - Write-Host -ForegroundColor Green "Correct ProductVersion detected for $($pVersion.FileName): $($pVersion.ProductVersion)" - } - else - { - Write-Host -ForegroundColor Red "Wrong ProductVersion detected for $($pVersion.FileName); expected: $(mdsPackageVersion); found: $($pVersion.ProductVersion)" - $failed = 1 - } + - powershell: | + # Check assembly versions. + # + # GOTCHA: This expects the Versions.props file having XML elements in a + # certain order. If the order changes, this check will fail! + # + # TODO: This also isn't checking the versions of the actual assemblies in + # the package, so it isn't terribly useful. - if ($pVersion.FileVersion -eq '$(mdsAssemblyFileVersion)') - { - Write-Host -ForegroundColor Green "Correct FileVersion detected for $($pVersion.FileName): $($pVersion.FileVersion)" - } - else + [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" + $AssemblyFileVersion = $versionprops.Project.PropertyGroup[2].AssemblyFileVersion + $AssemblyVersion = $versionprops.Project.PropertyGroup[2].AssemblyVersion + + if($AssemblyFileVersion -eq $AssemblyVersion) { - Write-Host -ForegroundColor Red "Wrong FileVersion detected for $($pVersion.FileName); expected $(mdsAssemblyFileVersion); found: $($pVersion.FileVersion)" - $failed = 1 + Write-Host AssemblyFileVersion: $AssemblyFileVersion should not be equal to: $AssemblyVersion + Exit -1 } - } - - if ($failed -ne 0) - { - Exit -1 - } - - Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object VersionInfo | Format-List - displayName: 'Verify "File Version" matches expected values for DLLs' - - - powershell: | - # Check assembly versions. - # - # GOTCHA: This expects the Versions.props file having XML elements in a - # certain order. If the order changes, this check will fail! - # - # TODO: This also isn't checking the versions of the actual assemblies in - # the package, so it isn't terribly useful. - - [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" - $AssemblyFileVersion = $versionprops.Project.PropertyGroup[2].AssemblyFileVersion - $AssemblyVersion = $versionprops.Project.PropertyGroup[2].AssemblyVersion - - if($AssemblyFileVersion -eq $AssemblyVersion) - { - Write-Host AssemblyFileVersion: $AssemblyFileVersion should not be equal to: $AssemblyVersion - Exit -1 - } - displayName: 'Check "AssemblyFileVersion" is not same as "AssemblyVersion" in version.props' + displayName: 'Check "AssemblyFileVersion" is not same as "AssemblyVersion" in version.props' diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index 2274d8cccf..25dbe98097 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -94,31 +94,31 @@ variables: # Define the effective versions for all of the packages we build and release. - ${{ if parameters.isPreview }}: - - name: effectiveSqlServerVersion - value: $(sqlServerPackagePreviewVersion) - - name: effectiveLoggingVersion - value: $(loggingPackagePreviewVersion) - - name: effectiveAbstractionsVersion - value: $(abstractionsPackagePreviewVersion) - - name: effectiveSqlClientVersion - value: $(mdsPackagePreviewVersion) - - name: effectiveAzureVersion - value: $(azurePackagePreviewVersion) - - name: effectiveAkvProviderVersion - value: $(akvPackagePreviewVersion) + - name: effectiveSqlServerVersion + value: $(sqlServerPackagePreviewVersion) + - name: effectiveLoggingVersion + value: $(loggingPackagePreviewVersion) + - name: effectiveAbstractionsVersion + value: $(abstractionsPackagePreviewVersion) + - name: effectiveSqlClientVersion + value: $(mdsPackagePreviewVersion) + - name: effectiveAzureVersion + value: $(azurePackagePreviewVersion) + - name: effectiveAkvProviderVersion + value: $(akvPackagePreviewVersion) - ${{ else }}: - - name: effectiveSqlServerVersion - value: $(sqlServerPackageVersion) - - name: effectiveLoggingVersion - value: $(loggingPackageVersion) - - name: effectiveAbstractionsVersion - value: $(abstractionsPackageVersion) - - name: effectiveSqlClientVersion - value: $(mdsPackageVersion) - - name: effectiveAzureVersion - value: $(azurePackageVersion) - - name: effectiveAkvProviderVersion - value: $(akvPackageVersion) + - name: effectiveSqlServerVersion + value: $(sqlServerPackageVersion) + - name: effectiveLoggingVersion + value: $(loggingPackageVersion) + - name: effectiveAbstractionsVersion + value: $(abstractionsPackageVersion) + - name: effectiveSqlClientVersion + value: $(mdsPackageVersion) + - name: effectiveAzureVersion + value: $(azurePackageVersion) + - name: effectiveAkvProviderVersion + value: $(akvPackageVersion) resources: repositories: @@ -132,8 +132,8 @@ extends: template: /v2/OneBranch.NonOfficial.CrossPlat.yml@templates parameters: release: - # This indicates the pipeline category to deploy Box products. See: - # https://eng.ms/docs/products/onebranch/release/yamlreleasepipelines/deployboxproducts + # This indicates the pipeline category to deploy Box products. See: + # https://eng.ms/docs/products/onebranch/release/yamlreleasepipelines/deployboxproducts category: NonAzure featureFlags: EnableCDPxPAT: false @@ -183,6 +183,7 @@ extends: parameters: debug: ${{ parameters.debug }} isPreview: ${{ parameters.isPreview }} + isOfficial: false # This is a non-official pipeline. publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} buildSqlClient: ${{ parameters.buildSqlClient }} diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index c9548b25d6..1754918116 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -115,31 +115,31 @@ variables: # Define the effective versions for all of the packages we build and release. - ${{ if parameters.isPreview }}: - - name: effectiveSqlServerVersion - value: $(sqlServerPackagePreviewVersion) - - name: effectiveLoggingVersion - value: $(loggingPackagePreviewVersion) - - name: effectiveAbstractionsVersion - value: $(abstractionsPackagePreviewVersion) - - name: effectiveSqlClientVersion - value: $(mdsPackagePreviewVersion) - - name: effectiveAzureVersion - value: $(azurePackagePreviewVersion) - - name: effectiveAkvProviderVersion - value: $(akvPackagePreviewVersion) + - name: effectiveSqlServerVersion + value: $(sqlServerPackagePreviewVersion) + - name: effectiveLoggingVersion + value: $(loggingPackagePreviewVersion) + - name: effectiveAbstractionsVersion + value: $(abstractionsPackagePreviewVersion) + - name: effectiveSqlClientVersion + value: $(mdsPackagePreviewVersion) + - name: effectiveAzureVersion + value: $(azurePackagePreviewVersion) + - name: effectiveAkvProviderVersion + value: $(akvPackagePreviewVersion) - ${{ else }}: - - name: effectiveSqlServerVersion - value: $(sqlServerPackageVersion) - - name: effectiveLoggingVersion - value: $(loggingPackageVersion) - - name: effectiveAbstractionsVersion - value: $(abstractionsPackageVersion) - - name: effectiveSqlClientVersion - value: $(mdsPackageVersion) - - name: effectiveAzureVersion - value: $(azurePackageVersion) - - name: effectiveAkvProviderVersion - value: $(akvPackageVersion) + - name: effectiveSqlServerVersion + value: $(sqlServerPackageVersion) + - name: effectiveLoggingVersion + value: $(loggingPackageVersion) + - name: effectiveAbstractionsVersion + value: $(abstractionsPackageVersion) + - name: effectiveSqlClientVersion + value: $(mdsPackageVersion) + - name: effectiveAzureVersion + value: $(azurePackageVersion) + - name: effectiveAkvProviderVersion + value: $(akvPackageVersion) resources: repositories: @@ -153,8 +153,8 @@ extends: template: /v2/OneBranch.Official.CrossPlat.yml@templates parameters: release: - # This indicates the pipeline category to deploy Box products. See: - # https://eng.ms/docs/products/onebranch/release/yamlreleasepipelines/deployboxproducts + # This indicates the pipeline category to deploy Box products. See: + # https://eng.ms/docs/products/onebranch/release/yamlreleasepipelines/deployboxproducts category: NonAzure featureFlags: EnableCDPxPAT: false @@ -208,6 +208,7 @@ extends: parameters: debug: ${{ parameters.debug }} isPreview: ${{ parameters.isPreview }} + isOfficial: true # This is an official pipeline. publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} buildSqlClient: ${{ parameters.buildSqlClient }} diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index 0b6962f1af..fad5c9a699 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -35,6 +35,11 @@ parameters: - name: isPreview type: boolean + # True if this is an official build, which runs additional ESRP malware scanning + # and codesigning steps. + - name: isOfficial + type: boolean + # True to publish symbols to public and private servers. - name: publishSymbols type: boolean @@ -60,40 +65,42 @@ stages: jobs: - ${{ if or(eq(parameters.buildAKVProvider, true), eq(parameters.buildSqlClient, true)) }}: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self - parameters: - packageName: Logging - packageFullName: Microsoft.Data.SqlClient.Internal.Logging - packageVersion: $(effectiveLoggingVersion) - versionProperties: >- - -p:LoggingPackageVersion=$(effectiveLoggingVersion) - -p:LoggingAssemblyFileVersion=$(loggingAssemblyFileVersion) - assemblyFileVersion: $(loggingAssemblyFileVersion) - publishSymbols: ${{ parameters.publishSymbols }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) + - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + parameters: + packageName: Logging + packageFullName: Microsoft.Data.SqlClient.Internal.Logging + packageVersion: $(effectiveLoggingVersion) + versionProperties: >- + -p:LoggingPackageVersion=$(effectiveLoggingVersion) + -p:LoggingAssemblyFileVersion=$(loggingAssemblyFileVersion) + assemblyFileVersion: $(loggingAssemblyFileVersion) + publishSymbols: ${{ parameters.publishSymbols }} + isOfficial: ${{ parameters.isOfficial }} + esrpConnectedServiceName: $(ESRPConnectedServiceName) + esrpClientId: $(ESRPClientId) + appRegistrationClientId: $(AppRegistrationClientId) + appRegistrationTenantId: $(AppRegistrationTenantId) + authAkvName: $(AuthAKVName) + authSignCertName: $(AuthSignCertName) - ${{ if eq(parameters.buildSqlServerServer, true) }}: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self - parameters: - packageName: SqlServer - packageFullName: Microsoft.SqlServer.Server - packageVersion: $(effectiveSqlServerVersion) - versionProperties: >- - -p:SqlServerAssemblyFileVersion=$(sqlServerAssemblyFileVersion) - -p:SqlServerPackageVersion=$(effectiveSqlServerVersion) - assemblyFileVersion: $(sqlServerAssemblyFileVersion) - publishSymbols: ${{ parameters.publishSymbols }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) + - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + parameters: + packageName: SqlServer + packageFullName: Microsoft.SqlServer.Server + packageVersion: $(effectiveSqlServerVersion) + versionProperties: >- + -p:SqlServerAssemblyFileVersion=$(sqlServerAssemblyFileVersion) + -p:SqlServerPackageVersion=$(effectiveSqlServerVersion) + assemblyFileVersion: $(sqlServerAssemblyFileVersion) + publishSymbols: ${{ parameters.publishSymbols }} + isOfficial: ${{ parameters.isOfficial }} + esrpConnectedServiceName: $(ESRPConnectedServiceName) + esrpClientId: $(ESRPClientId) + appRegistrationClientId: $(AppRegistrationClientId) + appRegistrationTenantId: $(AppRegistrationTenantId) + authAkvName: $(AuthAKVName) + authSignCertName: $(AuthSignCertName) # ==================================================================== # Stage 2: Abstractions package (depends on Logging from Stage 1) @@ -101,31 +108,32 @@ stages: # dependency on Internal.Logging. # ==================================================================== - ${{ if eq(parameters.buildSqlClient, true) }}: - - stage: build_abstractions - displayName: "Build Abstractions Package" - dependsOn: build_independent - - jobs: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self - parameters: - packageName: Abstractions - packageFullName: Microsoft.Data.SqlClient.Extensions.Abstractions - packageVersion: $(effectiveAbstractionsVersion) - versionProperties: >- - -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) - -p:AbstractionsAssemblyFileVersion=$(abstractionsAssemblyFileVersion) - -p:LoggingPackageVersion=$(effectiveLoggingVersion) - assemblyFileVersion: $(abstractionsAssemblyFileVersion) - publishSymbols: ${{ parameters.publishSymbols }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) - downloadArtifacts: - - artifactName: $(loggingArtifactsName) - displayName: Logging Package + - stage: build_abstractions + displayName: "Build Abstractions Package" + dependsOn: build_independent + + jobs: + - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + parameters: + packageName: Abstractions + packageFullName: Microsoft.Data.SqlClient.Extensions.Abstractions + packageVersion: $(effectiveAbstractionsVersion) + versionProperties: >- + -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) + -p:AbstractionsAssemblyFileVersion=$(abstractionsAssemblyFileVersion) + -p:LoggingPackageVersion=$(effectiveLoggingVersion) + assemblyFileVersion: $(abstractionsAssemblyFileVersion) + publishSymbols: ${{ parameters.publishSymbols }} + isOfficial: ${{ parameters.isOfficial }} + esrpConnectedServiceName: $(ESRPConnectedServiceName) + esrpClientId: $(ESRPClientId) + appRegistrationClientId: $(AppRegistrationClientId) + appRegistrationTenantId: $(AppRegistrationTenantId) + authAkvName: $(AuthAKVName) + authSignCertName: $(AuthSignCertName) + downloadArtifacts: + - artifactName: $(loggingArtifactsName) + displayName: Logging Package # ==================================================================== # Stage 3: Core packages (depend on Abstractions) @@ -133,88 +141,92 @@ stages: # Stage name kept as 'build_dependent' for validate job compatibility. # ==================================================================== - ${{ if eq(parameters.buildSqlClient, true) }}: - - stage: build_dependent - displayName: "Build Core Packages" - dependsOn: build_abstractions - - jobs: - - template: /eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml@self - parameters: - publishSymbols: ${{ parameters.publishSymbols }} - isPreview: ${{ parameters.isPreview }} - # TODO: This job should use the effective versions for Abstractions, Logging, - # SqlServer, and SqlClient. - - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self - parameters: - packageName: Azure - packageFullName: Microsoft.Data.SqlClient.Extensions.Azure - packageVersion: $(effectiveAzureVersion) - versionProperties: >- - -p:AzurePackageVersion=$(effectiveAzureVersion) - -p:AzureAssemblyFileVersion=$(azureAssemblyFileVersion) - -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) - -p:LoggingPackageVersion=$(effectiveLoggingVersion) - assemblyFileVersion: $(azureAssemblyFileVersion) - publishSymbols: ${{ parameters.publishSymbols }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) - downloadArtifacts: - - artifactName: $(abstractionsArtifactsName) - displayName: Abstractions Package - - artifactName: $(loggingArtifactsName) - displayName: Logging Package + - stage: build_dependent + displayName: "Build Core Packages" + dependsOn: build_abstractions + + jobs: + - template: /eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml@self + parameters: + publishSymbols: ${{ parameters.publishSymbols }} + isPreview: ${{ parameters.isPreview }} + isOfficial: ${{ parameters.isOfficial }} + # TODO: This job should use the effective versions for Abstractions, Logging, + # SqlServer, and SqlClient. + + - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + parameters: + packageName: Azure + packageFullName: Microsoft.Data.SqlClient.Extensions.Azure + packageVersion: $(effectiveAzureVersion) + versionProperties: >- + -p:AzurePackageVersion=$(effectiveAzureVersion) + -p:AzureAssemblyFileVersion=$(azureAssemblyFileVersion) + -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) + -p:LoggingPackageVersion=$(effectiveLoggingVersion) + assemblyFileVersion: $(azureAssemblyFileVersion) + publishSymbols: ${{ parameters.publishSymbols }} + isOfficial: ${{ parameters.isOfficial }} + esrpConnectedServiceName: $(ESRPConnectedServiceName) + esrpClientId: $(ESRPClientId) + appRegistrationClientId: $(AppRegistrationClientId) + appRegistrationTenantId: $(AppRegistrationTenantId) + authAkvName: $(AuthAKVName) + authSignCertName: $(AuthSignCertName) + downloadArtifacts: + - artifactName: $(abstractionsArtifactsName) + displayName: Abstractions Package + - artifactName: $(loggingArtifactsName) + displayName: Logging Package # ==================================================================== # Stage 4: Add-on packages (depend on core packages) # AKV Provider builds after MDS completes. # ==================================================================== - ${{ if and(eq(parameters.buildAKVProvider, true), eq(parameters.buildSqlClient, true)) }}: - - stage: build_addons - displayName: "Build Add-on Packages" - dependsOn: build_dependent - - jobs: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self - parameters: - packageName: AkvProvider - packageFullName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider - packageVersion: $(effectiveAkvProviderVersion) - versionProperties: >- - -p:AkvPackageVersion=$(effectiveAkvProviderVersion) - -p:AkvAssemblyFileVersion=$(akvAssemblyFileVersion) - -p:MdsPackageVersion=$(effectiveSqlClientVersion) - -p:LoggingPackageVersion=$(effectiveLoggingVersion) - -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) - assemblyFileVersion: $(akvAssemblyFileVersion) - publishSymbols: ${{ parameters.publishSymbols }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) - downloadArtifacts: - - artifactName: $(sqlClientArtifactsName) - displayName: SqlClient Package - - artifactName: $(abstractionsArtifactsName) - displayName: Abstractions Package - - artifactName: $(loggingArtifactsName) - displayName: Logging Package + - stage: build_addons + displayName: "Build Add-on Packages" + dependsOn: build_dependent + + jobs: + - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + parameters: + packageName: AkvProvider + packageFullName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + packageVersion: $(effectiveAkvProviderVersion) + versionProperties: >- + -p:AkvPackageVersion=$(effectiveAkvProviderVersion) + -p:AkvAssemblyFileVersion=$(akvAssemblyFileVersion) + -p:MdsPackageVersion=$(effectiveSqlClientVersion) + -p:LoggingPackageVersion=$(effectiveLoggingVersion) + -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) + assemblyFileVersion: $(akvAssemblyFileVersion) + publishSymbols: ${{ parameters.publishSymbols }} + isOfficial: ${{ parameters.isOfficial }} + esrpConnectedServiceName: $(ESRPConnectedServiceName) + esrpClientId: $(ESRPClientId) + appRegistrationClientId: $(AppRegistrationClientId) + appRegistrationTenantId: $(AppRegistrationTenantId) + authAkvName: $(AuthAKVName) + authSignCertName: $(AuthSignCertName) + downloadArtifacts: + - artifactName: $(sqlClientArtifactsName) + displayName: SqlClient Package + - artifactName: $(abstractionsArtifactsName) + displayName: Abstractions Package + - artifactName: $(loggingArtifactsName) + displayName: Logging Package # ==================================================================== # Validation # ==================================================================== - ${{ if eq(parameters.buildSqlClient, true) }}: - - stage: mds_package_validation - displayName: "MDS Package Validation" - dependsOn: build_dependent - jobs: - - template: /eng/pipelines/onebranch/jobs/validate-signed-package-job.yml@self - parameters: - artifactName: $(sqlClientArtifactsName) - isPreview: ${{ parameters.isPreview }} + - stage: mds_package_validation + displayName: "MDS Package Validation" + dependsOn: build_dependent + jobs: + - template: /eng/pipelines/onebranch/jobs/validate-signed-package-job.yml@self + parameters: + artifactName: $(sqlClientArtifactsName) + isPreview: ${{ parameters.isPreview }} + isOfficial: ${{ parameters.isOfficial }} diff --git a/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml b/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml index e86964cf3d..824b3fab65 100644 --- a/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml +++ b/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml @@ -33,36 +33,36 @@ parameters: # Glob pattern to match NuGet packages for scanning and signing. - name: pattern type: string - default: '*.*nupkg' + default: "*.*nupkg" steps: # See: https://aka.ms/esrp.scantask - task: EsrpMalwareScanning@6 - displayName: 'ESRP Nuget Malware Scanning' + displayName: "ESRP Nuget Malware Scanning" inputs: - AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' - AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + AppRegistrationClientId: "${{ parameters.appRegistrationClientId }}" + AppRegistrationTenantId: "${{ parameters.appRegistrationTenantId }}" CleanupTempStorage: 1 - ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' - EsrpClientId: '${{ parameters.esrpClientId }}' - FolderPath: '$(PACK_OUTPUT)' - Pattern: '${{ parameters.pattern }}' + ConnectedServiceName: "${{ parameters.esrpConnectedServiceName }}" + EsrpClientId: "${{ parameters.esrpClientId }}" + FolderPath: "$(PACK_OUTPUT)" + Pattern: "${{ parameters.pattern }}" UseMSIAuthentication: true VerboseLogin: 1 # See: https://aka.ms/esrp.signtask - task: EsrpCodeSigning@6 - displayName: 'ESRP Signing NuGet Package' + 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: '$(PACK_OUTPUT)' - Pattern: '${{ parameters.pattern }}' - signConfigType: 'inlineSignParams' + AppRegistrationClientId: "${{ parameters.appRegistrationClientId }}" + AppRegistrationTenantId: "${{ parameters.appRegistrationTenantId }}" + ConnectedServiceName: "${{ parameters.esrpConnectedServiceName }}" + EsrpClientId: "${{ parameters.esrpClientId }}" + AuthAKVName: "${{ parameters.authAkvName }}" + AuthSignCertName: "${{ parameters.authSignCertName }}" + FolderPath: "$(PACK_OUTPUT)" + Pattern: "${{ parameters.pattern }}" + signConfigType: "inlineSignParams" UseMSIAuthentication: true inlineOperation: | [ diff --git a/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml b/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml index 83053a8d29..09f7715145 100644 --- a/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml +++ b/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml @@ -3,11 +3,12 @@ # 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: artifactType values: - - dll - - pkg + - dll + - pkg - name: sourceRoot type: string @@ -15,11 +16,11 @@ parameters: - name: dllPattern type: string - default: 'Microsoft.Data.SqlClient*.dll' + default: "Microsoft.Data.SqlClient*.dll" - name: nupkgPattern type: string - default: '*.*nupkg' + default: "*.*nupkg" - name: artifactDirectory type: string @@ -50,117 +51,118 @@ parameters: default: $(EsrpClientId) steps: -- ${{ if eq(parameters.artifactType, 'dll') }}: - # See: https://aka.ms/esrp.scantask - - task: EsrpMalwareScanning@6 - displayName: 'ESRP MalwareScanning' - inputs: - ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' - AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' - AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' - EsrpClientId: '${{parameters.EsrpClientId }}' - UseMSIAuthentication: true - FolderPath: '${{parameters.sourceRoot }}' - Pattern: '${{ parameters.dllPattern }}' - CleanupTempStorage: 1 - VerboseLogin: 1 + # ESRP scan and sign the DLLs or NuGet packages, depending on the artifact type. + - ${{ if eq(parameters.artifactType, 'dll') }}: + # See: https://aka.ms/esrp.scantask + - task: EsrpMalwareScanning@6 + displayName: "ESRP MalwareScanning" + inputs: + ConnectedServiceName: "${{parameters.ESRPConnectedServiceName }}" + AppRegistrationClientId: "${{parameters.appRegistrationClientId }}" + AppRegistrationTenantId: "${{parameters.appRegistrationTenantId }}" + EsrpClientId: "${{parameters.EsrpClientId }}" + UseMSIAuthentication: true + FolderPath: "${{parameters.sourceRoot }}" + Pattern: "${{ parameters.dllPattern }}" + CleanupTempStorage: 1 + VerboseLogin: 1 - # See: https://aka.ms/esrp.signtask - - task: EsrpCodeSigning@6 - displayName: 'ESRP CodeSigning' - inputs: - ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' - AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' - AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' - EsrpClientId: '${{parameters.EsrpClientId }}' - UseMSIAuthentication: true - AuthAKVName: '${{parameters.AuthAKVName }}' - AuthSignCertName: '${{parameters.AuthSignCertName }}' - FolderPath: '${{parameters.sourceRoot }}' - Pattern: '${{ parameters.dllPattern }}' - signConfigType: inlineSignParams - 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" - } - ] + # See: https://aka.ms/esrp.signtask + - task: EsrpCodeSigning@6 + displayName: "ESRP CodeSigning" + inputs: + ConnectedServiceName: "${{parameters.ESRPConnectedServiceName }}" + AppRegistrationClientId: "${{parameters.appRegistrationClientId }}" + AppRegistrationTenantId: "${{parameters.appRegistrationTenantId }}" + EsrpClientId: "${{parameters.EsrpClientId }}" + UseMSIAuthentication: true + AuthAKVName: "${{parameters.AuthAKVName }}" + AuthSignCertName: "${{parameters.AuthSignCertName }}" + FolderPath: "${{parameters.sourceRoot }}" + Pattern: "${{ parameters.dllPattern }}" + signConfigType: inlineSignParams + 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" + } + ] -- ${{ if eq(parameters.artifactType, 'pkg') }}: - # See: https://aka.ms/esrp.scantask - - task: EsrpMalwareScanning@6 - displayName: 'ESRP MalwareScanning Nuget Package' - inputs: - ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' - AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' - AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' - EsrpClientId: '${{parameters.EsrpClientId }}' - UseMSIAuthentication: true - FolderPath: '${{parameters.artifactDirectory }}' - Pattern: '${{ parameters.nupkgPattern }}' - CleanupTempStorage: 1 - VerboseLogin: 1 + - ${{ if eq(parameters.artifactType, 'pkg') }}: + # See: https://aka.ms/esrp.scantask + - task: EsrpMalwareScanning@6 + displayName: "ESRP MalwareScanning Nuget Package" + inputs: + ConnectedServiceName: "${{parameters.ESRPConnectedServiceName }}" + AppRegistrationClientId: "${{parameters.appRegistrationClientId }}" + AppRegistrationTenantId: "${{parameters.appRegistrationTenantId }}" + EsrpClientId: "${{parameters.EsrpClientId }}" + UseMSIAuthentication: true + FolderPath: "${{parameters.artifactDirectory }}" + Pattern: "${{ parameters.nupkgPattern }}" + CleanupTempStorage: 1 + VerboseLogin: 1 - # See: https://aka.ms/esrp.signtask - - task: EsrpCodeSigning@6 - displayName: 'ESRP CodeSigning Nuget Package' - inputs: - ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' - AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' - AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' - EsrpClientId: '${{parameters.EsrpClientId }}' - UseMSIAuthentication: true - AuthAKVName: '${{parameters.AuthAKVName }}' - AuthSignCertName: '${{parameters.AuthSignCertName }}' - FolderPath: '${{parameters.artifactDirectory }}' - Pattern: '${{ parameters.nupkgPattern }}' - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-401405", - "operationSetCode": "NuGetSign", - "parameters": [ ], - "toolName": "sign", - "toolVersion": "1.0" - }, - { - "keyCode": "CP-401405", - "operationSetCode": "NuGetVerify", - "parameters": [ ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] + # See: https://aka.ms/esrp.signtask + - task: EsrpCodeSigning@6 + displayName: "ESRP CodeSigning Nuget Package" + inputs: + ConnectedServiceName: "${{parameters.ESRPConnectedServiceName }}" + AppRegistrationClientId: "${{parameters.appRegistrationClientId }}" + AppRegistrationTenantId: "${{parameters.appRegistrationTenantId }}" + EsrpClientId: "${{parameters.EsrpClientId }}" + UseMSIAuthentication: true + AuthAKVName: "${{parameters.AuthAKVName }}" + AuthSignCertName: "${{parameters.AuthSignCertName }}" + FolderPath: "${{parameters.artifactDirectory }}" + Pattern: "${{ parameters.nupkgPattern }}" + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetSign", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetVerify", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] From 59afd23a47b2a1da5598220df54b786a2bf4d019 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:51:58 -0700 Subject: [PATCH 008/298] Test | Fix Transient Fault handling and other flaky unit tests (#4080) * Test | Fix Transient Fault handling flaky tests * Attempt to fix * Fix the MultiPartIdentifier tests getting skipped * Fix serialization issue of SqlTypeWorkaroundsTests * Fix one more cases of possible error scenarios --- .../Data/Common/MultipartIdentifierTests.cs | 8 ++++ .../Data/SqlTypes/SqlTypeWorkaroundsTests.cs | 6 +-- .../ConnectionFailoverTests.cs | 29 ++++++++---- .../ConnectionRoutingTests.cs | 5 +- .../ConnectionRoutingTestsAzure.cs | 5 +- .../SimulatedServerTests/ConnectionTests.cs | 47 ++++++++++--------- .../tools/TDS/TDS.Servers/GenericTdsServer.cs | 28 +++++++++-- .../TDS.Servers/TransientTdsErrorTdsServer.cs | 2 + 8 files changed, 85 insertions(+), 45 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/MultipartIdentifierTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/MultipartIdentifierTests.cs index 72f07573a1..a826f70447 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/MultipartIdentifierTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/MultipartIdentifierTests.cs @@ -26,6 +26,7 @@ public static TheoryData ValidSinglePartIdentifierVariations { ReadOnlySpan part1Words = ["word1", "word 1"]; TheoryData data = []; + HashSet seen = []; // Combination 1: embedded and non-embedded whitespace. // Combination 2: leading and/or trailing whitespace, and no whitespace @@ -36,6 +37,13 @@ public static TheoryData ValidSinglePartIdentifierVariations { foreach ((string p1Combination, string p1Expected) in GeneratePartCombinations(part1)) { + // Skip duplicates — different generation paths can produce + // identical (input, expected) pairs, which xUnit rejects. + if (!seen.Add(p1Combination)) + { + continue; + } + string onePartCombination = p1Combination; string[] onePartExpected = [p1Expected]; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs index cf97f21a39..0697ea6376 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs @@ -63,7 +63,7 @@ public void ByteArrayToSqlBinary_NullInput() }; [Theory] - [MemberData(nameof(SqlDecimalWriteTdsValue_NonNullInput_Data))] + [MemberData(nameof(SqlDecimalWriteTdsValue_NonNullInput_Data), DisableDiscoveryEnumeration = true)] public void SqlDecimalWriteTdsValue_NonNullInput(SqlDecimal input) { // Arrange @@ -155,7 +155,7 @@ public void ByteArrayToSqlGuid_ValidInput(byte[] input) }; [Theory] - [MemberData(nameof(LongToSqlMoney_Data))] + [MemberData(nameof(LongToSqlMoney_Data), DisableDiscoveryEnumeration = true)] public void LongToSqlMoney(long input, SqlMoney expected) { // Act @@ -176,7 +176,7 @@ public void LongToSqlMoney(long input, SqlMoney expected) }; [Theory] - [MemberData(nameof(SqlMoneyToLong_NonNullInput_Data))] + [MemberData(nameof(SqlMoneyToLong_NonNullInput_Data), DisableDiscoveryEnumeration = true)] public void SqlMoneyToLong_NonNullInput(SqlMoney input, long expected) { // Act diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index d23fc9c532..7c8e67a09a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -11,7 +11,6 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { - [Trait("Category", "flaky")] [Collection("SimulatedServerTests")] public class ConnectionFailoverTests { @@ -70,7 +69,7 @@ public void TransientFault_NoFailover_DoesNotClearPool(uint errorCode) Assert.Equal($"localhost,{initialServer.EndPoint.Port}", secondConnection.DataSource); // 1 for the initial connection, 2 for the second connection - Assert.Equal(3, initialServer.PreLoginCount); + Assert.Equal(3, initialServer.PreLoginCount - initialServer.AbandonedPreLoginCount); // A failover should not be triggered, so prelogin count to the failover server should be 0 Assert.Equal(0, failoverServer.PreLoginCount); } @@ -219,6 +218,7 @@ public void NetworkDelay_ShouldConnectToPrimary() InitialCatalog = "master", // Required for failover partner to work ConnectTimeout = 5, Encrypt = false, + Pooling = false, // Disable pooling to ensure a fresh connection attempt is made MultiSubnetFailover = false, #if NETFRAMEWORK TransparentNetworkIPResolution = false, @@ -268,6 +268,7 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa ConnectRetryCount = 0, // Disable retry FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", // User provided failover partner Encrypt = false, + Pooling = false, // Disable pooling to ensure a fresh connection attempt is made on failover }; using SqlConnection connection = new(builder.ConnectionString); @@ -313,6 +314,9 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai ConnectRetryInterval = 1, FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", // User provided failover partner Encrypt = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif }; using SqlConnection connection = new(builder.ConnectionString); // Act @@ -324,7 +328,8 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", connection.DataSource); Assert.Equal(1, server.PreLoginCount); - Assert.Equal(1, failoverServer.PreLoginCount); + Assert.Equal(0, server.Login7Count); + Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); } [Theory] @@ -357,7 +362,8 @@ public void TransientFault_ShouldConnectToPrimary(uint errorCode) InitialCatalog = "master", ConnectTimeout = 30, ConnectRetryInterval = 1, - Encrypt = false + Encrypt = false, + Pooling = false, // Disable pooling to ensure a fresh connection attempt is made }; using SqlConnection connection = new(builder.ConnectionString); @@ -369,7 +375,7 @@ public void TransientFault_ShouldConnectToPrimary(uint errorCode) Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } [Theory] @@ -454,7 +460,7 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e FailoverPartner = $"localhost:{failoverServer.EndPoint.Port}", // User provided failover partner }; using SqlConnection connection = new(builder.ConnectionString); - + // Act connection.Open(); @@ -463,7 +469,7 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } [Theory] @@ -559,11 +565,14 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // Close the connection to return it to the pool connection.Close(); - // Act // Dispose of the server to trigger a failover server.Dispose(); + // Clear the pool to ensure the next connection attempt doesn't reuse + // the pooled connection to the now-disposed primary server. + SqlConnection.ClearAllPools(); + // Opening a new connection will use the failover partner stored in the pool group. // This will fail if the server provided failover partner was stored to the pool group. using SqlConnection failoverConnection = new(builder.ConnectionString); @@ -573,9 +582,9 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs Assert.Equal(ConnectionState.Open, failoverConnection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", failoverConnection.DataSource); // 1 for the initial connection - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); // 1 for the failover connection - Assert.Equal(1, failoverServer.PreLoginCount); + Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs index 108118dda7..b763f2b55c 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs @@ -10,7 +10,6 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { - [Trait("Category", "flaky")] [Collection("SimulatedServerTests")] public class ConnectionRoutingTests { @@ -58,8 +57,8 @@ public void TransientFaultAtRoutedLocation_ShouldReturnToGateway(uint errorCode) Assert.Equal($"localhost,{router.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - Assert.Equal(2, router.PreLoginCount); - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, router.PreLoginCount - router.AbandonedPreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } [Theory] diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs index dd945e37f3..84c3452826 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs @@ -10,7 +10,6 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { - [Trait("Category", "flaky")] [Collection("SimulatedServerTests")] public class ConnectionRoutingTestsAzure : IDisposable { @@ -76,8 +75,8 @@ public void TransientFaultAtRoutedLocation_ShouldReturnToGateway(uint errorCode) Assert.Equal($"localhost,{router.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - Assert.Equal(2, router.PreLoginCount); - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, router.PreLoginCount - router.AbandonedPreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } [Theory] diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index a729e6afe2..21a6cb65de 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -29,7 +29,8 @@ public void ConnectionTest() { using TdsServer server = new(new TdsServerArguments() { }); server.Start(); - var connStr = new SqlConnectionStringBuilder() { + var connStr = new SqlConnectionStringBuilder() + { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, }.ConnectionString; @@ -43,7 +44,8 @@ public void IntegratedAuthConnectionTest() { using TdsServer server = new(new TdsServerArguments() { }); server.Start(); - var connStr = new SqlConnectionStringBuilder() { + var connStr = new SqlConnectionStringBuilder() + { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, }.ConnectionString; @@ -61,9 +63,10 @@ public void IntegratedAuthConnectionTest() [Fact] public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() { - using TdsServer server = new(new TdsServerArguments() {Encryption = TDSPreLoginTokenEncryptionType.None }); + using TdsServer server = new(new TdsServerArguments() { Encryption = TDSPreLoginTokenEncryptionType.None }); server.Start(); - var connStr = new SqlConnectionStringBuilder() { + var connStr = new SqlConnectionStringBuilder() + { DataSource = $"localhost,{server.EndPoint.Port}" }.ConnectionString; @@ -72,7 +75,6 @@ public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() Assert.Contains("The instance of SQL Server you attempted to connect to does not support encryption.", ex.Message, StringComparison.OrdinalIgnoreCase); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -80,26 +82,28 @@ public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() public async Task TransientFault_RetryEnabled_ShouldSucceed_Async(uint errorCode) { using TransientTdsErrorTdsServer server = new( - new TransientTdsErrorTdsServerArguments() + new TransientTdsErrorTdsServerArguments() { - IsEnabledTransientError = true, - Number = errorCode, + IsEnabledTransientError = true, + Number = errorCode, }); server.Start(); SqlConnectionStringBuilder builder = new() { DataSource = "localhost," + server.EndPoint.Port, - Encrypt = SqlConnectionEncryptOption.Optional + Encrypt = SqlConnectionEncryptOption.Optional, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false +#endif }; using SqlConnection connection = new(builder.ConnectionString); await connection.OpenAsync(); Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -123,10 +127,9 @@ public void TransientFault_RetryEnabled_ShouldSucceed(uint errorCode) connection.Open(); Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -151,10 +154,9 @@ public async Task TransientFault_RetryDisabled_ShouldFail_Async(uint errorCode) SqlException e = await Assert.ThrowsAsync(async () => await connection.OpenAsync()); Assert.Equal((int)errorCode, e.Number); Assert.Equal(ConnectionState.Closed, connection.State); - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -179,10 +181,9 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) SqlException e = Assert.Throws(() => connection.Open()); Assert.Equal((int)errorCode, e.Number); Assert.Equal(ConnectionState.Closed, connection.State); - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] [Theory] [InlineData(false)] [InlineData(true)] @@ -200,6 +201,7 @@ public async Task NetworkError_RetryEnabled_ShouldSucceed_Async(bool multiSubnet DataSource = "localhost," + server.EndPoint.Port, Encrypt = SqlConnectionEncryptOption.Optional, ConnectTimeout = 5, + Pooling = false, // Disable pooling to ensure a fresh connection attempt is made MultiSubnetFailover = multiSubnetFailoverEnabled, #if NETFRAMEWORK TransparentNetworkIPResolution = multiSubnetFailoverEnabled @@ -216,11 +218,10 @@ public async Task NetworkError_RetryEnabled_ShouldSucceed_Async(bool multiSubnet } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } } - [Trait("Category", "flaky")] [Theory] [InlineData(true)] [InlineData(false)] @@ -261,11 +262,10 @@ public async Task NetworkDelay_RetryDisabled_Async(bool multiSubnetFailoverEnabl } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } } - [Trait("Category", "flaky")] [Theory] [InlineData(true)] [InlineData(false)] @@ -306,7 +306,7 @@ public void NetworkDelay_RetryDisabled(bool multiSubnetFailoverEnabled) } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } } @@ -467,7 +467,8 @@ public void ConnectionTimeoutTest(int timeout) //TODO: do we even need a server for this test? using TdsServer server = new(); server.Start(); - var connStr = new SqlConnectionStringBuilder() { + var connStr = new SqlConnectionStringBuilder() + { DataSource = $"localhost,{server.EndPoint.Port}", ConnectTimeout = timeout, Encrypt = SqlConnectionEncryptOption.Optional 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 d2c81e6ca1..1eb8bdf8c6 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 @@ -95,6 +95,11 @@ public delegate void OnAuthenticationCompletedDelegate( /// private int _preLoginCount = 0; + /// + /// Counts Login7 requests to the server. + /// + protected int _login7Count = 0; + private TDSServerEndPoint _endpoint; /// @@ -137,6 +142,18 @@ public GenericTdsServer(T arguments, QueryEngine queryEngine) /// public int PreLoginCount => _preLoginCount; + /// + /// Counts Login7 requests to the server. + /// + public int Login7Count => _login7Count; + + /// + /// Counts pre-login requests that did not result in a Login7 request, + /// which indicates the client abandoned the connection (e.g. interval + /// timer timeout during TNIR or failover). + /// + public int AbandonedPreLoginCount => _preLoginCount - _login7Count; + public OnAuthenticationCompletedDelegate OnAuthenticationResponseCompleted { private get; set; } public OnLogin7ValidatedDelegate OnLogin7Validated { private get; set; } @@ -148,9 +165,12 @@ public void Start([CallerMemberName] string methodName = "") { throw new InvalidOperationException("Server is already started"); } - _endpoint = new TDSServerEndPoint(this) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) }; - _endpoint.EndpointName = methodName; - _endpoint.EventLog = Arguments.Log; + _endpoint = new TDSServerEndPoint(this) + { + ServerEndPoint = new IPEndPoint(IPAddress.Any, 0), + EndpointName = methodName, + EventLog = Arguments.Log + }; _endpoint.Start(); } @@ -245,6 +265,8 @@ public virtual TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, /// public virtual TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) { + Interlocked.Increment(ref _login7Count); + // Inflate login7 request from the message TDSLogin7Token loginRequest = request[0] as TDSLogin7Token; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs index ecd89f5812..6ec7ac2528 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs @@ -60,6 +60,8 @@ public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, // Check if we're still going to raise transient error if (Arguments.IsEnabledTransientError && RequestCounter < Arguments.RepeatCount) { + // Increment Login7 count since we won't call base.OnLogin7Request + Interlocked.Increment(ref _login7Count); return GenerateErrorMessage(request); } From ab95f6f35d1aabfdcad4ea50a57b926932e77956 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 26 Mar 2026 07:08:57 +1100 Subject: [PATCH 009/298] remove duplicate dictionary lookups (#4000) --- ...taSourceEnumeratorManagedHelper.netcore.cs | 16 ++++----- .../Data/SqlClient/SQLFallbackDNSCache.cs | 9 ++--- .../Microsoft/Data/SqlClient/SqlDependency.cs | 33 +++++-------------- .../Data/SqlClient/SqlDependencyListener.cs | 22 ++++++------- .../Data/SqlClient/SqlDependencyUtils.cs | 6 +--- 5 files changed, 30 insertions(+), 56 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.netcore.cs index 2bc42aa38c..73a164a108 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.netcore.cs @@ -61,14 +61,14 @@ private static DataTable ParseServerEnumString(string serverInstances) if (InstanceDetails.Count > 0) { dataRow = dataTable.NewRow(); - dataRow[0] = InstanceDetails.ContainsKey(SqlDataSourceEnumeratorUtil.ServerNameCol) == true ? - InstanceDetails[SqlDataSourceEnumeratorUtil.ServerNameCol] : string.Empty; - dataRow[1] = InstanceDetails.ContainsKey(SqlDataSourceEnumeratorUtil.InstanceNameCol) == true ? - InstanceDetails[SqlDataSourceEnumeratorUtil.InstanceNameCol] : string.Empty; - dataRow[2] = InstanceDetails.ContainsKey(SqlDataSourceEnumeratorUtil.IsClusteredCol) == true ? - InstanceDetails[SqlDataSourceEnumeratorUtil.IsClusteredCol] : string.Empty; - dataRow[3] = InstanceDetails.ContainsKey(SqlDataSourceEnumeratorUtil.VersionNameCol) == true ? - InstanceDetails[SqlDataSourceEnumeratorUtil.VersionNameCol] : string.Empty; + dataRow[0] = InstanceDetails.TryGetValue(SqlDataSourceEnumeratorUtil.ServerNameCol, out string serverName) ? + serverName : string.Empty; + dataRow[1] = InstanceDetails.TryGetValue(SqlDataSourceEnumeratorUtil.InstanceNameCol, out string instanceName) ? + instanceName : string.Empty; + dataRow[2] = InstanceDetails.TryGetValue(SqlDataSourceEnumeratorUtil.IsClusteredCol, out string isClustered) ? + isClustered : string.Empty; + dataRow[3] = InstanceDetails.TryGetValue(SqlDataSourceEnumeratorUtil.VersionNameCol, out string versionName) ? + versionName : string.Empty; dataTable.Rows.Add(dataRow); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs index 4dd3086b45..07ddd6fd50 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs @@ -28,13 +28,8 @@ internal bool AddDNSInfo(SQLDNSInfo item) { if (item != null) { - if (DNSInfoCache.ContainsKey(item.FQDN)) - { - - DeleteDNSInfo(item.FQDN); - } - - return DNSInfoCache.TryAdd(item.FQDN, item); + DNSInfoCache[item.FQDN] = item; + return true; } return false; 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 83d9753799..10efdd9e05 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -817,29 +817,21 @@ private static bool AddToServerUserHash(string server, IdentityUserNamePair iden { Dictionary> identityDatabaseHash; - if (!s_serverUserHash.ContainsKey(server)) + if (!s_serverUserHash.TryGetValue(server, out identityDatabaseHash)) { SqlClientEventSource.Log.TryNotificationTraceEvent(" Hash did not contain server, adding."); identityDatabaseHash = new Dictionary>(); s_serverUserHash.Add(server, identityDatabaseHash); } - else - { - identityDatabaseHash = s_serverUserHash[server]; - } List databaseServiceList; - if (!identityDatabaseHash.ContainsKey(identityUser)) + if (!identityDatabaseHash.TryGetValue(identityUser, out databaseServiceList)) { SqlClientEventSource.Log.TryNotificationTraceEvent(" Hash contained server but not user, adding user."); databaseServiceList = new List(); identityDatabaseHash.Add(identityUser, databaseServiceList); } - else - { - databaseServiceList = identityDatabaseHash[identityUser]; - } if (!databaseServiceList.Contains(databaseService)) { @@ -870,15 +862,12 @@ private static void RemoveFromServerUserHash(string server, IdentityUserNamePair { Dictionary> identityDatabaseHash; - if (s_serverUserHash.ContainsKey(server)) + if (s_serverUserHash.TryGetValue(server, out identityDatabaseHash)) { - identityDatabaseHash = s_serverUserHash[server]; - List databaseServiceList; - if (identityDatabaseHash.ContainsKey(identityUser)) + if (identityDatabaseHash.TryGetValue(identityUser, out databaseServiceList)) { - databaseServiceList = identityDatabaseHash[identityUser]; int index = databaseServiceList.IndexOf(databaseService); if (index >= 0) @@ -934,7 +923,9 @@ internal static string GetDefaultComposedOptions(string server, string failoverS lock (s_serverUserHash) { - if (!s_serverUserHash.ContainsKey(server)) + Dictionary> identityDatabaseHash; + + if (!s_serverUserHash.TryGetValue(server, out identityDatabaseHash)) { if (0 == s_serverUserHash.Count) { @@ -942,7 +933,7 @@ internal static string GetDefaultComposedOptions(string server, string failoverS SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR - no start calls have been made, about to throw."); throw SQL.SqlDepDefaultOptionsButNoStart(); } - else if (!string.IsNullOrEmpty(failoverServer) && s_serverUserHash.ContainsKey(failoverServer)) + else if (!string.IsNullOrEmpty(failoverServer) && s_serverUserHash.TryGetValue(failoverServer, out identityDatabaseHash)) { SqlClientEventSource.Log.TryNotificationTraceEvent(" using failover server instead\n"); server = failoverServer; @@ -954,11 +945,9 @@ internal static string GetDefaultComposedOptions(string server, string failoverS } } - Dictionary> identityDatabaseHash = s_serverUserHash[server]; - List databaseList = null; - if (!identityDatabaseHash.ContainsKey(identityUser)) + if (!identityDatabaseHash.TryGetValue(identityUser, out databaseList)) { if (identityDatabaseHash.Count > 1) { @@ -977,10 +966,6 @@ internal static string GetDefaultComposedOptions(string server, string failoverS } } } - else - { - databaseList = identityDatabaseHash[identityUser]; - } DatabaseServicePair pair = new(database, null); DatabaseServicePair resultingPair = null; 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 306b36b747..e97d8589a3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs @@ -252,11 +252,10 @@ internal bool AppDomainUnload(string appDomainKey) // For each decrement, subtract from count, and delete if we reach 0. lock (_appDomainKeyHash) { - if (_appDomainKeyHash.ContainsKey(appDomainKey)) + if (_appDomainKeyHash.TryGetValue(appDomainKey, out int value)) { // Do nothing if AppDomain did not call Start! SqlClientEventSource.Log.TryNotificationTraceEvent(" _appDomainKeyHash contained AppDomainKey: '{0}'.", appDomainKey); - int value = _appDomainKeyHash[appDomainKey]; SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.AppDomainUnload|DEP> _appDomainKeyHash for AppDomainKey: '{0}' count: '{1}'.", appDomainKey, value); Debug.Assert(value > 0, "Why is value 0 or less?"); @@ -272,9 +271,9 @@ internal bool AppDomainUnload(string appDomainKey) Debug.Assert(0 == value, "We did not reach 0 at end of loop in AppDomainUnload!"); Debug.Assert(!_appDomainKeyHash.ContainsKey(appDomainKey), "Key not removed after AppDomainUnload!"); - if (_appDomainKeyHash.ContainsKey(appDomainKey)) + if (_appDomainKeyHash.TryGetValue(appDomainKey, out int remainingCount)) { - SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.AppDomainUnload|DEP|ERR> ERROR - after the Stop() loop, _appDomainKeyHash for AppDomainKey: '{0}' entry not removed from hash. Count: {1}'", appDomainKey, _appDomainKeyHash[appDomainKey]); + SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.AppDomainUnload|DEP|ERR> ERROR - after the Stop() loop, _appDomainKeyHash for AppDomainKey: '{0}' entry not removed from hash. Count: {1}'", appDomainKey, remainingCount); } } else @@ -510,10 +509,11 @@ internal void IncrementStartCount(string appDomainKey, out bool appDomainStart) // For each increment, add to count, and create entry if not present. lock (_appDomainKeyHash) { - if (_appDomainKeyHash.ContainsKey(appDomainKey)) + if (_appDomainKeyHash.TryGetValue(appDomainKey, out int count)) { - _appDomainKeyHash[appDomainKey] = _appDomainKeyHash[appDomainKey] + 1; - SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.IncrementStartCount|DEP> _appDomainKeyHash contained AppDomainKey: '{0}', incremented count: '{1}'.", appDomainKey, _appDomainKeyHash[appDomainKey]); + count++; + _appDomainKeyHash[appDomainKey] = count; + SqlClientEventSource.Log.TryNotificationTraceEvent("SqlConnectionContainer.IncrementStartCount|DEP> _appDomainKeyHash contained AppDomainKey: '{0}', incremented count: '{1}'.", appDomainKey, count); } else { @@ -1618,7 +1618,7 @@ private bool Start( { if (!_sqlDependencyPerAppDomainDispatchers.ContainsKey(appDomainKey)) { - _sqlDependencyPerAppDomainDispatchers[appDomainKey] = dispatcher; + _sqlDependencyPerAppDomainDispatchers.Add(appDomainKey, dispatcher); } } @@ -1637,7 +1637,7 @@ private bool Start( SqlConnectionContainer container = null; lock (_connectionContainers) { - if (!_connectionContainers.ContainsKey(hashHelper)) + if (!_connectionContainers.TryGetValue(hashHelper, out container)) { SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, hashtable miss, creating new container.", ObjectID); container = new SqlConnectionContainer(hashHelper, appDomainKey, useDefaults); @@ -1647,7 +1647,6 @@ private bool Start( } else { - container = _connectionContainers[hashHelper]; SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, hashtable hit, container: {1}", ObjectID, container.ObjectID); if (container.InErrorState) { @@ -1713,9 +1712,8 @@ internal bool Stop( lock (_connectionContainers) { - if (_connectionContainers.ContainsKey(hashHelper)) + if (_connectionContainers.TryGetValue(hashHelper, out SqlConnectionContainer container)) { - SqlConnectionContainer container = _connectionContainers[hashHelper]; SqlClientEventSource.Log.TryNotificationTraceEvent(" {0}, hashtable hit, container: {1}", ObjectID, container.ObjectID); server = container.Server; // Return server, database, and queue info for use by calling SqlDependency. database = container.Database; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs index 59b36d23cc..b990270924 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs @@ -365,11 +365,7 @@ internal SqlDependency LookupDependencyEntry(string id) lock (_instanceLock) { - if (_dependencyIdToDependencyHash.ContainsKey(id)) - { - entry = _dependencyIdToDependencyHash[id]; - } - else + if (!_dependencyIdToDependencyHash.TryGetValue(id, out entry)) { SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR - dependency ID mismatch - not throwing."); } From aea5ade9e0d379b202e7fe3117709fcc50f75f5b Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:25:03 -0300 Subject: [PATCH 010/298] Added CI test stages for SQL Server 16 and 17. (#4087) --- eng/pipelines/dotnet-sqlclient-ci-core.yml | 62 +++++++++++++++++++ .../sqlclient-pr-package-ref-pipeline.yml | 2 + .../sqlclient-pr-project-ref-pipeline.yml | 2 + 3 files changed, 66 insertions(+) diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index f9ead686ff..e4d6d70a7f 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -93,6 +93,12 @@ parameters: type: boolean default: true + # If true, run manual tests against legacy SQL Server versions (2016, 2017). + # Enabled for CI pipelines; disabled for PR pipelines to keep validation fast. + - name: runLegacySqlTests + type: boolean + default: true + - name: dotnetVerbosity type: string default: normal @@ -289,6 +295,62 @@ stages: # Configuration of test jobs. Each entry in this object will become a test job, and the # properties of each entry will be supplied as parameters to the test job template. testConfigurations: + # SQL Server 2016 and 2017 on Windows Server 2022 (x64 only). + # x86 testing is intentionally skipped for these legacy SQL versions + # because x86 support is already validated via SQL 2019 and 2022 images. + ${{ if eq(parameters.runLegacySqlTests, true) }}: + # Windows Server 22 with local SQL Server 2016, x64 build platform. + windows_sql_16_x64: + pool: ${{parameters.defaultPoolName }} + images: + Win22_Sql16: ADO-MMS22-SQL16 + TargetFrameworks: ${{parameters.targetFrameworks }} + netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} + buildPlatforms: ${{parameters.buildPlatforms }} + testSets: ${{parameters.testSets }} + useManagedSNI: ${{parameters.useManagedSNI }} + configSqlFor: local + operatingSystem: Windows + configProperties: + TCPConnectionString: $(SQL_TCP_CONN_STRING) + NPConnectionString: $(SQL_NP_CONN_STRING) + AzureKeyVaultUrl: $(AzureKeyVaultUrl) + AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) + SupportsIntegratedSecurity: true + UserManagedIdentityClientId: $(UserManagedIdentityClientId) + FileStreamDirectory: $(FileStreamDirectory) + LocalDbAppName: $(LocalDbAppName) + LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + AliasName: $(SQLAliasName) + SQLRootPath: $(SQL16RootPath) + enableLocalDB: true + + # Windows Server 22 with local SQL Server 2017, x64 build platform. + windows_sql_17_x64: + pool: ${{parameters.defaultPoolName }} + images: + Win22_Sql17: ADO-MMS22-SQL17 + TargetFrameworks: ${{parameters.targetFrameworks }} + netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} + buildPlatforms: ${{parameters.buildPlatforms }} + testSets: ${{parameters.testSets }} + useManagedSNI: ${{parameters.useManagedSNI }} + configSqlFor: local + operatingSystem: Windows + configProperties: + TCPConnectionString: $(SQL_TCP_CONN_STRING) + NPConnectionString: $(SQL_NP_CONN_STRING) + AzureKeyVaultUrl: $(AzureKeyVaultUrl) + AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) + SupportsIntegratedSecurity: true + UserManagedIdentityClientId: $(UserManagedIdentityClientId) + FileStreamDirectory: $(FileStreamDirectory) + LocalDbAppName: $(LocalDbAppName) + LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + AliasName: $(SQLAliasName) + SQLRootPath: $(SQL17RootPath) + enableLocalDB: true + # Windows Server 22 with local SQL Server 2019, x64 build platform. windows_sql_19_x64: pool: ${{parameters.defaultPoolName }} diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index a575d2dd6a..440d7afa27 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -139,6 +139,8 @@ extends: testJobTimeout: ${{ parameters.testJobTimeout }} testSets: ${{ parameters.testSets }} useManagedSNI: ${{ parameters.useManagedSNI }} + # Legacy SQL Server tests (2016/2017) run in CI only, not on PRs. + runLegacySqlTests: false # Don't run the AE tests in Debug mode; they rarely succeed. ${{ if eq(parameters.buildConfiguration, 'Debug') }}: runAlwaysEncryptedTests: false diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index dd8f34e58b..d3ae773181 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -139,6 +139,8 @@ extends: testJobTimeout: ${{ parameters.testJobTimeout }} testSets: ${{ parameters.testSets }} useManagedSNI: ${{ parameters.useManagedSNI }} + # Legacy SQL Server tests (2016/2017) run in CI only, not on PRs. + runLegacySqlTests: false # Don't run the AE tests in Debug mode; they rarely succeed. ${{ if eq(parameters.buildConfiguration, 'Debug') }}: runAlwaysEncryptedTests: false From 39ae09b4abecc328423f157f43fccccd1b1535da Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:48:30 -0700 Subject: [PATCH 011/298] Test | Fix Diagnostic Tests and remove Remote Executor (#4078) --- .../Microsoft/Data/SqlClient/SqlConnection.cs | 4 +- .../tests/Directory.Packages.props | 1 - ...icrosoft.Data.SqlClient.ManualTests.csproj | 6 +- .../TracingTests/DiagnosticTest.cs | 1053 ++++------------- 4 files changed, 268 insertions(+), 796 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index e22988b35d..b96e6f654d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -1937,8 +1937,10 @@ private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationTok s_diagnosticListener.IsEnabled(SqlClientConnectionOpenError.Name)) { result.Task.ContinueWith( - continuationAction: s_openAsyncComplete, + continuationAction: static (task, state) => s_openAsyncComplete(task, state), state: operationId, // connection is passed in TaskCompletionSource async state + cancellationToken: CancellationToken.None, // we want the continuation task to run even if the original operation was cancelled + continuationOptions: TaskContinuationOptions.ExecuteSynchronously, scheduler: TaskScheduler.Default ); } diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props index 78ea36f1fa..72bddcc898 100644 --- a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props @@ -12,7 +12,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 336838b068..25a1d53d0d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -342,7 +342,8 @@ + Condition="'$(ReferenceType)' != 'Package'" + Private="true" /> @@ -360,7 +361,6 @@ - @@ -395,7 +395,6 @@ - @@ -422,4 +421,5 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs index f550e7db70..db49fc7c32 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs @@ -2,31 +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. +using System; using System.Collections; +using System.Collections.Generic; +using System.Data; using System.Diagnostics; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Xml; using Microsoft.SqlServer.TDS; using Microsoft.SqlServer.TDS.Done; using Microsoft.SqlServer.TDS.EndPoint; using Microsoft.SqlServer.TDS.Error; -using Microsoft.SqlServer.TDS.Servers; using Microsoft.SqlServer.TDS.SQLBatch; +using Microsoft.SqlServer.TDS.Servers; using Xunit; -using System.Runtime.CompilerServices; -using System; -using System.Data; -using Microsoft.DotNet.RemoteExecutor; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { - // TODO(ADO-39873): Re-enable these tests after addressing their flakiness. - [Trait("Category", "flaky")] + // Serialized execution: DiagnosticListener is global state, so these tests + // must not run in parallel with each other. + [Collection("DiagnosticTests")] public class DiagnosticTest { - private const string BadConnectionString = "data source = bad; initial catalog = bad; integrated security = true; connection timeout = 1;"; - private const string WriteCommandBefore = "Microsoft.Data.SqlClient.WriteCommandBefore"; private const string WriteCommandAfter = "Microsoft.Data.SqlClient.WriteCommandAfter"; private const string WriteCommandError = "Microsoft.Data.SqlClient.WriteCommandError"; @@ -35,969 +34,441 @@ public class DiagnosticTest private const string WriteConnectionOpenError = "Microsoft.Data.SqlClient.WriteConnectionOpenError"; private const string WriteConnectionCloseBefore = "Microsoft.Data.SqlClient.WriteConnectionCloseBefore"; private const string WriteConnectionCloseAfter = "Microsoft.Data.SqlClient.WriteConnectionCloseAfter"; - private const string WriteConnectionCloseError = "Microsoft.Data.SqlClient.WriteConnectionCloseError"; + private const string BadConnectionString = "data source = bad; initial catalog = bad; integrated security = true; connection timeout = 1;"; + + #region Sync tests [Fact] public void ExecuteScalarTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();"; - - conn.Open(); - cmd.ExecuteScalar(); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); + conn.Open(); + cmd.ExecuteScalar(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] public void ExecuteScalarErrorTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT 1 / 0;"; - - conn.Open(); - Assert.Throws(() => cmd.ExecuteScalar()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT 1 / 0;", conn); + conn.Open(); + Assert.Throws(() => cmd.ExecuteScalar()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] public void ExecuteNonQueryTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();"; - - conn.Open(); - cmd.ExecuteNonQuery(); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); + conn.Open(); + cmd.ExecuteNonQuery(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] public void ExecuteNonQueryErrorTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT 1 / 0;"; - - // Limiting the command timeout to 3 seconds. This should be lower than the Process timeout. - cmd.CommandTimeout = 3; - conn.Open(); - - Assert.Throws(() => cmd.ExecuteNonQuery()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT 1 / 0;", conn); + cmd.CommandTimeout = 3; + conn.Open(); + Assert.Throws(() => cmd.ExecuteNonQuery()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] public void ExecuteReaderTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();"; - - conn.Open(); - SqlDataReader reader = cmd.ExecuteReader(); - while (reader.Read()) - { - // Read until end. - } - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); + conn.Open(); + using SqlDataReader reader = cmd.ExecuteReader(); + while (reader.Read()) { } + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] public void ExecuteReaderErrorTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT 1 / 0;"; - - conn.Open(); - // @TODO: TestTdsServer should not throw on ExecuteReader, it should throw on reader.Read - Assert.Throws(() => cmd.ExecuteReader()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT 1 / 0;", conn); + conn.Open(); + Assert.Throws(() => cmd.ExecuteReader()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] public void ExecuteReaderWithCommandBehaviorTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();"; - - conn.Open(); - SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.Default); - while (reader.Read()) - { - // Read to end - } - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); + conn.Open(); + using SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.Default); + while (reader.Read()) { } + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } - // Synapse: Parse error at line: 1, column: 27: Incorrect syntax near 'for'. - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [Fact] public void ExecuteXmlReaderTest() { - RemoteExecutor.Invoke(() => + // The TDS test server does not return XML-formatted results, so + // ExecuteXmlReader will throw. We verify the error diagnostic path. + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(_ => - { - // @TODO: Test TDS server doesn't support ExecuteXmlReader, so connect to real server as workaround - using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT TOP 10 * FROM sys.objects FOR xml auto, xmldata;"; - - conn.Open(); - XmlReader reader = cmd.ExecuteXmlReader(); - while (reader.Read()) - { - // Read to end - } - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT TOP 10 * FROM sys.objects FOR xml auto, xmldata;", conn); + conn.Open(); + Assert.Throws(() => cmd.ExecuteXmlReader()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [Fact] public void ExecuteXmlReaderErrorTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) - { - cmd.Connection = conn; - cmd.CommandText = "SELECT *, baddata = 1 / 0 FROM sys.objects FOR xml auto, xmldata;"; - - conn.Open(); - // @TODO: TestTdsServer should not throw on ExecuteXmlReader, should throw on reader.Read - Assert.Throws(() => cmd.ExecuteXmlReader()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT *, baddata = 1 / 0 FROM sys.objects FOR xml auto, xmldata;", conn); + conn.Open(); + Assert.Throws(() => cmd.ExecuteXmlReader()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ExecuteScalarAsyncTest() + public void ConnectionOpenTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(connectionString => { - CollectStatisticsDiagnosticsAsync(async connectionString => - { -#if NET - await using (SqlConnection conn = new SqlConnection(connectionString)) - await using (SqlCommand cmd = new SqlCommand()) -#else - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) -#endif - { - cmd.Connection = conn; - cmd.CommandText = "SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();"; - - conn.Open(); - await cmd.ExecuteScalarAsync(); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(connectionString); + conn.Open(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ExecuteScalarAsyncErrorTest() + public void ConnectionOpenErrorTest() { - RemoteExecutor.Invoke(() => + CollectStatisticsDiagnostics(_ => { - CollectStatisticsDiagnosticsAsync(async connectionString => - { -#if NET - await using (SqlConnection conn = new SqlConnection(connectionString)) - await using (SqlCommand cmd = new SqlCommand()) -#else - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) -#endif - { - cmd.Connection = conn; - cmd.CommandText = "SELECT 1 / 0;"; - - conn.Open(); - await Assert.ThrowsAsync(() => cmd.ExecuteScalarAsync()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + using SqlConnection conn = new(BadConnectionString); + Assert.Throws(() => conn.Open()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenError]); } + #endregion + + #region Async tests + [Fact] - public void ExecuteNonQueryAsyncTest() + public async Task ExecuteScalarAsyncTest() { - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async connectionString => { - CollectStatisticsDiagnosticsAsync(async connectionString => - { #if NET - await using (SqlConnection conn = new SqlConnection(connectionString)) - await using (SqlCommand cmd = new SqlCommand()) + await using SqlConnection conn = new(connectionString); + await using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); #else - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); #endif - { - cmd.Connection = conn; - cmd.CommandText = "SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();"; - - conn.Open(); - await cmd.ExecuteNonQueryAsync(); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + conn.Open(); + await cmd.ExecuteScalarAsync(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ExecuteNonQueryAsyncErrorTest() + public async Task ExecuteScalarAsyncErrorTest() { - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async connectionString => { - CollectStatisticsDiagnosticsAsync(async connectionString => - { #if NET - await using (SqlConnection conn = new SqlConnection(connectionString)) - await using (SqlCommand cmd = new SqlCommand()) + await using SqlConnection conn = new(connectionString); + await using SqlCommand cmd = new("SELECT 1 / 0;", conn); #else - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT 1 / 0;", conn); #endif - { - cmd.Connection = conn; - cmd.CommandText = "SELECT 1 / 0;"; - - conn.Open(); - await Assert.ThrowsAsync(() => cmd.ExecuteNonQueryAsync()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + conn.Open(); + await Assert.ThrowsAsync(() => cmd.ExecuteScalarAsync()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ExecuteReaderAsyncTest() + public async Task ExecuteNonQueryAsyncTest() { - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async connectionString => { - CollectStatisticsDiagnosticsAsync(async connectionString => - { #if NET - await using (SqlConnection conn = new SqlConnection(connectionString)) - await using (SqlCommand cmd = new SqlCommand()) + await using SqlConnection conn = new(connectionString); + await using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); #else - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); #endif - { - cmd.Connection = conn; - cmd.CommandText = "SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();"; - - conn.Open(); - SqlDataReader reader = await cmd.ExecuteReaderAsync(); - while (reader.Read()) - { - // Read to end - } - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + conn.Open(); + await cmd.ExecuteNonQueryAsync(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ExecuteReaderAsyncErrorTest() + public async Task ExecuteNonQueryAsyncErrorTest() { - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async connectionString => { - CollectStatisticsDiagnosticsAsync(async connectionString => - { #if NET - await using (SqlConnection conn = new SqlConnection(connectionString)) - await using (SqlCommand cmd = new SqlCommand()) + await using SqlConnection conn = new(connectionString); + await using SqlCommand cmd = new("SELECT 1 / 0;", conn); #else - using (SqlConnection conn = new SqlConnection(connectionString)) - using (SqlCommand cmd = new SqlCommand()) + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT 1 / 0;", conn); #endif - { - cmd.Connection = conn; - cmd.CommandText = "SELECT 1 / 0;"; - - conn.Open(); - // @TODO: TestTdsServer should not throw on ExecuteReader, should throw on reader.Read - await Assert.ThrowsAsync(() => cmd.ExecuteReaderAsync()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + conn.Open(); + await Assert.ThrowsAsync(() => cmd.ExecuteNonQueryAsync()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } - // Synapse: Parse error at line: 1, column: 27: Incorrect syntax near 'for'. - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void ExecuteXmlReaderAsyncTest() + [Fact] + public async Task ExecuteReaderAsyncTest() { - // @TODO: TestTdsServer does not handle xml reader, so connect to a real server as a workaround - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async connectionString => { - CollectStatisticsDiagnosticsAsync(async _ => - { #if NET - await using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) - await using (SqlCommand cmd = new SqlCommand()) + await using SqlConnection conn = new(connectionString); + await using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); #else - using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) - using (SqlCommand cmd = new SqlCommand()) + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT [name], [state] FROM [sys].[databases] WHERE [name] = db_name();", conn); #endif - { - cmd.Connection = conn; - cmd.CommandText = "SELECT TOP 10 * FROM sys.objects FOR xml auto, xmldata;"; - - conn.Open(); - XmlReader reader = await cmd.ExecuteXmlReaderAsync(); - while (reader.Read()) - { - // Read to end - } - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + conn.Open(); + using SqlDataReader reader = await cmd.ExecuteReaderAsync(); + while (reader.Read()) { } + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ExecuteXmlReaderAsyncErrorTest() + public async Task ExecuteReaderAsyncErrorTest() { - // @TODO: TestTdsServer does not handle xml reader, so connect to a real server as a workaround - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async connectionString => { - - CollectStatisticsDiagnosticsAsync(async _ => - { #if NET - await using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) - await using (SqlCommand cmd = new SqlCommand()) + await using SqlConnection conn = new(connectionString); + await using SqlCommand cmd = new("SELECT 1 / 0;", conn); #else - using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) - using (SqlCommand cmd = new SqlCommand()) + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT 1 / 0;", conn); #endif - { - cmd.Connection = conn; - cmd.CommandText = "select *, baddata = 1 / 0 from sys.objects for xml auto, xmldata;"; - - // @TODO: Since this test uses a real database connection, the exception is - // thrown during reader.Read. (ie, TestTdsServer does not obey proper - // exception behavior) - // NB: As a result of the exception being thrown during reader.Read, - // cmd.ExecuteXmlReaderAsync returns successfully. This means that we receive - // a WriteCommandAfter event rather than WriteCommandError. - await conn.OpenAsync(); - XmlReader reader = await cmd.ExecuteXmlReaderAsync(); - await Assert.ThrowsAsync(() => reader.ReadAsync()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + conn.Open(); + await Assert.ThrowsAsync(() => cmd.ExecuteReaderAsync()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ConnectionOpenTest() + public async Task ExecuteXmlReaderAsyncTest() { - RemoteExecutor.Invoke(() => + // The TDS test server does not return XML-formatted results, so + // ExecuteXmlReaderAsync will throw. We verify the error diagnostic path. + await CollectStatisticsDiagnosticsAsync(async connectionString => { - CollectStatisticsDiagnostics(connectionString => - { - using (SqlConnection sqlConnection = new SqlConnection(connectionString)) - { - sqlConnection.Open(); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); +#if NET + await using SqlConnection conn = new(connectionString); + await using SqlCommand cmd = new("SELECT TOP 10 * FROM sys.objects FOR xml auto, xmldata;", conn); +#else + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT TOP 10 * FROM sys.objects FOR xml auto, xmldata;", conn); +#endif + conn.Open(); + await Assert.ThrowsAsync(() => cmd.ExecuteXmlReaderAsync()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ConnectionOpenErrorTest() + public async Task ExecuteXmlReaderAsyncErrorTest() { - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async connectionString => { - CollectStatisticsDiagnostics(_ => - { - using (SqlConnection sqlConnection = new SqlConnection(BadConnectionString)) - { - Assert.Throws(() => sqlConnection.Open()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenError]); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); +#if NET + await using SqlConnection conn = new(connectionString); + await using SqlCommand cmd = new("SELECT *, baddata = 1 / 0 FROM sys.objects FOR xml auto, xmldata;", conn); +#else + using SqlConnection conn = new(connectionString); + using SqlCommand cmd = new("SELECT *, baddata = 1 / 0 FROM sys.objects FOR xml auto, xmldata;", conn); +#endif + conn.Open(); + Assert.Throws(() => cmd.ExecuteXmlReader()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ConnectionOpenAsyncTest() + public async Task ConnectionOpenAsyncTest() { - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async connectionString => { - CollectStatisticsDiagnosticsAsync(async connectionString => - { #if NET - await using (SqlConnection sqlConnection = new SqlConnection(connectionString)) + await using SqlConnection conn = new(connectionString); #else - using (SqlConnection sqlConnection = new SqlConnection(connectionString)) + using SqlConnection conn = new(connectionString); #endif - { - await sqlConnection.OpenAsync(); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + await conn.OpenAsync(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); } [Fact] - public void ConnectionOpenAsyncErrorTest() + public async Task ConnectionOpenAsyncErrorTest() { - RemoteExecutor.Invoke(() => + await CollectStatisticsDiagnosticsAsync(async _ => { - CollectStatisticsDiagnosticsAsync(async _ => - { #if NET - await using (SqlConnection sqlConnection = new SqlConnection(BadConnectionString)) + await using SqlConnection conn = new(BadConnectionString); #else - using (SqlConnection sqlConnection = new SqlConnection(BadConnectionString)) + using SqlConnection conn = new(BadConnectionString); #endif - { - await Assert.ThrowsAsync(() => sqlConnection.OpenAsync()); - } - }, [WriteConnectionOpenBefore, WriteConnectionOpenError]).Wait(); - return RemoteExecutor.SuccessExitCode; - }).Dispose(); + await Assert.ThrowsAsync(() => conn.OpenAsync()); + }, [WriteConnectionOpenBefore, WriteConnectionOpenError]); } - private static void CollectStatisticsDiagnostics(Action sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") - { - bool statsLogged = false; - bool operationHasError = false; - Guid beginOperationId = Guid.Empty; - - FakeDiagnosticListenerObserver diagnosticListenerObserver = new FakeDiagnosticListenerObserver(kvp => - { - IDictionary statistics; - - if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteCommandBefore")) - { - Assert.NotNull(kvp.Value); - - Guid retrievedOperationId = GetPropertyValueFromType(kvp.Value, "OperationId"); - Assert.NotEqual(retrievedOperationId, Guid.Empty); - - SqlCommand sqlCommand = GetPropertyValueFromType(kvp.Value, "Command"); - Assert.NotNull(sqlCommand); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - if (sqlCommand.Connection.State == ConnectionState.Open) - { - Assert.NotEqual(connectionId, Guid.Empty); - } - - beginOperationId = retrievedOperationId; - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteCommandAfter")) - { - Assert.NotNull(kvp.Value); - - Guid retrievedOperationId = GetPropertyValueFromType(kvp.Value, "OperationId"); - Assert.NotEqual(retrievedOperationId, Guid.Empty); - - SqlCommand sqlCommand = GetPropertyValueFromType(kvp.Value, "Command"); - Assert.NotNull(sqlCommand); - - statistics = GetPropertyValueFromType(kvp.Value, "Statistics"); - if (!operationHasError) - { - Assert.NotNull(statistics); - } - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - if (sqlCommand.Connection.State == ConnectionState.Open) - { - Assert.NotEqual(connectionId, Guid.Empty); - } - - // if we get to this point, then statistics exist and this must be the "end" - // event, so we need to make sure the operation IDs match - Assert.Equal(retrievedOperationId, beginOperationId); - beginOperationId = Guid.Empty; - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteCommandError")) - { - operationHasError = true; - Assert.NotNull(kvp.Value); - - SqlCommand sqlCommand = GetPropertyValueFromType(kvp.Value, "Command"); - Assert.NotNull(sqlCommand); - - Exception ex = GetPropertyValueFromType(kvp.Value, "Exception"); - Assert.NotNull(ex); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - if (sqlCommand.Connection.State == ConnectionState.Open) - { - Assert.NotEqual(connectionId, Guid.Empty); - } - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionOpenBefore")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionOpenAfter")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - statistics = GetPropertyValueFromType(kvp.Value, "Statistics"); - Assert.NotNull(statistics); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - Assert.NotEqual(connectionId, Guid.Empty); - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionOpenError")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - Exception ex = GetPropertyValueFromType(kvp.Value, "Exception"); - Assert.NotNull(ex); - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionCloseBefore")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - Assert.NotEqual(connectionId, Guid.Empty); - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionCloseAfter")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); + #endregion - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); + #region Helpers - statistics = GetPropertyValueFromType(kvp.Value, "Statistics"); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - Assert.NotEqual(connectionId, Guid.Empty); - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionCloseError")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - Exception ex = GetPropertyValueFromType(kvp.Value, "Exception"); - Assert.NotNull(ex); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - Assert.NotEqual(connectionId, Guid.Empty); - - statsLogged = true; - } - }); - - diagnosticListenerObserver.Enable(); - using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) + private static void CollectStatisticsDiagnostics( + Action sqlOperation, + string[] expectedDiagnostics, + [CallerMemberName] string methodName = "") + { + bool statsLogged = false; + FakeDiagnosticListenerObserver observer = new(kvp => { + ValidateDiagnosticPayload(kvp); + statsLogged = true; + }); - Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); - - using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments())) + observer.Enable(); + using (DiagnosticListener.AllListeners.Subscribe(observer)) + using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments())) + { + server.Start(methodName); + string connectionString = new SqlConnectionStringBuilder { - server.Start(methodName); - Console.WriteLine(string.Format("Test: {0} Started Server", methodName)); - - var connectionString = new SqlConnectionStringBuilder - { - DataSource = $"localhost,{server.EndPoint.Port}", - Encrypt = SqlConnectionEncryptOption.Optional - }.ConnectionString; - - sqlOperation(connectionString); + DataSource = $"localhost,{server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional + }.ConnectionString; - Console.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); + sqlOperation(connectionString); - Assert.True(statsLogged); + Assert.True(statsLogged, "Expected at least one diagnostic event"); + observer.Disable(); - diagnosticListenerObserver.Disable(); - foreach (string expected in expectedDiagnostics) - { - Assert.True(diagnosticListenerObserver.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); - } - - Console.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); + foreach (string expected in expectedDiagnostics) + { + Assert.True(observer.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); } - Console.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); } - Console.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); } - private static async Task CollectStatisticsDiagnosticsAsync(Func sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") + private static async Task CollectStatisticsDiagnosticsAsync( + Func sqlOperation, + string[] expectedDiagnostics, + [CallerMemberName] string methodName = "") { bool statsLogged = false; - bool operationHasError = false; - Guid beginOperationId = Guid.Empty; - - FakeDiagnosticListenerObserver diagnosticListenerObserver = new FakeDiagnosticListenerObserver(kvp => + FakeDiagnosticListenerObserver observer = new(kvp => { - IDictionary statistics; - - if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteCommandBefore")) - { - Assert.NotNull(kvp.Value); - - Guid retrievedOperationId = GetPropertyValueFromType(kvp.Value, "OperationId"); - Assert.NotEqual(retrievedOperationId, Guid.Empty); - - SqlCommand sqlCommand = GetPropertyValueFromType(kvp.Value, "Command"); - Assert.NotNull(sqlCommand); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - beginOperationId = retrievedOperationId; - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteCommandAfter")) - { - Assert.NotNull(kvp.Value); - - Guid retrievedOperationId = GetPropertyValueFromType(kvp.Value, "OperationId"); - Assert.NotEqual(retrievedOperationId, Guid.Empty); - - SqlCommand sqlCommand = GetPropertyValueFromType(kvp.Value, "Command"); - Assert.NotNull(sqlCommand); - - statistics = GetPropertyValueFromType(kvp.Value, "Statistics"); - if (!operationHasError) - { - Assert.NotNull(statistics); - } - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - // if we get to this point, then statistics exist and this must be the "end" - // event, so we need to make sure the operation IDs match - Assert.Equal(retrievedOperationId, beginOperationId); - beginOperationId = Guid.Empty; - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteCommandError")) - { - operationHasError = true; - Assert.NotNull(kvp.Value); - - SqlCommand sqlCommand = GetPropertyValueFromType(kvp.Value, "Command"); - Assert.NotNull(sqlCommand); - - Exception ex = GetPropertyValueFromType(kvp.Value, "Exception"); - Assert.NotNull(ex); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionOpenBefore")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionOpenAfter")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - statistics = GetPropertyValueFromType(kvp.Value, "Statistics"); - Assert.NotNull(statistics); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - if (sqlConnection.State == ConnectionState.Open) - { - Assert.NotEqual(connectionId, Guid.Empty); - } - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionOpenError")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - Exception ex = GetPropertyValueFromType(kvp.Value, "Exception"); - Assert.NotNull(ex); + ValidateDiagnosticPayload(kvp); + statsLogged = true; + }); - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionCloseBefore")) + observer.Enable(); + using (DiagnosticListener.AllListeners.Subscribe(observer)) + using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments())) + { + server.Start(methodName); + string connectionString = new SqlConnectionStringBuilder { - Assert.NotNull(kvp.Value); + DataSource = $"localhost,{server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional + }.ConnectionString; - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); + await sqlOperation(connectionString); - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); + Assert.True(statsLogged, "Expected at least one diagnostic event"); + observer.Disable(); - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - Assert.NotEqual(connectionId, Guid.Empty); - - statsLogged = true; - } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionCloseAfter")) + foreach (string expected in expectedDiagnostics) { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - statistics = GetPropertyValueFromType(kvp.Value, "Statistics"); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - Assert.NotEqual(connectionId, Guid.Empty); - - statsLogged = true; + Assert.True(observer.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); } - else if (kvp.Key.Equals("Microsoft.Data.SqlClient.WriteConnectionCloseError")) - { - Assert.NotNull(kvp.Value); - - SqlConnection sqlConnection = GetPropertyValueFromType(kvp.Value, "Connection"); - Assert.NotNull(sqlConnection); - - string operation = GetPropertyValueFromType(kvp.Value, "Operation"); - Assert.False(string.IsNullOrWhiteSpace(operation)); - - Exception ex = GetPropertyValueFromType(kvp.Value, "Exception"); - Assert.NotNull(ex); - - Guid connectionId = GetPropertyValueFromType(kvp.Value, "ConnectionId"); - Assert.NotEqual(connectionId, Guid.Empty); + } + } - statsLogged = true; - } - }); + private static void ValidateDiagnosticPayload(KeyValuePair kvp) + { + Assert.NotNull(kvp.Value); - diagnosticListenerObserver.Enable(); - using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) + if (kvp.Key.Contains("Command")) { - Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); - using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments())) - { - server.Start(methodName); - Console.WriteLine(string.Format("Test: {0} Started Server", methodName)); - - var connectionString = new SqlConnectionStringBuilder - { - DataSource = $"localhost,{server.EndPoint.Port}", - Encrypt = SqlConnectionEncryptOption.Optional - }.ConnectionString; - await sqlOperation(connectionString); - - Console.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); - - Assert.True(statsLogged); - - diagnosticListenerObserver.Disable(); - foreach (string expected in expectedDiagnostics) - { - Assert.True(diagnosticListenerObserver.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); - } + SqlCommand cmd = GetPropertyValueFromType(kvp.Value, "Command"); + Assert.NotNull(cmd); + } + else if (kvp.Key.Contains("Connection")) + { + SqlConnection conn = GetPropertyValueFromType(kvp.Value, "Connection"); + Assert.NotNull(conn); + } - Console.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); - } - Console.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); + if (kvp.Key.Contains("Error")) + { + Exception ex = GetPropertyValueFromType(kvp.Value, "Exception"); + Assert.NotNull(ex); } - Console.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); + + string operation = GetPropertyValueFromType(kvp.Value, "Operation"); + Assert.False(string.IsNullOrWhiteSpace(operation)); } private static T GetPropertyValueFromType(object obj, string propName) { Type type = obj.GetType(); PropertyInfo pi = type.GetRuntimeProperty(propName); - - var propertyValue = pi.GetValue(obj); - return (T)propertyValue; + return (T)pi.GetValue(obj); } + + #endregion } + /// + /// Query engine that handles error queries (division by zero). + /// public class DiagnosticsQueryEngine : QueryEngine { - public DiagnosticsQueryEngine() : base(new TdsServerArguments()) - { - } + public DiagnosticsQueryEngine() : base(new TdsServerArguments()) { } protected override TDSMessageCollection CreateQueryResponse(ITDSServerSession session, TDSSQLBatchToken batchRequest) { string lowerBatchText = batchRequest.Text.ToLowerInvariant(); - - if (lowerBatchText.Contains("1 / 0")) // SELECT 1/0 + if (lowerBatchText.Contains("1 / 0")) { - TDSErrorToken errorToken = new TDSErrorToken(8134, 1, 16, "Divide by zero error encountered."); - TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Count, TDSDoneTokenCommandType.Select, 1); - TDSMessage responseMessage = new TDSMessage(TDSMessageType.Response, errorToken, doneToken); + TDSErrorToken errorToken = new(8134, 1, 16, "Divide by zero error encountered."); + TDSDoneToken doneToken = new(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Count, TDSDoneTokenCommandType.Select, 1); + TDSMessage responseMessage = new(TDSMessageType.Response, errorToken, doneToken); return new TDSMessageCollection(responseMessage); } - else - { - return base.CreateQueryResponse(session, batchRequest); - } + return base.CreateQueryResponse(session, batchRequest); } } } From 49db91a7d028e73258f2d9b5eac0297ac936e65e Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:11:12 -0700 Subject: [PATCH 012/298] Add type forwards to public Abstractions types (#4067) --- .../ref/Microsoft.Data.SqlClient.csproj | 3 ++ .../src/Microsoft.Data.SqlClient.csproj | 3 ++ .../netfx/ref/Microsoft.Data.SqlClient.csproj | 3 ++ .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 ++ .../src/TypeForwards.Abstractions.cs | 9 +++++ .../tests/UnitTests/TypeForwardTests.cs | 34 +++++++++++++++++++ 6 files changed, 55 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/src/TypeForwards.Abstractions.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/TypeForwardTests.cs 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 f3d5bdc844..3215e42068 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -31,6 +31,9 @@ + + TypeForwards.Abstractions.cs +
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 6c54000f9c..7145dbac28 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -1023,6 +1023,9 @@ TypeForwards.netcore.cs + + TypeForwards.Abstractions.cs + 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 208181ffb6..93fc5a5375 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -26,6 +26,9 @@ + + TypeForwards.Abstractions.cs + 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 05a4284565..8408dabc0d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1006,6 +1006,9 @@ System\Runtime\CompilerServices\IsExternalInit.netfx.cs + + TypeForwards.Abstractions.cs + diff --git a/src/Microsoft.Data.SqlClient/src/TypeForwards.Abstractions.cs b/src/Microsoft.Data.SqlClient/src/TypeForwards.Abstractions.cs new file mode 100644 index 0000000000..f8272c5d2b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/TypeForwards.Abstractions.cs @@ -0,0 +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. + +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.Data.SqlClient.SqlAuthenticationMethod))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.Data.SqlClient.SqlAuthenticationParameters))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.Data.SqlClient.SqlAuthenticationProvider))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.Data.SqlClient.SqlAuthenticationProviderException))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.Data.SqlClient.SqlAuthenticationToken))] diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/TypeForwardTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/TypeForwardTests.cs new file mode 100644 index 0000000000..8cb9811933 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/TypeForwardTests.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. + +using System; +using System.Reflection; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests +{ + public class TypeForwardTests + { + private static readonly Assembly s_sqlClientAssembly = typeof(SqlConnection).Assembly; + + [Theory] + [InlineData("Microsoft.Data.SqlClient.SqlAuthenticationMethod", true)] + [InlineData("Microsoft.Data.SqlClient.SqlAuthenticationParameters", false)] + [InlineData("Microsoft.Data.SqlClient.SqlAuthenticationProvider", false)] + [InlineData("Microsoft.Data.SqlClient.SqlAuthenticationProviderException", false)] + [InlineData("Microsoft.Data.SqlClient.SqlAuthenticationToken", false)] + public void AbstractionsType_CanBeLoadedFromSqlClientAssembly(string typeName, bool isEnum) + { + // Types moved to the Abstractions assembly must remain loadable via the + // Microsoft.Data.SqlClient assembly for backward compatibility. + Type? type = s_sqlClientAssembly.GetType(typeName, throwOnError: true); + + Assert.NotNull(type); + Assert.Equal(isEnum, type.IsEnum); + + // Assert that the assembly containing the type is the Abstractions assembly. + Assert.Equal("Microsoft.Data.SqlClient.Extensions.Abstractions", type.Assembly.GetName().Name); + } + } +} From ccdd846a99f4ec33e39d0de089712ea570de7ed2 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:24:12 -0300 Subject: [PATCH 013/298] Create stress test pipeline (#3867) * Task 40503: Create stress test pipeline - Added initial placeholder pipeline entry point file. - Wired up the new top-level stress test pipeline to the existing stages/jobs. - Using human friendly triggering pipeline names. - Removed PR pipeline as upstream trigger. - When triggered manually, we must choose one of the upstream pipelines to use for artifact download. - Temporarily added PR-SqlClient-Package to help with PR-based testing. - Sorted template parameters alphabetically. - Added debugging for artifact details. * Changed the pipeline to expect project-references to SqlClient projects. * Moved stress test pipeline files into a stress/ directory. * Fixed expansion errors. * Addressed Copilot feedback. * Addressed my own comments. --- eng/pipelines/dotnet-sqlclient-ci-core.yml | 18 -- ...qlclient-ci-package-reference-pipeline.yml | 7 - ...qlclient-ci-project-reference-pipeline.yml | 7 - .../jobs/pack-abstractions-package-ci-job.yml | 4 +- .../jobs/pack-azure-package-ci-job.yml | 4 +- eng/pipelines/jobs/stress-tests-ci-job.yml | 194 ----------------- .../jobs/test-abstractions-package-ci-job.yml | 6 +- .../jobs/test-azure-package-ci-job.yml | 6 +- .../sqlclient-pr-package-ref-pipeline.yml | 8 +- .../sqlclient-pr-project-ref-pipeline.yml | 8 +- .../stages/stress-tests-ci-stage.yml | 205 ------------------ eng/pipelines/stress-tests-pipeline.yml | 81 ------- eng/pipelines/stress/stress-tests-job.yml | 180 +++++++++++++++ .../stress/stress-tests-pipeline.yml | 101 +++++++++ eng/pipelines/stress/stress-tests-stage.yml | 130 +++++++++++ .../tests/StressTests/Readme.md | 94 +++----- .../tests/StressTests/StressTests.slnx | 7 - 17 files changed, 449 insertions(+), 611 deletions(-) delete mode 100644 eng/pipelines/jobs/stress-tests-ci-job.yml delete mode 100644 eng/pipelines/stages/stress-tests-ci-stage.yml delete mode 100644 eng/pipelines/stress-tests-pipeline.yml create mode 100644 eng/pipelines/stress/stress-tests-job.yml create mode 100644 eng/pipelines/stress/stress-tests-pipeline.yml create mode 100644 eng/pipelines/stress/stress-tests-stage.yml delete mode 100644 src/Microsoft.Data.SqlClient/tests/StressTests/StressTests.slnx diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index e4d6d70a7f..d39c590efc 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -78,11 +78,6 @@ parameters: type: string default: $(ci_var_defaultPoolName) - # True to add a Stress Test stage to the pipeline. - - name: enableStressTests - type: boolean - default: false - # The timeout, in minutes, for each test job. - name: testJobTimeout type: number @@ -218,19 +213,6 @@ stages: loggingArtifactsName: $(loggingArtifactsName) mdsArtifactsName: $(mdsArtifactsName) - # Run the stress tests, if desired. - - ${{ if eq(parameters.enableStressTests, true) }}: - - template: /eng/pipelines/stages/stress-tests-ci-stage.yml@self - parameters: - buildConfiguration: ${{ parameters.buildConfiguration }} - additionalDependsOn: - - build_sqlclient_package_stage - - build_azure_package_stage - mdsArtifactsName: $(mdsArtifactsName) - mdsPackageVersion: $(mdsPackageVersion) - azurePackageVersion: $(azurePackageVersion) - dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - # Run the MDS and AKV tests. - template: /eng/pipelines/common/templates/stages/ci-run-tests-stage.yml@self parameters: diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index 262c2b909a..d38fb14d57 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -103,12 +103,6 @@ parameters: type: boolean default: false - # True to run the stress tests stage. - - name: enableStressTests - displayName: Enable Stress Tests - type: boolean - default: false - # The target frameworks to build and run tests for on Windows. # # These are _not_ the target frameworks to build the driver packages for. @@ -173,7 +167,6 @@ extends: referenceType: Package debug: ${{ parameters.debug }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - enableStressTests: ${{ parameters.enableStressTests }} targetFrameworks: ${{ parameters.targetFrameworks }} targetFrameworksUnix: ${{ parameters.targetFrameworksUnix }} testJobTimeout: ${{ parameters.testJobTimeout }} diff --git a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml index a5a78f0f8b..7fef476e59 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml @@ -103,12 +103,6 @@ parameters: type: boolean default: false - # True to run the stress tests stage. - - name: enableStressTests - displayName: Enable Stress Tests - type: boolean - default: false - # The target frameworks to build and run tests for on Windows. # # These are _not_ the target frameworks to build the driver packages for. @@ -173,7 +167,6 @@ extends: referenceType: Project debug: ${{ parameters.debug }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - enableStressTests: ${{ parameters.enableStressTests }} targetFrameworks: ${{ parameters.targetFrameworks }} targetFrameworksUnix: ${{ parameters.targetFrameworksUnix }} testJobTimeout: ${{ parameters.testJobTimeout }} diff --git a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml index 9db1c05696..aed7672618 100644 --- a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml @@ -78,9 +78,7 @@ jobs: # Explicitly unset the $PLATFORM environment variable that is set by the # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. # This is defined with a non-standard Platform of 'AnyCPU', and will fail - # the builds if left defined. The stress tests solution does not require - # any specific Platform, and so its solution file doesn't support any - # non-standard platforms. + # the builds if left defined. # # Note that Azure Pipelines will inject this variable as PLATFORM into the # environment of all tasks in this job. diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index 95853d43e0..3b03dc0adc 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -107,9 +107,7 @@ jobs: # Explicitly unset the $PLATFORM environment variable that is set by the # 'ADO Build properties' Library in the ADO SqlClientDrivers public # project. This is defined with a non-standard Platform of 'AnyCPU', and - # will fail the builds if left defined. The stress tests solution does - # not require any specific Platform, and so its solution file doesn't - # support any non-standard platforms. + # will fail the builds if left defined. # # Note that Azure Pipelines will inject this variable as PLATFORM into the # environment of all tasks in this job. diff --git a/eng/pipelines/jobs/stress-tests-ci-job.yml b/eng/pipelines/jobs/stress-tests-ci-job.yml deleted file mode 100644 index 7477295f54..0000000000 --- a/eng/pipelines/jobs/stress-tests-ci-job.yml +++ /dev/null @@ -1,194 +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. -################################################################################ - -# This job builds and runs stress tests against an MDS NuGet package available -# as a pipeline artifact. -# -# The stress tests are located here: -# -# src/Microsoft.Data.SqlClient/tests/StressTests -# -# This template defines a job named 'run_stress_tests_job_' that can be -# depended on by downstream jobs. - -parameters: - # The suffix to append to the job name. - - name: jobNameSuffix - type: string - default: '' - - # The prefix to prepend to the job's display name: - # - # [] Run Stress Tests - # - - name: displayNamePrefix - type: string - default: '' - - # The name of the Azure Pipelines pool to use. - - name: poolName - type: string - default: '' - - # The pool VM image to use. - - name: vmImage - type: string - default: '' - - # The pipeline step to run to configure SQL Server. - - name: sqlSetupStep - type: step - - # The name of the MDS pipeline artifacts to download. - - name: mdsArtifactsName - type: string - default: '' - - # The solution file to restore/build. - - name: solution - type: string - default: '' - - # The test project to run. - - name: testProject - type: string - default: '' - - # dotnet CLI arguments for the restore step. - - name: restoreArguments - type: string - default: '' - - # dotnet CLI arguments for the build and run steps. - - name: buildArguments - type: string - default: '' - - # The list of .NET runtimes to test against. - - name: netTestRuntimes - type: object - default: [] - - # The list of .NET Framework runtimes to test against. - - name: netFrameworkTestRuntimes - type: object - default: [] - - # The stress test config file contents to write to the config file. - - name: configContent - type: string - default: '' - -jobs: -- job: run_stress_tests_job_${{ parameters.jobNameSuffix }} - displayName: '[${{ parameters.displayNamePrefix }}] Run Stress Tests' - pool: - name: ${{ parameters.poolName }} - ${{ if eq(parameters.poolName, 'Azure Pipelines') }}: - vmImage: ${{ parameters.vmImage }} - ${{ else }}: - demands: - - imageOverride -equals ${{ parameters.vmImage }} - - variables: - # Stress test command-line arguments. - - name: testArguments - value: -a SqlClient.Stress.Tests -console - - # Explicitly unset the $PLATFORM environment variable that is set by the - # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. - # This is defined with a non-standard Platform of 'AnyCPU', and will fail - # the builds if left defined. The stress tests solution does not require - # any specific Platform, and so its solution file doesn't support any - # non-standard platforms. - # - # Note that Azure Pipelines will inject this variable as PLATFORM into the - # environment of all tasks in this job. - # - # See: - # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch - # - - name: Platform - value: '' - - # Do the same for $CONFIGURATION since we explicitly set it using our - # 'buildConfiguration' parameter, and we don't want the environment to - # override us. - - name: Configuration - value: '' - - steps: - - # Install the .NET SDK and Runtimes. - - template: /eng/pipelines/steps/install-dotnet.yml@self - parameters: - runtimes: [8.x, 9.x] - - # Download the pipeline artifact that contains the MDS package to test. - - task: DownloadPipelineArtifact@2 - displayName: Download MDS Artifacts - inputs: - artifactName: ${{ parameters.mdsArtifactsName }} - targetPath: $(Build.SourcesDirectory)/packages - - # Setup the local SQL Server. - - ${{ parameters.sqlSetupStep }} - - # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't support - # all of our argument combinations for the different build steps. - - # Restore the solution. - - task: DotNetCoreCLI@2 - displayName: Restore Solution - inputs: - command: custom - custom: restore - projects: ${{ parameters.solution }} - arguments: ${{ parameters.restoreArguments }} - - # Build the solution. - - task: DotNetCoreCLI@2 - displayName: Build Solution - inputs: - command: custom - custom: build - projects: ${{ parameters.solution }} - arguments: ${{ parameters.buildArguments }} --no-restore - - # Write the config file. - - task: PowerShell@2 - displayName: Write Config File - inputs: - pwsh: true - targetType: inline - script: | - # Capture the multi-line JSON content into a variable. - $content = @" - ${{ parameters.configContent }} - "@ - - # Write the JSON content to the config file. - $content | Out-File -FilePath "config.json" - - # Run the stress tests for each .NET runtime. - - ${{ each runtime in parameters.netTestRuntimes }}: - - task: DotNetCoreCLI@2 - displayName: Test [${{runtime}}] - inputs: - command: custom - custom: run - projects: ${{ parameters.testProject }} - arguments: ${{ parameters.buildArguments }} --no-build -f ${{runtime}} -e STRESS_CONFIG_FILE=config.json -- $(testArguments) - - # Run the stress tests for each .NET Framework runtime. - - ${{ each runtime in parameters.netFrameworkTestRuntimes }}: - - task: DotNetCoreCLI@2 - displayName: Test [${{runtime}}] - inputs: - command: custom - custom: run - projects: ${{ parameters.testProject }} - arguments: ${{ parameters.buildArguments }} --no-build -f ${{runtime}} -e STRESS_CONFIG_FILE=config.json -- $(testArguments) diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml index 9d205b5108..a88d4dc347 100644 --- a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -27,7 +27,7 @@ parameters: # The prefix to prepend to the job's display name: # - # [] Run Stress Tests + # [] Test Abstractions Package # - name: displayNamePrefix type: string @@ -97,9 +97,7 @@ jobs: # Explicitly unset the $PLATFORM environment variable that is set by the # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. # This is defined with a non-standard Platform of 'AnyCPU', and will fail - # the builds if left defined. The stress tests solution does not require - # any specific Platform, and so its solution file doesn't support any - # non-standard platforms. + # the builds if left defined. # # Note that Azure Pipelines will inject this variable as PLATFORM into the # environment of all tasks in this job. diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 53ca26366f..5e5dffe381 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -46,7 +46,7 @@ parameters: # The prefix to prepend to the job's display name: # - # [] Run Stress Tests + # [] Test Azure Package # - name: displayNamePrefix type: string @@ -158,9 +158,7 @@ jobs: # Explicitly unset the $PLATFORM environment variable that is set by the # 'ADO Build properties' Library in the ADO SqlClientDrivers public # project. This is defined with a non-standard Platform of 'AnyCPU', and - # will fail the builds if left defined. The stress tests solution does - # not require any specific Platform, and so its solution file doesn't - # support any non-standard platforms. + # will fail the builds if left defined. # # Note that Azure Pipelines will inject this variable as PLATFORM into the # environment of all tasks in this job. diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index 440d7afa27..c454da710a 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -51,6 +51,7 @@ pr: - NuGet.config exclude: - eng/pipelines/onebranch/* + - eng/pipelines/stress/* # Do not trigger commit or schedule runs for this pipeline. trigger: none @@ -78,12 +79,6 @@ parameters: type: boolean default: false - # True to run the stress tests stage. - - name: enableStressTests - displayName: Enable Stress Tests - type: boolean - default: false - # The target frameworks to build and run tests for on Windows. # # These are _not_ the target frameworks to build the driver packages for. @@ -133,7 +128,6 @@ extends: buildPlatforms: ${{ parameters.buildPlatforms }} referenceType: Package debug: ${{ parameters.debug }} - enableStressTests: ${{ parameters.enableStressTests }} targetFrameworks: ${{ parameters.targetFrameworks }} targetFrameworksUnix: ${{ parameters.targetFrameworksUnix }} testJobTimeout: ${{ parameters.testJobTimeout }} diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index d3ae773181..539d03fe82 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -51,6 +51,7 @@ pr: - NuGet.config exclude: - eng/pipelines/onebranch/* + - eng/pipelines/stress/* # Do not trigger commit or schedule runs for this pipeline. trigger: none @@ -78,12 +79,6 @@ parameters: type: boolean default: false - # True to run the stress tests stage. - - name: enableStressTests - displayName: Enable Stress Tests - type: boolean - default: false - # The target frameworks to build and run tests for on Windows. # # These are _not_ the target frameworks to build the driver packages for. @@ -133,7 +128,6 @@ extends: buildPlatforms: ${{ parameters.buildPlatforms }} referenceType: Project debug: ${{ parameters.debug }} - enableStressTests: ${{ parameters.enableStressTests }} targetFrameworks: ${{ parameters.targetFrameworks }} targetFrameworksUnix: ${{ parameters.targetFrameworksUnix }} testJobTimeout: ${{ parameters.testJobTimeout }} diff --git a/eng/pipelines/stages/stress-tests-ci-stage.yml b/eng/pipelines/stages/stress-tests-ci-stage.yml deleted file mode 100644 index 978105d580..0000000000 --- a/eng/pipelines/stages/stress-tests-ci-stage.yml +++ /dev/null @@ -1,205 +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. -################################################################################ - -# This stage builds and runs stress tests against an MDS NuGet package available -# as a pipeline artifact. -# -# The stress tests are located here: -# -# src/Microsoft.Data.SqlClient/tests/StressTests -# -# All tests use a localhost SQL Server configured for SQL auth via the 'sa' user -# and the provided password. -# -# This stage depends on the secrets_stage. -# -# This template defines a stage named 'run_stress_tests_stage' that can be -# depended on by downstream stages. - -parameters: - - # The names of any additional stages this stage depends on, for example the stages that publish - # the MDS package artifacts we will test. - - name: additionalDependsOn - type: object - default: [] - - # The Azure package version to stress test. This version must be available in - # one of the configured NuGet sources. - - name: azurePackageVersion - displayName: Azure Package Version - type: string - - # The type of build to produce (Debug or Release) - - name: buildConfiguration - type: string - default: Debug - values: - - Debug - - Release - - # The verbosity level for the dotnet CLI commands. - - name: dotnetVerbosity - type: string - default: normal - values: - - quiet - - minimal - - normal - - detailed - - diagnostic - - # The name of the MDS pipeline artifacts to download. - - name: mdsArtifactsName - type: string - default: MDS.Artifacts - - # The MDS package version found in the pipeline artifacts we will download. - - name: mdsPackageVersion - type: string - - # The list of .NET Framework runtimes to test against. - - name: netFrameworkTestRuntimes - type: object - default: [net462] - - # The list of .NET runtimes to test against. - - name: netTestRuntimes - type: object - default: [net8.0, net9.0, net10.0] - -stages: - - stage: run_stress_tests_stage - displayName: Run Stress Tests - dependsOn: - - secrets_stage - - ${{ each dep in parameters.additionalDependsOn }}: - - ${{ dep }} - - variables: - # The directory where dotnet artifacts will be staged. Not to be - # confused with pipeline artifact. - - name: dotnetArtifactsDir - value: $(Build.StagingDirectory)/dotnetArtifacts - - # The solution file to use for all dotnet CLI commands. - - name: solution - value: src/Microsoft.Data.SqlClient/tests/StressTests/StressTests.slnx - - # The stress test project to run. - - name: testProject - value: src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj - - # dotnet CLI arguments common to all commands. - - name: commonArguments - value: >- - --verbosity ${{parameters.dotnetVerbosity}} - --artifacts-path $(dotnetArtifactsDir) - -p:MdsPackageVersion=${{parameters.mdsPackageVersion}} - -p:AzurePackageVersion=${{parameters.azurePackageVersion}} - - # dotnet CLI arguments for build/run commands. - - name: buildArguments - value: >- - $(commonArguments) - --configuration ${{parameters.buildConfiguration}} - - # Bring the SA password from the secrets_stage into scope here. - - name: saPassword - value: $[stageDependencies.secrets_stage.secrets_job.outputs['SaPassword.Value']] - - # The contents of the config file to use for all tests. We will write - # this to a JSON file for each test job, and then point to it via the - # STRESS_CONFIG_FILE environment variable. - - name: ConfigContent - value: | - [ - { - "name": "Azure SQL", - "type": "SqlServer", - "isDefault": true, - "dataSource": "localhost", - "user": "sa", - "password": "$(saPassword)", - "supportsWindowsAuthentication": false, - "isLocal": false, - "disableMultiSubnetFailover": true, - "disableNamedPipes": true, - "encrypt": false - } - ] - - jobs: - - # -------------------------------------------------------------------------- - # Build and test on Linux. - - - template: /eng/pipelines/jobs/stress-tests-ci-job.yml@self - parameters: - jobNameSuffix: linux - displayNamePrefix: Linux - poolName: $(ci_var_defaultPoolName) - vmImage: ADO-UB22-SQL22 - sqlSetupStep: - template: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml@self - parameters: - saPassword: $(saPassword) - mdsArtifactsName: ${{ parameters.mdsArtifactsName }} - solution: $(solution) - testProject: $(testProject) - restoreArguments: $(commonArguments) - buildArguments: $(buildArguments) - netTestRuntimes: ${{ parameters.netTestRuntimes }} - configContent: $(ConfigContent) - - # -------------------------------------------------------------------------- - # Build and test on Windows - - - template: /eng/pipelines/jobs/stress-tests-ci-job.yml@self - parameters: - jobNameSuffix: windows - displayNamePrefix: Win - poolName: $(ci_var_defaultPoolName) - # The Windows images include a suitable .NET Framework runtime, so we - # don't have to install one explicitly. - vmImage: ADO-MMS22-SQL22 - sqlSetupStep: - template: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml@self - parameters: - saPassword: $(saPassword) - mdsArtifactsName: ${{ parameters.mdsArtifactsName }} - solution: $(solution) - testProject: $(testProject) - restoreArguments: $(commonArguments) - buildArguments: $(buildArguments) - netTestRuntimes: ${{ parameters.netTestRuntimes }} - # Note that we include the .NET Framework runtimes for test runs on - # Windows. - netFrameworkTestRuntimes: ${{ parameters.netFrameworkTestRuntimes }} - configContent: $(ConfigContent) - - # -------------------------------------------------------------------------- - # Build and test on macOS. - - - template: /eng/pipelines/jobs/stress-tests-ci-job.yml@self - parameters: - jobNameSuffix: macos - displayNamePrefix: macOS - # We don't have any 1ES Hosted Pool images for macOS, so we use a - # generic one from Azure Pipelines. - poolName: Azure Pipelines - vmImage: macos-latest - sqlSetupStep: - template: /eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml@self - parameters: - saPassword: $(saPassword) - mdsArtifactsName: ${{ parameters.mdsArtifactsName }} - solution: $(solution) - testProject: $(testProject) - restoreArguments: $(commonArguments) - buildArguments: $(buildArguments) - netTestRuntimes: ${{ parameters.netTestRuntimes }} - configContent: $(ConfigContent) diff --git a/eng/pipelines/stress-tests-pipeline.yml b/eng/pipelines/stress-tests-pipeline.yml deleted file mode 100644 index e08f255e5a..0000000000 --- a/eng/pipelines/stress-tests-pipeline.yml +++ /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. # -################################################################################# - -# This pipeline runs the stress test suite using the most recent artifacts from -# the CI-SqlClient-Package pipeline. -# -# It runs after every successful run of the following pipelines: -# -# Public project: -# - PR-SqlClient-Package -# - CI-SqlClient-Package -# -# ADO.net project: -# - MDS Main CI-Package -# -# This pipeline definition is mapped to the Stress-SqlClient pipelines: -# -# Public project: -# -# TODO -# -# ADO.net project: -# -# TODO - -# Set the pipeline run name to the day-of-year and the daily run counter. -name: $(DayOfYear)$(Rev:rr) - -# Do not trigger this pipeline for PRs, commits, or schedules. -pr: none -trigger: none - -# Trigger this pipeline after successful runs of each of the following pipelines. -resources: - pipelines: - # The PR-SqlClient-Package pipeline in the Public project. - - pipeline: upstreamPipeline1 - project: Public - source: /ADO/PR-SqlClient-Package - trigger: true - # The CI-SqlClient-Package pipeline in the Public project. - - pipeline: upstreamPipeline2 - project: Public - source: /ADO/CI-SqlClient-Package - trigger: true - # The MDS Main CI-Package pipeline in the ADO.net project. - - pipeline: upstreamPipeline3 - project: ADO.net - source: /Internal CI/MDS Main CI-Package - trigger: true - -# Pipeline parameters, visible in the Azure DevOps UI. -parameters: - - # The build configuration to use; defaults to Release. - - name: buildConfiguration - displayName: Build Configuration - type: string - default: Release - values: - - Debug - - Release - - # True to emit debug information and steps. - - name: debug - displayName: Enable debug output - type: boolean - default: false - -stages: -- stage: Placeholder - displayName: Placeholder Stage - jobs: - - job: PlaceholderJob - displayName: Placeholder Job - steps: - - script: echo "This is a placeholder stage." - displayName: Placeholder Step diff --git a/eng/pipelines/stress/stress-tests-job.yml b/eng/pipelines/stress/stress-tests-job.yml new file mode 100644 index 0000000000..40cecf6e93 --- /dev/null +++ b/eng/pipelines/stress/stress-tests-job.yml @@ -0,0 +1,180 @@ +#################################################################################################### +# Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this +# file to you under the MIT license. See the LICENSE file in the project root for more information. +#################################################################################################### + +# This job builds and runs stress tests by compiling the SqlClient projects transitively. +# +# The stress tests are located here: +# +# src/Microsoft.Data.SqlClient/tests/StressTests +# +# This template defines a job named 'stress_tests_job_' that can be depended on by +# downstream jobs. + +parameters: + + # The type of build to produce (Debug or Release) + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # True to enable debugging steps. + - name: debug + type: boolean + default: false + + # The prefix to prepend to the job's display name: + # + # [] Run Stress Tests + # + - name: displayNamePrefix + type: string + + # The verbosity level for the dotnet CLI commands. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + + # The suffix to append to the job name. + - name: jobNameSuffix + type: string + + # The list of .NET Framework runtimes to test against. + - name: netFrameworkTestRuntimes + type: object + default: [] + + # The list of .NET runtimes to test against. + - name: netTestRuntimes + type: object + default: [] + + # The name of the Azure Pipelines pool to use. + - name: poolName + type: string + + # The local SQL Server instance's 'sa' password, for use in the config file. + - name: saPassword + type: string + + # The step to run to configure SQL Server. This should configure a local SQL Server instance with + # an 'sa' login using the same password provided by the saPassword parameter. + - name: sqlSetupStep + type: step + + # The pool VM image to use. + - name: vmImage + type: string + +jobs: + - job: stress_tests_job_${{ parameters.jobNameSuffix }} + displayName: '[${{ parameters.displayNamePrefix }}] Run Stress Tests' + pool: + name: ${{ parameters.poolName }} + ${{ if eq(parameters.poolName, 'Azure Pipelines') }}: + vmImage: ${{ parameters.vmImage }} + ${{ else }}: + demands: + - imageOverride -equals ${{ parameters.vmImage }} + + variables: + + # The directory where dotnet artifacts will be staged. Not to be + # confused with pipeline artifacts. + - name: dotnetArtifactsDir + value: $(Build.StagingDirectory)/dotnetArtifacts + + # The top-level project file to build and run. + - name: project + value: $(Build.SourcesDirectory)/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj + + # dotnet CLI arguments. + - name: dotnetArguments + value: >- + --verbosity ${{ parameters.dotnetVerbosity }} + --artifacts-path $(dotnetArtifactsDir) + --configuration ${{ parameters.buildConfiguration }} + + # The contents of the config file to use for all tests. We will write this to a JSON file and + # then point to it via the STRESS_CONFIG_FILE environment variable. + - name: configContent + value: | + [ + { + "name": "Azure SQL", + "type": "SqlServer", + "isDefault": true, + "dataSource": "localhost", + "user": "sa", + "password": "${{ parameters.saPassword }}", + "supportsWindowsAuthentication": false, + "isLocal": false, + "disableMultiSubnetFailover": true, + "disableNamedPipes": true, + "encrypt": false + } + ] + + # Stress test command-line arguments. + - name: testArguments + value: -a SqlClient.Stress.Tests -console + + steps: + + # Install the .NET SDK and Runtimes. + - template: /eng/pipelines/steps/install-dotnet.yml@self + parameters: + runtimes: [8.x, 9.x] + + # Setup the local SQL Server. + - ${{ parameters.sqlSetupStep }} + + # Write the config file. + - task: PowerShell@2 + displayName: Write Config File + inputs: + pwsh: true + targetType: inline + script: | + # Capture the multi-line JSON content into a variable. + $content = @" + $(configContent) + "@ + + # Write the JSON content to the config file. + $content | Out-File -FilePath "config.json" + + # Build the project. + - task: DotNetCoreCLI@2 + displayName: Build Project + inputs: + command: build + projects: $(project) + arguments: $(dotnetArguments) + + # Run the stress tests for each .NET runtime. + - ${{ each runtime in parameters.netTestRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{ runtime }}] + inputs: + command: run + projects: $(project) + arguments: $(dotnetArguments) --no-build -f ${{ runtime }} -e STRESS_CONFIG_FILE=config.json -- $(testArguments) + + # Run the stress tests for each .NET Framework runtime. + - ${{ each runtime in parameters.netFrameworkTestRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{ runtime }}] + inputs: + command: run + projects: $(project) + arguments: $(dotnetArguments) --no-build -f ${{ runtime }} -e STRESS_CONFIG_FILE=config.json -- $(testArguments) diff --git a/eng/pipelines/stress/stress-tests-pipeline.yml b/eng/pipelines/stress/stress-tests-pipeline.yml new file mode 100644 index 0000000000..cec68912fb --- /dev/null +++ b/eng/pipelines/stress/stress-tests-pipeline.yml @@ -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. +#################################################################################################### + +# This pipeline runs the stress test suite against the SqlClient projects, building them +# transitively as necessary, triggered by successful runs of the following pipelines: +# +# Public project: +# Triggering pipelines: PR-SqlClient-Project, CI-SqlClient (branch main only) +# Pipeline name: Stress-SqlClient +# Pipeline URL: TODO: Create the Azure DevOps pipeline. +# +# ADO.Net project: +# Triggering pipeline: MDS Main CI (branch internal/main only) +# Pipeline name: Stress-SqlClient +# Pipeline URL: TODO: Create the Azure DevOps pipeline. + +# Set the pipeline run name to the day-of-year and the daily run counter. +name: $(DayOfYear)$(Rev:rr) + +# Do not trigger this pipeline for PRs, commits, or schedules. +pr: none +trigger: none + +# Trigger this pipeline after successful runs of the desired pipelines. +# +# The pipeline identifiers are displayed in the Azure DevOps UI, so it is helpful if they indicate +# the project, folder, and pipeline name, hence the verbose values below. +# +resources: + pipelines: + + # The PR-SqlClient-Project pipeline in the Public project. + - pipeline: Public-ADO-PR-SqlClient-Project + project: Public + source: /ADO/PR-SqlClient-Project + trigger: true + + # The CI-SqlClient pipeline in the Public project. + - pipeline: Public-ADO-CI-SqlClient + project: Public + source: /ADO/CI-SqlClient + trigger: + branches: + include: + - main + + # The MDS Main CI pipeline in the ADO.Net project. + - pipeline: ADO-Net-Internal-CI-MDS-Main-CI + project: ADO.Net + source: /Internal CI/MDS Main CI + trigger: + branches: + include: + - internal/main + +# Pipeline parameters, visible in the Azure DevOps UI. +parameters: + + # The build configuration to use; defaults to Release. + - name: buildConfiguration + displayName: Build Configuration + type: string + default: Release + values: + - Debug + - Release + + # True to emit debug information and steps. + - name: debug + displayName: Enable debug output + type: boolean + default: false + + # Dotnet CLI verbosity level. + - name: dotnetVerbosity + displayName: dotnet CLI Verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +# The stages to run. +stages: + + # Generate secrets. We use these for the local SQL Server 'sa' logins. + - template: /eng/pipelines/stages/generate-secrets-ci-stage.yml@self + parameters: + debug: ${{ parameters.debug }} + + # Run the stress tests. + - template: /eng/pipelines/stress/stress-tests-stage.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} diff --git a/eng/pipelines/stress/stress-tests-stage.yml b/eng/pipelines/stress/stress-tests-stage.yml new file mode 100644 index 0000000000..0f0000f99a --- /dev/null +++ b/eng/pipelines/stress/stress-tests-stage.yml @@ -0,0 +1,130 @@ +#################################################################################################### +# Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this +# file to you under the MIT license. See the LICENSE file in the project root for more information. +#################################################################################################### + +# This stage builds and runs stress tests against the SqlClient projects, building them transitively +# as necessary. +# +# The stress tests are located here: +# +# src/Microsoft.Data.SqlClient/tests/StressTests +# +# All tests use a localhost SQL Server configured for SQL auth via the 'sa' user and the generated +# password. +# +# This stage depends on the secrets_stage. +# +# This template defines a stage named 'stress_tests_stage' that can be depended on by downstream +# stages. + +parameters: + + # The type of build to produce (Debug or Release) + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # True to enable debugging steps. + - name: debug + type: boolean + default: false + + # The verbosity level for the dotnet CLI commands. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + + # The list of .NET Framework runtimes to test against. + - name: netFrameworkTestRuntimes + type: object + default: [net462] + + # The list of .NET runtimes to test against. + - name: netTestRuntimes + type: object + default: [net8.0, net9.0, net10.0] + +stages: + - stage: stress_tests_stage + displayName: Run Stress Tests + dependsOn: + - secrets_stage + + variables: + # Bring the SA password from the secrets_stage into scope here. + - name: saPassword + value: $[stageDependencies.secrets_stage.secrets_job.outputs['SaPassword.Value']] + + jobs: + + # ---------------------------------------------------------------------------------------------- + # Build and test on Linux. + + - template: /eng/pipelines/stress/stress-tests-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + displayNamePrefix: Linux + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: linux + netTestRuntimes: ${{ parameters.netTestRuntimes }} + poolName: ADO-CI-1ES-Pool + saPassword: $(saPassword) + sqlSetupStep: + template: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml@self + parameters: + saPassword: $(saPassword) + vmImage: ADO-UB22-SQL22 + + # ---------------------------------------------------------------------------------------------- + # Build and test on Windows + + - template: /eng/pipelines/stress/stress-tests-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + displayNamePrefix: Win + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: windows + # Note that we include the .NET Framework runtimes for test runs on Windows. + netFrameworkTestRuntimes: ${{ parameters.netFrameworkTestRuntimes }} + netTestRuntimes: ${{ parameters.netTestRuntimes }} + poolName: ADO-CI-1ES-Pool + saPassword: $(saPassword) + sqlSetupStep: + template: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml@self + parameters: + saPassword: $(saPassword) + # The Windows images include a suitable .NET Framework runtime, so we don't have to install + # one explicitly. + vmImage: ADO-MMS22-SQL22 + + # ---------------------------------------------------------------------------------------------- + # Build and test on macOS. + + - template: /eng/pipelines/stress/stress-tests-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + displayNamePrefix: macOS + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: macos + netTestRuntimes: ${{ parameters.netTestRuntimes }} + # We don't have any 1ES Hosted Pool images for macOS, so we use a generic one from Azure + # Pipelines. + poolName: Azure Pipelines + saPassword: $(saPassword) + sqlSetupStep: + template: /eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml@self + parameters: + saPassword: $(saPassword) + vmImage: macos-latest diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md b/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md index f0723c3189..546428dbe9 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Readme.md @@ -1,26 +1,21 @@ # Microsoft.Data.SqlClient Stress Test -This Stress testing application for `Microsoft.Data.SqlClient` is under progress. +This Stress testing application for the `Microsoft.Data.SqlClient` suite is a work in progress. -This project intends to help finding a certain level of effectiveness under -unfavorable conditions, and verifying the mode of failures. +This project intends to help finding a certain level of effectiveness under unfavorable conditions, +and verifying the mode of failures. -This is a console application targeting all frameworks supported by MDS, -currently: +This is a console application targeting all frameworks supported by MDS, currently: -- .NET 8.0 +- .NET 10.0 - .NET 9.0 -- .NET Framework 4.6.2 -- .NET Framework 4.7 -- .NET Framework 4.7.1 -- .NET Framework 4.7.2 -- .NET Framework 4.8 -- .NET Framework 4.8.1 +- .NET 8.0 +- .NET Framework 4.6.2 (Windows only; higher .NET Framework versions are supported at runtime via + compatibility) ## Purpose of application for developers -Define fuzz tests for all new features/APIs in the driver and to be run before -every GA release. +Define fuzz tests for all new features/APIs in the driver and to be run before every GA release. ## Pre-Requisites @@ -40,14 +35,13 @@ Required in the config file: |`disableNamedPipes`|`true`, `false`|`true` means the connections will create just using tcp protocol.| |`encrypt`|`true`, `false`|Assigns the encrypt property of the connection strings.| -Note: The database user must have permission to create and drop databases. -Each execution of the stress tests will create a database with a name like: +Note: The database user must have permission to create and drop databases. Each execution of the +stress tests will create a database with a name like: - `StressTests-` -The database will be dropped as a best effort once testing is complete. This -allows for multiple test runs to execute in parallel against the same database -server without colliding. +The database will be dropped as a best effort once testing is complete. This allows for multiple +test runs to execute in parallel against the same database server without colliding. ## Adding new Tests @@ -55,83 +49,55 @@ server without colliding. ## Building the application -To build the application using the `StressTests.slnx` solution: - -```bash -dotnet build [-c|--configuration ] -``` - -```bash -# Builds the application for the Client Os in `Debug` Configuration for `AnyCpu` -# platform. -# -# All supported target frameworks are built by default. - -$ dotnet build -``` - -```bash -# Build the application for .Net framework 4.8.1 with `Debug` configuration. - -$ dotnet build -f net481 -``` +Build the application using the top-level project `SqlClient.Stress.Runner`: ```bash -# Build the application for .Net 9.0 with `Release` configuration. - -$ dotnet build -f net9.0 -c Release -``` - -```bash -# Cleans all build directories - -$ dotnet clean +$ cd .../src/Microsoft.Data.SqlClient/tests/StressTests +dotnet build SqlClient.Stress.Runner [-c ] ``` ## Running tests -After building the application, find the built folder with target framework and -run the `stresstest.exe` file with required arguments. +After building the application, find the built folder with target framework and run the +`stresstest.exe` file with required arguments. -Find the result in a log file inside the `logs` folder besides the command -prompt. +Find the result in a log file inside the `logs` folder besides the command prompt. -You may specify the config file by supplying an environment variable that -points to the file: +You may specify the config file by supplying an environment variable that points to the file: - `STRESS_CONFIG_FILE=/path/to/my/config.jsonc` ## Command prompt -You must run the stress tests from the root of the Stress Tests project -directory (i.e. the same directory this readme file is in). +You must run the stress tests from the root of the Stress Tests project directory (i.e. the same +directory this readme file is in). ```bash # Linux -$ cd /home/paul/dev/SqlClient/src/Microsoft.Data.SqlClient/tests/StressTests +$ cd .../src/Microsoft.Data.SqlClient/tests/StressTests # Via dotnet run CLI: -$ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj -- -a SqlClient.Stress.Tests +$ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner -- -a SqlClient.Stress.Tests # Via dotnet CLI: $ dotnet SqlClient.Stress.Runner/bin/Debug/net9.0/stresstest.dll -a SqlClient.Stress.Tests # With a specific config file and all output to console: -$ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj -e STRESS_CONFIG_FILE=/path/to/config.jsonc -- -a SqlClient.Stress.Tests -console +$ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner -e STRESS_CONFIG_FILE=/path/to/config.jsonc -- -a SqlClient.Stress.Tests -console ``` ```powershell # Windows -> cd \dev\SqlClient\src\Microsoft.Data.SqlClient\tests\StressTests +> cd ...\src\Microsoft.Data.SqlClient\tests\StressTests # Via dotnet run CLI: -> dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj -- -a SqlClient.Stress.Tests +> dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner -- -a SqlClient.Stress.Tests # Via executable: > .\SqlClient.Stress.Runner\bin\Debug\net481\stresstest.exe -a SqlClient.Stress.Tests # With a specific config file and all output to console: -> dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj -e STRESS_CONFIG_FILE=c:\path\to\config.jsonc -- -a SqlClient.Stress.Tests -console +> dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner -e STRESS_CONFIG_FILE=c:\path\to\config.jsonc -- -a SqlClient.Stress.Tests -console ``` ## Supported arguments @@ -164,14 +130,14 @@ $ dotnet run --no-build -f net9.0 --project SqlClient.Stress.Runner/SqlClient.St # Run the application for a built target framework and all discovered tests # without debugger attached and shows the test methods' names. -> .\stresstest.exe -a SqlClient.Stress.Tests -all -printMethodName +> .\stresstest.exe -a SqlClient.Stress.Tests -all -printMethodName ``` ```powershell # Run the application for a built target framework and all discovered tests and # will wait for debugger to be attached. -> .\stresstest.exe -a SqlClient.Stress.Tests -all -debug +> .\stresstest.exe -a SqlClient.Stress.Tests -all -debug ``` ```powershell diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/StressTests.slnx b/src/Microsoft.Data.SqlClient/tests/StressTests/StressTests.slnx deleted file mode 100644 index a0dfd5e503..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/StressTests.slnx +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - From 8c887846aaa2846d7a3ab3718b61a24b60e1b394 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Sat, 28 Mar 2026 01:57:23 +0530 Subject: [PATCH 014/298] Fix API docs (#4084) --- .../Microsoft.Data.SqlClient/SqlCommand.xml | 414 +++++++++--------- .../SqlDataAdapter.xml | 224 +++++----- .../SqlDataReader.xml | 38 +- 3 files changed, 338 insertions(+), 338 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml index a3d87c843d..7509f756b3 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml @@ -357,35 +357,35 @@ Next, compile and execute the following: using System; using System.Data; using Microsoft.Data.SqlClient; - + class Class1 { static void Main() { - // This is a simple example that demonstrates the usage of the + // This is a simple example that demonstrates the usage of the // BeginExecuteNonQuery functionality. - // The WAITFOR statement simply adds enough time to prove the + // The WAITFOR statement simply adds enough time to prove the // asynchronous nature of the command. - + string commandText = "UPDATE Production.Product SET ReorderPoint = ReorderPoint + 1 " + "WHERE ReorderPoint Is Not Null;" + "WAITFOR DELAY '0:0:3';" + "UPDATE Production.Product SET ReorderPoint = ReorderPoint - 1 " + "WHERE ReorderPoint Is Not Null"; - + RunCommandAsynchronously(commandText, GetConnectionString()); - + Console.WriteLine("Press ENTER to continue."); Console.ReadLine(); } - + private static void RunCommandAsynchronously(string commandText, string connectionString) { // Given command text and connection string, asynchronously execute // the specified command against the connection. For this example, - // the code displays an indicator as it is working, verifying the - // asynchronous behavior. + // the code displays an indicator as it is working, verifying the + // asynchronous behavior. using (SqlConnection connection = new SqlConnection(connectionString)) { try @@ -393,17 +393,17 @@ Next, compile and execute the following: int count = 0; SqlCommand command = new SqlCommand(commandText, connection); connection.Open(); - + IAsyncResult result = command.BeginExecuteNonQuery(); while (!result.IsCompleted) { Console.WriteLine("Waiting ({0})", count++); // Wait for 1/10 second, so the counter - // does not consume all available resources + // does not consume all available resources // on the main thread. System.Threading.Thread.Sleep(100); } - + Console.WriteLine("Command complete. Affected {0} rows.", command.EndExecuteNonQuery(result)); } @@ -423,11 +423,11 @@ Next, compile and execute the following: } } } - + private static string GetConnectionString() { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. + // To avoid storing the connection string in your code, + // you can retrieve it from a configuration file. return "Data Source=(local);Integrated Security=SSPI;" + "Initial Catalog=AdventureWorks"; } @@ -527,7 +527,7 @@ Next, compile and execute the following: using System.Text; using System.Windows.Forms; using Microsoft.Data.SqlClient; - + namespace Microsoft.AdoDotNet.CodeSamples { public partial class Form1 : Form @@ -536,9 +536,9 @@ Next, compile and execute the following: { InitializeComponent(); } - - // Hook up the form's Load event handler (you can double-click on - // the form's design surface in Visual Studio), and then add + + // Hook up the form's Load event handler (you can double-click on + // the form's design surface in Visual Studio), and then add // this code to the form's class: private void Form1_Load(object sender, EventArgs e) { @@ -546,42 +546,42 @@ Next, compile and execute the following: this.FormClosing += new System.Windows.Forms. FormClosingEventHandler(this.Form1_FormClosing); } - + // You need this delegate in order to display text from a thread // other than the form's thread. See the HandleCallback // procedure for more information. - // This same delegate matches both the DisplayStatus + // This same delegate matches both the DisplayStatus // and DisplayResults methods. private delegate void DisplayInfoDelegate(string Text); - + // This flag ensures that the user does not attempt - // to restart the command or close the form while the + // to restart the command or close the form while the // asynchronous command is executing. private bool isExecuting; - - // This example maintains the connection object + + // This example maintains the connection object // externally, so that it is available for closing. private SqlConnection connection; - + private static string GetConnectionString() { - // To avoid storing the connection string in your code, + // 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"; } - + private void DisplayStatus(string Text) { this.label1.Text = Text; } - + private void DisplayResults(string Text) { this.label1.Text = Text; DisplayStatus("Ready"); } - + private void Form1_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e) { if (isExecuting) @@ -591,7 +591,7 @@ Next, compile and execute the following: e.Cancel = true; } } - + private void button1_Click(object sender, System.EventArgs e) { if (isExecuting) @@ -608,7 +608,7 @@ Next, compile and execute the following: DisplayResults(""); DisplayStatus("Connecting..."); connection = new SqlConnection(GetConnectionString()); - // To emulate a long-running query, wait for + // To emulate a long-running query, wait for // a few seconds before working with the data. // This command does not do much, but that's the point-- // it does not change your data, in the long run. @@ -618,19 +618,19 @@ Next, compile and execute the following: "WHERE ReorderPoint Is Not Null;" + "UPDATE Production.Product SET ReorderPoint = ReorderPoint - 1 " + "WHERE ReorderPoint Is Not Null"; - + command = new SqlCommand(commandText, connection); connection.Open(); - + DisplayStatus("Executing..."); isExecuting = true; - // Although it is not required that you pass the - // SqlCommand object as the second parameter in the + // Although it is not required that you pass the + // SqlCommand object as the second parameter in the // BeginExecuteNonQuery call, doing so makes it easier // to call EndExecuteNonQuery in the callback procedure. AsyncCallback callback = new AsyncCallback(HandleCallback); command.BeginExecuteNonQuery(callback, command); - + } catch (Exception ex) { @@ -643,7 +643,7 @@ Next, compile and execute the following: } } } - + private void HandleCallback(IAsyncResult result) { try @@ -658,38 +658,38 @@ Next, compile and execute the following: { rowText = " row affected."; } - + rowText = rowCount + rowText; - + // You may not interact with the form and its contents // from a different thread, and this callback procedure // is all but guaranteed to be running from a different thread - // than the form. Therefore you cannot simply call code that + // than the form. Therefore you cannot simply call code that // displays the results, like this: // DisplayResults(rowText) - + // Instead, you must call the procedure from the form's thread. // One simple way to accomplish this is to call the Invoke // method of the form, which calls the delegate you supply - // from the form's thread. + // from the form's thread. DisplayInfoDelegate del = new DisplayInfoDelegate(DisplayResults); this.Invoke(del, rowText); - + } catch (Exception ex) { - // Because you are now running code in a separate thread, + // Because you are now running code in a separate thread, // if you do not handle the exception here, none of your other - // code catches the exception. Because none of + // code catches the exception. Because none of // your code is on the call stack in this thread, there is nothing - // higher up the stack to catch the exception if you do not - // handle it here. You can either log the exception or - // invoke a delegate (as in the non-error case in this + // higher up the stack to catch the exception if you do not + // handle it here. You can either log the exception or + // invoke a delegate (as in the non-error case in this // example) to display the error on the form. In no case // can you simply display the error without executing a delegate - // as in the try block here. - - // You can create the delegate instance as you + // as in the try block here. + + // You can create the delegate instance as you // invoke it, like this: this.Invoke(new DisplayInfoDelegate(DisplayStatus), String.Format("Ready(last error: {0}", ex.Message)); @@ -851,7 +851,7 @@ Next, compile and execute the following: // Display the data within the reader. while (reader.Read()) { - // Display all the columns. + // Display all the columns. for (int i = 0; i < reader.FieldCount; i++) { Console.Write("{0}\t", reader.GetValue(i)); @@ -862,8 +862,8 @@ Next, compile and execute the following: private static string GetConnectionString() { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. + // 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"; @@ -953,54 +953,54 @@ Next, compile and execute the following: using System; using System.Data; using Microsoft.Data.SqlClient; - + class Class1 { static void Main() { // This example is not terribly useful, but it proves a point. - // The WAITFOR statement simply adds enough time to prove the + // The WAITFOR statement simply adds enough time to prove the // asynchronous nature of the command. string commandText = "WAITFOR DELAY '00:00:03';" + "SELECT ProductID, Name FROM Production.Product WHERE ListPrice < 100"; - + RunCommandAsynchronously(commandText, GetConnectionString()); - + Console.WriteLine("Press ENTER to continue."); Console.ReadLine(); } - + private static void RunCommandAsynchronously(string commandText, string connectionString) { // Given command text and connection string, asynchronously execute // the specified command against the connection. For this example, - // the code displays an indicator as it is working, verifying the - // asynchronous behavior. + // the code displays an indicator as it is working, verifying the + // asynchronous behavior. try { // The code does not need to handle closing the connection explicitly-- // the use of the CommandBehavior.CloseConnection option takes care - // of that for you. + // of that for you. SqlConnection connection = new SqlConnection(connectionString); SqlCommand command = new SqlCommand(commandText, connection); - + connection.Open(); IAsyncResult result = command.BeginExecuteReader(CommandBehavior.CloseConnection); - + // Although it is not necessary, the following code - // displays a counter in the console window, indicating that - // the main thread is not blocked while awaiting the command + // displays a counter in the console window, indicating that + // the main thread is not blocked while awaiting the command // results. int count = 0; while (!result.IsCompleted) { Console.WriteLine("Waiting ({0})", count++); // Wait for 1/10 second, so the counter - // does not consume all available resources + // does not consume all available resources // on the main thread. System.Threading.Thread.Sleep(100); } - + using (SqlDataReader reader = command.EndExecuteReader(result)) { DisplayResults(reader); @@ -1021,26 +1021,26 @@ Next, compile and execute the following: Console.WriteLine("Error: {0}", ex.Message); } } - + private static void DisplayResults(SqlDataReader reader) { // Display the data within the reader. while (reader.Read()) { - // Display all the columns. + // Display all the columns. for (int i = 0; i < reader.FieldCount; i++) { Console.Write("{0}\t", reader.GetValue(i)); } - + Console.WriteLine(); } } - + private static string GetConnectionString() { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. + // 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"; } @@ -1138,54 +1138,54 @@ Next, compile and execute the following: using System; using System.Data; using Microsoft.Data.SqlClient; - + class Class1 { static void Main() { // This example is not terribly useful, but it proves a point. - // The WAITFOR statement simply adds enough time to prove the + // The WAITFOR statement simply adds enough time to prove the // asynchronous nature of the command. string commandText = "WAITFOR DELAY '00:00:03';" + "SELECT ProductID, Name FROM Production.Product WHERE ListPrice < 100"; - + RunCommandAsynchronously(commandText, GetConnectionString()); - + Console.WriteLine("Press ENTER to continue."); Console.ReadLine(); } - + private static void RunCommandAsynchronously(string commandText, string connectionString) { // Given command text and connection string, asynchronously execute // the specified command against the connection. For this example, - // the code displays an indicator as it is working, verifying the - // asynchronous behavior. + // the code displays an indicator as it is working, verifying the + // asynchronous behavior. try { // The code does not need to handle closing the connection explicitly-- // the use of the CommandBehavior.CloseConnection option takes care - // of that for you. + // of that for you. SqlConnection connection = new SqlConnection(connectionString); SqlCommand command = new SqlCommand(commandText, connection); - + connection.Open(); IAsyncResult result = command.BeginExecuteReader(CommandBehavior.CloseConnection); - + // Although it is not necessary, the following code - // displays a counter in the console window, indicating that - // the main thread is not blocked while awaiting the command + // displays a counter in the console window, indicating that + // the main thread is not blocked while awaiting the command // results. int count = 0; while (!result.IsCompleted) { Console.WriteLine("Waiting ({0})", count++); // Wait for 1/10 second, so the counter - // does not consume all available resources + // does not consume all available resources // on the main thread. System.Threading.Thread.Sleep(100); } - + using (SqlDataReader reader = command.EndExecuteReader(result)) { DisplayResults(reader); @@ -1206,26 +1206,26 @@ Next, compile and execute the following: Console.WriteLine("Error: {0}", ex.Message); } } - + private static void DisplayResults(SqlDataReader reader) { // Display the data within the reader. while (reader.Read()) { - // Display all the columns. + // Display all the columns. for (int i = 0; i < reader.FieldCount; i++) { Console.Write("{0}\t", reader.GetValue(i)); } - + Console.WriteLine(); } } - + private static string GetConnectionString() { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. + // 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"; } @@ -1335,7 +1335,7 @@ Next, compile and execute the following: using System.Text; using System.Windows.Forms; using Microsoft.Data.SqlClient; - + namespace Microsoft.AdoDotNet.CodeSamples { public partial class Form1 : Form @@ -1344,28 +1344,28 @@ Next, compile and execute the following: { InitializeComponent(); } - - // Hook up the form's Load event handler (you can double-click on - // the form's design surface in Visual Studio), and then add + + // Hook up the form's Load event handler (you can double-click on + // the form's design surface in Visual Studio), and then add // this code to the form's class: // You need this delegate in order to fill the grid from // a thread other than the form's thread. See the HandleCallback // procedure for more information. private delegate void FillGridDelegate(SqlDataReader reader); - + // You need this delegate to update the status bar. private delegate void DisplayStatusDelegate(string Text); - + // This flag ensures that the user does not attempt - // to restart the command or close the form while the + // to restart the command or close the form while the // asynchronous command is executing. private bool isExecuting; - + private void DisplayStatus(string Text) { this.label1.Text = Text; } - + private void FillGrid(SqlDataReader reader) { try @@ -1385,7 +1385,7 @@ Next, compile and execute the following: finally { // Closing the reader also closes the connection, - // because this reader was created using the + // because this reader was created using the // CommandBehavior.CloseConnection value. if (reader != null) { @@ -1393,7 +1393,7 @@ Next, compile and execute the following: } } } - + private void HandleCallback(IAsyncResult result) { try @@ -1403,37 +1403,37 @@ Next, compile and execute the following: // of the IAsyncResult parameter. SqlCommand command = (SqlCommand)result.AsyncState; SqlDataReader reader = command.EndExecuteReader(result); - + // You may not interact with the form and its contents // from a different thread, and this callback procedure // is all but guaranteed to be running from a different thread - // than the form. Therefore you cannot simply call code that + // than the form. Therefore you cannot simply call code that // fills the grid, like this: // FillGrid(reader); // Instead, you must call the procedure from the form's thread. // One simple way to accomplish this is to call the Invoke // method of the form, which calls the delegate you supply - // from the form's thread. + // from the form's thread. FillGridDelegate del = new FillGridDelegate(FillGrid); this.Invoke(del, reader); - - // Do not close the reader here, because it is being used in + + // Do not close the reader here, because it is being used in // a separate thread. Instead, have the procedure you have // called close the reader once it is done with it. } catch (Exception ex) { - // Because you are now running code in a separate thread, + // Because you are now running code in a separate thread, // if you do not handle the exception here, none of your other - // code catches the exception. Because there is none of + // code catches the exception. Because there is none of // your code on the call stack in this thread, there is nothing - // higher up the stack to catch the exception if you do not - // handle it here. You can either log the exception or - // invoke a delegate (as in the non-error case in this + // higher up the stack to catch the exception if you do not + // handle it here. You can either log the exception or + // invoke a delegate (as in the non-error case in this // example) to display the error on the form. In no case // can you simply display the error without executing a delegate - // as in the try block here. - // You can create the delegate instance as you + // as in the try block here. + // You can create the delegate instance as you // invoke it, like this: this.Invoke(new DisplayStatusDelegate(DisplayStatus), "Error: " + ex.Message); } @@ -1442,15 +1442,15 @@ Next, compile and execute the following: isExecuting = false; } } - + private string GetConnectionString() { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. + // 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"; } - + private void button1_Click(object sender, System.EventArgs e) { if (isExecuting) @@ -1467,17 +1467,17 @@ Next, compile and execute the following: { DisplayStatus("Connecting..."); connection = new SqlConnection(GetConnectionString()); - // To emulate a long-running query, wait for + // To emulate a long-running query, wait for // a few seconds before retrieving the real data. command = new SqlCommand("WAITFOR DELAY '0:0:5';" + "SELECT ProductID, Name, ListPrice, Weight FROM Production.Product", connection); connection.Open(); - + DisplayStatus("Executing..."); isExecuting = true; - // Although it is not required that you pass the - // SqlCommand object as the second parameter in the + // Although it is not required that you pass the + // SqlCommand object as the second parameter in the // BeginExecuteReader call, doing so makes it easier // to call EndExecuteReader in the callback procedure. AsyncCallback callback = new AsyncCallback(HandleCallback); @@ -1494,13 +1494,13 @@ Next, compile and execute the following: } } } - + private void Form1_Load(object sender, System.EventArgs e) { this.button1.Click += new System.EventHandler(this.button1_Click); this.FormClosing += new FormClosingEventHandler(Form1_FormClosing); } - + void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (isExecuting) @@ -1605,58 +1605,58 @@ Next, compile and execute the following: using System.Data; using Microsoft.Data.SqlClient; using System.Xml; - + class Class1 { static void Main() { // This example is not terribly effective, but it proves a point. - // The WAITFOR statement simply adds enough time to prove the + // The WAITFOR statement simply adds enough time to prove the // asynchronous nature of the command. string commandText = "WAITFOR DELAY '00:00:03';" + "SELECT Name, ListPrice FROM Production.Product " + "WHERE ListPrice < 100 " + "FOR XML AUTO, XMLDATA"; - + RunCommandAsynchronously(commandText, GetConnectionString()); - + Console.WriteLine("Press ENTER to continue."); Console.ReadLine(); } - + private static void RunCommandAsynchronously(string commandText, string connectionString) { // Given command text and connection string, asynchronously execute // the specified command against the connection. For this example, - // the code displays an indicator as it is working, verifying the - // asynchronous behavior. + // the code displays an indicator as it is working, verifying the + // asynchronous behavior. using (SqlConnection connection = new SqlConnection(connectionString)) { SqlCommand command = new SqlCommand(commandText, connection); - + connection.Open(); IAsyncResult result = command.BeginExecuteXmlReader(); - + // Although it is not necessary, the following procedure - // displays a counter in the console window, indicating that - // the main thread is not blocked while awaiting the command + // displays a counter in the console window, indicating that + // the main thread is not blocked while awaiting the command // results. int count = 0; while (!result.IsCompleted) { Console.WriteLine("Waiting ({0})", count++); // Wait for 1/10 second, so the counter - // does not consume all available resources + // does not consume all available resources // on the main thread. System.Threading.Thread.Sleep(100); } - + XmlReader reader = command.EndExecuteXmlReader(result); DisplayProductInfo(reader); } } - + private static void DisplayProductInfo(XmlReader reader) { // Display the data within the reader. @@ -1670,11 +1670,11 @@ Next, compile and execute the following: } } } - + private static string GetConnectionString() { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. + // 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"; } @@ -1788,49 +1788,49 @@ Next, compile and execute the following: using System.Windows.Forms; using System.Xml; using Microsoft.Data.SqlClient; - + namespace Microsoft.AdoDotNet.CodeSamples { public partial class Form1 : Form { - // Hook up the form's Load event handler and then add + // Hook up the form's Load event handler and then add // this code to the form's class: // You need these delegates in order to display text from a thread // other than the form's thread. See the HandleCallback // procedure for more information. private delegate void DisplayInfoDelegate(string Text); private delegate void DisplayReaderDelegate(XmlReader reader); - + private bool isExecuting; - - // This example maintains the connection object + + // This example maintains the connection object // externally, so that it is available for closing. private SqlConnection connection; - + public Form1() { InitializeComponent(); } - + private string GetConnectionString() { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. + // 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"; } - + private void DisplayStatus(string Text) { this.label1.Text = Text; } - + private void ClearProductInfo() { // Clear the list box. this.listBox1.Items.Clear(); } - + private void DisplayProductInfo(XmlReader reader) { // Display the data within the reader. @@ -1845,7 +1845,7 @@ Next, compile and execute the following: } DisplayStatus("Ready"); } - + private void Form1_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e) { @@ -1856,7 +1856,7 @@ Next, compile and execute the following: e.Cancel = true; } } - + private void button1_Click(object sender, System.EventArgs e) { if (isExecuting) @@ -1873,27 +1873,27 @@ Next, compile and execute the following: ClearProductInfo(); DisplayStatus("Connecting..."); connection = new SqlConnection(GetConnectionString()); - - // To emulate a long-running query, wait for + + // To emulate a long-running query, wait for // a few seconds before working with the data. string commandText = "WAITFOR DELAY '00:00:03';" + "SELECT Name, ListPrice FROM Production.Product " + "WHERE ListPrice < 100 " + "FOR XML AUTO, XMLDATA"; - + command = new SqlCommand(commandText, connection); connection.Open(); - + DisplayStatus("Executing..."); isExecuting = true; - // Although it is not required that you pass the - // SqlCommand object as the second parameter in the + // Although it is not required that you pass the + // SqlCommand object as the second parameter in the // BeginExecuteXmlReader call, doing so makes it easier // to call EndExecuteXmlReader in the callback procedure. AsyncCallback callback = new AsyncCallback(HandleCallback); command.BeginExecuteXmlReader(callback, command); - + } catch (Exception ex) { @@ -1906,7 +1906,7 @@ Next, compile and execute the following: } } } - + private void HandleCallback(IAsyncResult result) { try @@ -1916,34 +1916,34 @@ Next, compile and execute the following: // of the IAsyncResult parameter. SqlCommand command = (SqlCommand)result.AsyncState; XmlReader reader = command.EndExecuteXmlReader(result); - + // You may not interact with the form and its contents // from a different thread, and this callback procedure // is all but guaranteed to be running from a different thread - // than the form. - + // than the form. + // Instead, you must call the procedure from the form's thread. // One simple way to accomplish this is to call the Invoke // method of the form, which calls the delegate you supply - // from the form's thread. + // from the form's thread. DisplayReaderDelegate del = new DisplayReaderDelegate(DisplayProductInfo); this.Invoke(del, reader); - + } catch (Exception ex) { - // Because you are now running code in a separate thread, + // Because you are now running code in a separate thread, // if you do not handle the exception here, none of your other - // code catches the exception. Because none of + // code catches the exception. Because none of // your code is on the call stack in this thread, there is nothing - // higher up the stack to catch the exception if you do not - // handle it here. You can either log the exception or - // invoke a delegate (as in the non-error case in this + // higher up the stack to catch the exception if you do not + // handle it here. You can either log the exception or + // invoke a delegate (as in the non-error case in this // example) to display the error on the form. In no case // can you simply display the error without executing a delegate - // as in the try block here. - - // You can create the delegate instance as you + // as in the try block here. + + // You can create the delegate instance as you // invoke it, like this: this.Invoke(new DisplayInfoDelegate(DisplayStatus), String.Format("Ready(last error: {0}", ex.Message)); @@ -1957,7 +1957,7 @@ Next, compile and execute the following: } } } - + private void Form1_Load(object sender, System.EventArgs e) { this.button1.Click += new System.EventHandler(this.button1_Click); @@ -2031,22 +2031,22 @@ Next, compile and execute the following: using System.Data; using System.Threading; using Microsoft.Data.SqlClient; - + class Program { private static SqlCommand m_rCommand; - + public static SqlCommand Command { get { return m_rCommand; } set { m_rCommand = value; } } - + public static void Thread_Cancel() { Command.Cancel(); } - + static void Main() { string connectionString = GetConnectionString(); @@ -2055,7 +2055,7 @@ Next, compile and execute the following: using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); - + Command = connection.CreateCommand(); Command.CommandText = "DROP TABLE TestCancel"; try @@ -2063,19 +2063,19 @@ Next, compile and execute the following: Command.ExecuteNonQuery(); } catch { } - + Command.CommandText = "CREATE TABLE TestCancel(co1 int, co2 char(10))"; Command.ExecuteNonQuery(); Command.CommandText = "INSERT INTO TestCancel VALUES (1, '1')"; Command.ExecuteNonQuery(); - + Command.CommandText = "SELECT * FROM TestCancel"; SqlDataReader reader = Command.ExecuteReader(); - + Thread rThread2 = new Thread(new ThreadStart(Thread_Cancel)); rThread2.Start(); rThread2.Join(); - + reader.Read(); System.Console.WriteLine(reader.FieldCount); reader.Close(); @@ -2088,7 +2088,7 @@ Next, compile and execute the following: } static private string GetConnectionString() { - // To avoid storing the connection string in your code, + // To avoid storing the connection string in your code, // you can retrieve it from a configuration file. return "Data Source=(local);Initial Catalog=AdventureWorks;" + "Integrated Security=SSPI"; @@ -2528,7 +2528,7 @@ If the option is enabled and a parameter with Direction Output or InputOutput is Although the returns no 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 number of rows affected by the command. For all other types of statements, the return value is -1. When a trigger exists on a table being inserted or updated, the return value includes the number of rows affected by both the insert or update operation and the number of rows affected by the trigger or triggers. When SET NOCOUNT ON is set on the connection (before or as part of executing the command, or as part of a trigger initiated by the execution of the command) the rows affected by individual statements stop contributing to the count of rows affected that is returned by this method. If no statements are detected that contribute to the count, the return value is -1. If a rollback occurs, the return value is also -1. + For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. For all other types of statements, the return value is -1. When a trigger exists on a table being inserted or updated, the return value includes the number of rows affected by both the insert or update operation and the number of rows affected by the trigger or triggers. When SET NOCOUNT ON is set on the connection (before or as part of executing the command, or as part of a trigger initiated by the execution of the command) the rows affected by individual statements stop contributing to the count of rows affected that is returned by this method. If no statements are detected that contribute to the count, the return value is -1. If a rollback occurs, the return value is also -1. @@ -2540,7 +2540,7 @@ If the option is enabled and a parameter with Direction Output or InputOutput is using System; using System.Data; using Microsoft.Data.SqlClient; - + namespace SqlCommandCS { class Program @@ -2762,7 +2762,7 @@ The following example creates a , and using System; using System.Data; using Microsoft.Data.SqlClient; - + class Program { static void Main() @@ -2772,7 +2772,7 @@ The following example creates a , and 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)) @@ -3076,7 +3076,7 @@ For more information about asynchronous programming in the .NET Framework Data P using System; using System.Data; using Microsoft.Data.SqlClient; - + public class Sample { public void CreateSqlCommand(string queryString, SqlConnection connection) @@ -3218,7 +3218,7 @@ For more information about asynchronous programming in the .NET Framework Data P using System; using System.Data; using Microsoft.Data.SqlClient; - + private static void CreateXMLReader(string queryString, string connectionString) { using (SqlConnection connection = new SqlConnection(connectionString)) @@ -3474,9 +3474,9 @@ The blocking connection simulates a situation like a command still running in th ### 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)] @@ -3535,7 +3535,7 @@ The following example demonstrates how to create a . -For vector data types, the property is ignored. The size of the vector is inferred from the of type . +For vector data types, the property is ignored. The size of the vector is inferred from the of type . Prior to Visual Studio 2010, threw an exception. Beginning in Visual Studio 2010, this method does not throw an exception. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDataAdapter.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDataAdapter.xml index 7a2f32a0bf..468c5046a6 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlDataAdapter.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDataAdapter.xml @@ -72,7 +72,7 @@ The following example uses the , , , , , , , , , , , , , , , , , , , The values in the are moved to the parameter values. - The event is raised. + 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. + The event is raised. is called. @@ -853,55 +853,55 @@ The following example uses the , , , The values in the are moved to the parameter values. - The event is raised. + 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. + The event is raised. is called. @@ -966,55 +966,55 @@ The following example uses the , , , method retur - Gets the value of the specified column as a . + Gets the value of the specified column as a . - A object representing the column at the given ordinal. + A object representing the column at the given ordinal. The index passed was outside the range of 0 to - 1 @@ -979,7 +979,7 @@ The method retur An attempt was made to read or access columns in a closed . - The retrieved data is not compatible with the type. + The retrieved data is not compatible with the type. No conversions are performed; therefore, the data retrieved must already be a vector value, or an exception is generated. @@ -1193,7 +1193,7 @@ The method retur 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. - + Trying to read a column that does not exist. @@ -1282,21 +1282,21 @@ The method retur // enough for all the columns. Object[] values = new Object[reader.FieldCount]; int fieldCount = reader.GetValues(values); - + Console.WriteLine("reader.GetValues retrieved {0} columns.", fieldCount); for (int i = 0; i < fieldCount; i++) { Console.WriteLine(values[i]); } - + Console.WriteLine(); - - // Now repeat, using an array that may contain a different + + // Now repeat, using an array that may contain a different // number of columns than the original data. This should work correctly, - // whether the size of the array is larger or smaller than + // whether the size of the array is larger or smaller than // the number of columns. - + // Attempt to retrieve three columns of data. values = new Object[3]; fieldCount = reader.GetValues(values); @@ -1334,7 +1334,7 @@ The method retur 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. - + Trying to read a column that does not exist. @@ -1394,7 +1394,7 @@ The method retur using System; using System.Data; using Microsoft.Data.SqlClient; - + class Program { static void Main(string[] args) @@ -1448,7 +1448,7 @@ The method retur 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. - + Trying to read a column that does not exist. From 14bbf42e44ae35eaa95decf08c9b8863af2f617e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:29:06 +0000 Subject: [PATCH 015/298] Merge SqlType resource methodology, removed unused strings (#3733) --- .../src/Microsoft.Data.SqlClient.csproj | 3 - .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 - .../src/Microsoft/Data/Common/AdapterUtil.cs | 3 + .../src/Microsoft/Data/SqlClient/SqlBuffer.cs | 4 +- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 11 ++- .../Microsoft/Data/SqlTypes/SQLResource.cs | 77 ------------------- .../src/Microsoft/Data/SqlTypes/SqlVector.cs | 4 +- .../Microsoft/Data/SqlTypes/SqlVectorTest.cs | 4 +- 8 files changed, 19 insertions(+), 90 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SQLResource.cs 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 7145dbac28..c2fdb27f3f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -1002,9 +1002,6 @@ Microsoft\Data\SqlTypes\SqlJson.cs - - Microsoft\Data\SQLTypes\SQLResource.cs - Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs 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 8408dabc0d..eb14ccc985 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -979,9 +979,6 @@ Microsoft\Data\SqlTypes\SqlVector.cs - - Microsoft\Data\SqlTypes\SQLResource.cs - Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs 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 e2a84bad04..7868c8c876 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -378,6 +378,9 @@ internal static AuthenticationException SSLCertificateAuthenticationException(st internal static ArgumentOutOfRangeException NotSupportedEnumerationValue(Type type, string value, string method) => ArgumentOutOfRange(StringsHelper.GetString(Strings.ADP_NotSupportedEnumerationValue, type.Name, value, method), type.Name); + internal static ArgumentOutOfRangeException InvalidArraySize(string parameterName) => + ArgumentOutOfRange(StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage), parameterName); + internal static void CheckArgumentNull(object value, string parameterName) { if (value is null) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs index 385a49c802..0ec34003f7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs @@ -271,11 +271,11 @@ internal decimal Decimal } catch (OverflowException) { - throw new OverflowException(SQLResource.ConversionOverflowMessage); + throw SQL.ConversionOverflow(); } } } - throw new OverflowException(SQLResource.ConversionOverflowMessage); + throw SQL.ConversionOverflow(); } return new decimal(_value._numericInfo._data1, _value._numericInfo._data2, _value._numericInfo._data3, !_value._numericInfo._positive, _value._numericInfo._scale); } 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 8f0d7fcba0..ab2261f0e9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -830,9 +830,14 @@ internal static Exception UDTUnexpectedResult(string exceptionText) return ADP.TypeLoad(StringsHelper.GetString(Strings.SQLUDT_Unexpected, exceptionText)); } + internal static Exception ConversionOverflow() + { + return new OverflowException(StringsHelper.GetString(Strings.SqlMisc_ConversionOverflowMessage)); + } + internal static Exception DateTimeOverflow() { - return new OverflowException(SqlTypes.SQLResource.DateTimeOverflowMessage); + return new OverflowException(StringsHelper.GetString(Strings.SqlMisc_DateTimeOverflowMessage)); } // @@ -2285,6 +2290,10 @@ internal static string ExRoutingDestination() { return StringsHelper.GetString(Strings.SQL_ExRoutingDestination); } + internal static string NullString() + { + return StringsHelper.GetString(Strings.SqlMisc_NullString); + } } /// diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SQLResource.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SQLResource.cs deleted file mode 100644 index 327d182d03..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SQLResource.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. - -namespace Microsoft.Data.SqlTypes -{ - - using System; - using Microsoft.Data; - - internal sealed class SQLResource - { - - private SQLResource() { /* prevent utility class from being instantiated*/ } - - internal static readonly String NullString = StringsHelper.GetString(Strings.SqlMisc_NullString); - - internal static readonly String MessageString = StringsHelper.GetString(Strings.SqlMisc_MessageString); - - internal static readonly String ArithOverflowMessage = StringsHelper.GetString(Strings.SqlMisc_ArithOverflowMessage); - - internal static readonly String DivideByZeroMessage = StringsHelper.GetString(Strings.SqlMisc_DivideByZeroMessage); - - internal static readonly String NullValueMessage = StringsHelper.GetString(Strings.SqlMisc_NullValueMessage); - - internal static readonly String TruncationMessage = StringsHelper.GetString(Strings.SqlMisc_TruncationMessage); - - internal static readonly String DateTimeOverflowMessage = StringsHelper.GetString(Strings.SqlMisc_DateTimeOverflowMessage); - - internal static readonly String ConcatDiffCollationMessage = StringsHelper.GetString(Strings.SqlMisc_ConcatDiffCollationMessage); - - internal static readonly String CompareDiffCollationMessage = StringsHelper.GetString(Strings.SqlMisc_CompareDiffCollationMessage); - - internal static readonly String InvalidFlagMessage = StringsHelper.GetString(Strings.SqlMisc_InvalidFlagMessage); - - internal static readonly String NumeToDecOverflowMessage = StringsHelper.GetString(Strings.SqlMisc_NumeToDecOverflowMessage); - - internal static readonly String ConversionOverflowMessage = StringsHelper.GetString(Strings.SqlMisc_ConversionOverflowMessage); - - internal static readonly String InvalidDateTimeMessage = StringsHelper.GetString(Strings.SqlMisc_InvalidDateTimeMessage); - - internal static readonly String TimeZoneSpecifiedMessage = StringsHelper.GetString(Strings.SqlMisc_TimeZoneSpecifiedMessage); - - internal static readonly String InvalidArraySizeMessage = StringsHelper.GetString(Strings.SqlMisc_InvalidArraySizeMessage); - - internal static readonly String InvalidPrecScaleMessage = StringsHelper.GetString(Strings.SqlMisc_InvalidPrecScaleMessage); - - internal static readonly String FormatMessage = StringsHelper.GetString(Strings.SqlMisc_FormatMessage); - - internal static readonly String NotFilledMessage = StringsHelper.GetString(Strings.SqlMisc_NotFilledMessage); - - internal static readonly String AlreadyFilledMessage = StringsHelper.GetString(Strings.SqlMisc_AlreadyFilledMessage); - - internal static readonly String ClosedXmlReaderMessage = StringsHelper.GetString(Strings.SqlMisc_ClosedXmlReaderMessage); - - internal static String InvalidOpStreamClosed(String method) - { - return StringsHelper.GetString(Strings.SqlMisc_InvalidOpStreamClosed, method); - } - - internal static String InvalidOpStreamNonWritable(String method) - { - return StringsHelper.GetString(Strings.SqlMisc_InvalidOpStreamNonWritable, method); - } - - internal static String InvalidOpStreamNonReadable(String method) - { - return StringsHelper.GetString(Strings.SqlMisc_InvalidOpStreamNonReadable, method); - } - - internal static String InvalidOpStreamNonSeekable(String method) - { - return StringsHelper.GetString(Strings.SqlMisc_InvalidOpStreamNonSeekable, method); - } - } // SqlResource - -} // namespace System diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs index 14e790e977..2e83b8daff 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs @@ -41,7 +41,7 @@ private SqlVector(int length) { if (length < 0) { - throw ADP.ArgumentOutOfRange(nameof(length), SQLResource.InvalidArraySizeMessage); + throw ADP.InvalidArraySize(nameof(length)); } (_elementType, _elementSize) = GetTypeFieldsOrThrow(); @@ -92,7 +92,7 @@ internal string GetString() { if (IsNull) { - return SQLResource.NullString; + return SQLMessage.NullString(); } return JsonSerializer.Serialize(Memory); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs index adca0e2b99..1b34c66298 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs @@ -38,7 +38,7 @@ public void Construct_Length() // 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()); + Assert.Equal(SQLMessage.NullString(), vec.GetString()); var ivec = vec as ISqlVector; Assert.Equal(0x00, ivec.ElementType); @@ -58,7 +58,7 @@ public void Construct_WithLengthZero() // 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()); + Assert.Equal(SQLMessage.NullString(), vec.GetString()); var ivec = vec as ISqlVector; Assert.Equal(0x00, ivec.ElementType); From 938d245026622ad80cabbf3ebde17565fe45d157 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 30 Mar 2026 08:12:11 -0300 Subject: [PATCH 016/298] Changed SBOM package version to use build number (#4095) --- eng/pipelines/onebranch/sqlclient-non-official.yml | 2 +- eng/pipelines/onebranch/sqlclient-official.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index 25dbe98097..d4b62ac5e8 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -154,7 +154,7 @@ extends: sbom: enabled: true packageName: Microsoft.Data.SqlClient - packageVersion: $(mdsPackageVersion) + packageVersion: $(Build.BuildNumber) policheck: enabled: true break: true diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index 1754918116..93287c3c51 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -179,7 +179,7 @@ extends: sbom: enabled: true packageName: Microsoft.Data.SqlClient - packageVersion: $(mdsPackageVersion) + packageVersion: $(Build.BuildNumber) policheck: enabled: true break: true From 2c37eb979b7be6616700832637788d941aa3a4cb Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Tue, 31 Mar 2026 15:39:36 -0500 Subject: [PATCH 017/298] Merge | Official Builds From Common Project (#4068) * Drop compound-* from onebranch step files for my own sanity * Consolidate compound-publish-symbols and publish-symbols-step into publish-symbols-step. * Rename code-analyze-step to roslyn-analyzers-csproj-step (for later changes) * Add mds-specific job/steps * Swap in build-signed-mds-package-job.yml for build-signed-sqlclient-package-job.yml * Remove dead job/steps * Fix folder path for DLL signing steps. * Addressing comments, round 1 * Addressing comments, round 2 Not sure how the PackBuild target got lost, but I brought it back * Add the new parameters to the csproj build job * Use variables artifact names in release stage * Two more quick fixes as per comments * ESRP searches recursively? * Fine, I give up, I will put the package output in PACK_OUTPUT. * Reinstate job-level apiscan parameter assignment * Wire isOfficial to MDS build job after rebase * Rework versioning logic and move package version calculation (outside of pipelines) entirely to MdsVersions.props * Add build number to PackMds target call in official pipeline * Remove assembly "file" versions as it is not necessary. * CI pipelines to automatic versioning system. * Rework the package version for CI to match the package version for OneBranch. This band-aid avoids completely messing with the CI properties. * Assembly version = major.0.0.0 File version = major.minor.patch.build Package version = major.minor.patch[-suffixbuildnumber] * Comment that Malcolm is holding the build up for. * Can I please be done with this PR now? --- build2.proj | 78 +++++- .../templates/steps/ci-project-build-step.yml | 6 +- .../libraries/ci-build-variables.yml | 10 +- .../jobs/build-signed-csproj-package-job.yml | 27 +- .../jobs/build-signed-mds-package-job.yml | 182 ++++++++++++ .../build-signed-sqlclient-package-job.yml | 145 ---------- .../jobs/validate-signed-package-job.yml | 29 +- .../onebranch/stages/build-stages.yml | 27 +- .../onebranch/stages/release-stages.yml | 12 +- ...ld-all-configurations-signed-dlls-step.yml | 58 ---- ...-csproj-step.yml => build-csproj-step.yml} | 2 +- .../onebranch/steps/build-mds-step.yml | 41 +++ .../steps/compound-nuget-pack-step.yml | 86 ------ .../steps/compound-publish-symbols-step.yml | 162 ----------- .../steps/copy-apiscan-files-mds-step.yml | 39 +++ .../steps/esrp-code-signing-step.yml | 168 ----------- ...ing-step.yml => esrp-dll-signing-step.yml} | 30 +- ...g-step.yml => esrp-nuget-signing-step.yml} | 44 +-- ...k-csproj-step.yml => pack-csproj-step.yml} | 0 .../onebranch/steps/pack-mds-step.yml | 42 +++ .../onebranch/steps/publish-symbols-step.yml | 264 ++++++++++-------- ...p.yml => roslyn-analyzers-csproj-step.yml} | 0 .../steps/roslyn-analyzers-mds-step.yml | 40 +++ .../onebranch/variables/common-variables.yml | 3 +- .../variables/onebranch-variables.yml | 2 +- .../MdsVersions.props | 102 +++++-- .../Microsoft.Data.SqlClient.csproj | 2 +- .../ref/Microsoft.Data.SqlClient.csproj | 2 +- .../src/Microsoft.Data.SqlClient.csproj | 2 +- tools/targets/GenerateThisAssemblyCs.targets | 4 +- 30 files changed, 751 insertions(+), 858 deletions(-) create mode 100644 eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml delete mode 100644 eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml delete mode 100644 eng/pipelines/onebranch/steps/build-all-configurations-signed-dlls-step.yml rename eng/pipelines/onebranch/steps/{compound-build-csproj-step.yml => build-csproj-step.yml} (95%) create mode 100644 eng/pipelines/onebranch/steps/build-mds-step.yml delete mode 100644 eng/pipelines/onebranch/steps/compound-nuget-pack-step.yml delete mode 100644 eng/pipelines/onebranch/steps/compound-publish-symbols-step.yml create mode 100644 eng/pipelines/onebranch/steps/copy-apiscan-files-mds-step.yml delete mode 100644 eng/pipelines/onebranch/steps/esrp-code-signing-step.yml rename eng/pipelines/onebranch/steps/{compound-esrp-dll-signing-step.yml => esrp-dll-signing-step.yml} (78%) rename eng/pipelines/onebranch/steps/{compound-esrp-nuget-signing-step.yml => esrp-nuget-signing-step.yml} (59%) rename eng/pipelines/onebranch/steps/{compound-pack-csproj-step.yml => pack-csproj-step.yml} (100%) create mode 100644 eng/pipelines/onebranch/steps/pack-mds-step.yml rename eng/pipelines/onebranch/steps/{code-analyze-step.yml => roslyn-analyzers-csproj-step.yml} (100%) create mode 100644 eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml diff --git a/build2.proj b/build2.proj index b9b08f9a4a..e2f760af75 100644 --- a/build2.proj +++ b/build2.proj @@ -25,6 +25,25 @@ -p:BuildNumber=$(BuildNumber) + + + + -p:BuildSuffix=$(BuildSuffix) + + nuget + + + true + BuildMds + $(BuildNumberArgument) - $(PacakgeVersionMdsArgument) + $(BuildSuffixArgument) + $(PackageVersionMdsArgument) $(ReferenceTypeArgument) @@ -316,7 +357,8 @@ $(BuildNumberArgument) - $(PacakgeVersionMdsArgument) + $(BuildSuffixArgument) + $(PackageVersionMdsArgument) $(ReferenceTypeArgument) @@ -341,6 +383,7 @@ $(BuildNumberArgument) + $(BuildSuffixArgument) $(PackageVersionMdsArgument) @@ -367,6 +410,7 @@ $(BuildNumberArgument) + $(BuildSuffixArgument) $(PackageVersionMdsArgument) @@ -388,13 +432,11 @@ automatically build before packaging. Instead, we rely on target dependency to handle building the same binaries that will be packaged. --> - + - @@ -408,13 +450,37 @@ + + + + "$(DotnetPath)dotnet" msbuild "$(MdsProjectPath)" + -nologo + -verbosity:quiet + -getProperty:MdsPackageVersion + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionMdsArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(GetMdsPackageVersionCommand), "\s+", " ")) + + + + + + + + <_EvaluatedMdsPackageVersion>$([System.Text.RegularExpressions.Regex]::Replace($(_EvaluatedMdsPackageVersion), "\s", "")) $([System.Text.RegularExpressions.Regex]::Replace($(CommitId), "\s", "")) "$(NugetPath)" pack "$(MdsNuspecPath)" -Symbols -SymbolPackageFormat snupkg - -Version "$(PackageVersionMds)" + -Version "$(_EvaluatedMdsPackageVersion)" -OutputDirectory "$(MdsArtifactRoot)/$(ReferenceType)-$(Configuration)" -properties "COMMITID=$(CommitId);Configuration=$(Configuration);ReferenceType=$(ReferenceType);AbstractionsPackageVersion=$(PackageVersionAbstractions);LoggingPackageVersion=$(PackageVersionLogging)" 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 d43222bfd1..d2604e0b3d 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -60,7 +60,9 @@ parameters: type: string default: $(loggingPackageVersion) - # Necessary to build AKV Provider when referenceType is Package. Ignored when referenceType is Project. + # Package version to use for MDS package. Because we explicitly provide this version, the + # assembly version will be generated based on this package version and the build number. + # See MdsVersions.props for details on how this works. - name: mdsPackageVersion type: string default: $(mdsPackageVersion) @@ -84,7 +86,7 @@ steps: msbuildArguments: -t:BuildMds -p:ReferenceType=${{ parameters.referenceType }} - -p:BuildNumber=${{ parameters.buildNumber }} + -p:BuildNumber=${{ parameters.assemblyBuildNumber }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 2779277678..027501df0f 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -54,13 +54,13 @@ variables: - name: loggingPackageVersion value: 1.0.0.$(Build.BuildNumber)-ci - # MDS library assembly file version - - name: mdsAssemblyFileVersion - value: 7.0.0.$(assemblyBuildNumber) - # MDS library NuGet package version + # NOTE: This differs from the other structures! MdsVersions.props will deconstruct a provided + # package version and build an assembly version from it. If the build number is included + # before the "-" the build number will be appended again, generating an invalid file version. + # @TODO: This is a band-aid to ensure that CI builds until we can centralize versioning. - name: mdsPackageVersion - value: 7.0.0.$(Build.BuildNumber)-ci + value: 7.0.0-ci$(AssemblyBuildNumber) # Local NuGet feed directory where downloaded pipeline artifacts are placed. # NuGet.config references this as a local package source for restore. diff --git a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml index e257386918..653d9419bc 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml @@ -134,7 +134,7 @@ jobs: - template: /eng/pipelines/steps/install-dotnet.yml@self # Perform Roslyn analysis before building, since this step will clobber build output. - - template: /eng/pipelines/onebranch/steps/code-analyze-step.yml@self + - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml@self parameters: msBuildArguments: >- -t:$(buildTarget) @@ -143,7 +143,7 @@ jobs: ${{ parameters.versionProperties }} # Build the package, producing DLLs only (no NuGet package yet). - - template: /eng/pipelines/onebranch/steps/compound-build-csproj-step.yml@self + - template: /eng/pipelines/onebranch/steps/build-csproj-step.yml@self parameters: buildTarget: $(buildTarget) buildConfiguration: ${{ parameters.buildConfiguration }} @@ -151,7 +151,7 @@ jobs: - ${{ if eq(parameters.isOfficial, true) }}: # ESRP sign the DLLs. - - template: /eng/pipelines/onebranch/steps/compound-esrp-dll-signing-step.yml@self + - template: /eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml@self parameters: appRegistrationClientId: ${{ parameters.appRegistrationClientId }} appRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} @@ -182,7 +182,7 @@ jobs: flattenFolders: false # Pack the signed DLLs into NuGet package (NoBuild=true). - - template: /eng/pipelines/onebranch/steps/compound-pack-csproj-step.yml@self + - template: /eng/pipelines/onebranch/steps/pack-csproj-step.yml@self parameters: packTarget: $(packTarget) buildConfiguration: ${{ parameters.buildConfiguration }} @@ -190,7 +190,7 @@ jobs: - ${{ if eq(parameters.isOfficial, true) }}: # ESRP sign the NuGet package. - - template: /eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml@self + - template: /eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml@self parameters: appRegistrationClientId: ${{ parameters.appRegistrationClientId }} appRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} @@ -198,11 +198,22 @@ jobs: authSignCertName: ${{ parameters.authSignCertName }} esrpClientId: ${{ parameters.esrpClientId }} esrpConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} - pattern: ${{ parameters.packageFullName }}.*nupkg + searchPath: $(PACK_OUTPUT) + searchPattern: '${{ parameters.packageFullName }}.*nupkg' # Publish symbols to servers + # @TODO: Get these parameters from variables/libraries - ${{ if eq(parameters.publishSymbols, true) }}: - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self parameters: - packageFullName: ${{ parameters.packageFullName }} - packageVersion: ${{ parameters.packageVersion }} + artifactName: '${{ parameters.packageFullName }}_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.packageVersion }}_$(System.TimelineId)' + azureSubscription: 'Symbols publishing Workload Identity federation service-ADO.Net' + packageName: '${{ parameters.packageFullName }}' + publishProjectName: 'Microsoft.Data.SqlClient.SNI' # This is used for all SqlClient packages. Don't know why, but it is. + publishServer: '$(SymbolServer)' + publishToInternal: 'true' + publishToPublic: 'true' + publishTokenUri: '$(SymbolTokenUri)' + searchPattern: '**/${{ parameters.packageFullName }}*.pdb' + uploadAccount: 'SqlClientDrivers' + version: '${{ parameters.packageVersion }}' diff --git a/eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml new file mode 100644 index 0000000000..4464a3a9f1 --- /dev/null +++ b/eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml @@ -0,0 +1,182 @@ +################################################################################# +# 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. # +################################################################################# + +# Job that performs a build of Microsoft.Data.SqlClient using the build2.proj file. + +parameters: + - name: apiScanDllPath + type: string + + - name: apiScanPdbPath + type: string + + # Whether this build is for an "official" OneBranch pipeline. This is used to enable ESRP signing + # on the artifacts of this job. + - name: isOfficial + type: boolean + + - 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 + + # Package Parameters + - name: abstractionsArtifactName + type: string + + - name: abstractionsPackageVersion + type: string + + - name: loggingArtifactName + type: string + + - name: loggingPackageVersion + type: string + + - name: mdsAssemblyFileVersion + type: string + + - name: mdsPackageVersion + type: string + +jobs: + - job: build_package_Mds + displayName: 'Build Microsoft.Data.SqlClient' + pool: + type: windows + + variables: + ob_outputDirectory: '$(PACK_OUTPUT)' + ob_sdl_apiscan_softwareFolder: ${{ parameters.apiScanDllPath }} + ob_sdl_apiscan_symbolsFolder: ${{ parameters.apiScanPdbPath }} + ob_sdl_apiscan_softwarename: 'Microsoft.Data.SqlClient' + ob_sdl_apiscan_versionNumber: ${{ parameters.mdsAssemblyFileVersion }} + + steps: + # Dump environment and parameters + - template: /eng/pipelines/onebranch/steps/script-output-environment-variables-step.yml@self + + - powershell: | + $jsonParams = '${{ convertToJson(parameters) }}' -replace '\\', '\\' + $jsonParams | ConvertFrom-Json | Format-List + displayName: 'Output Job Parameters' + + # Download Abstractions and Logging packages from previous stages into /packages/ so they + # are available via the local NuGet feed when restoring. + # @TODO: With the new build2.proj pack targets, this technically is not necessary. + - task: DownloadPipelineArtifact@2 + displayName: Download Microsoft.Data.SqlClient.Extensions.Abstractions Artifact + inputs: + artifactName: '${{ parameters.abstractionsArtifactName }}' + targetPath: '$(REPO_ROOT)/packages' + + - task: DownloadPipelineArtifact@2 + displayName: Download Microsoft.Data.SqlClient.Extensions.Logging Artifact + inputs: + artifactName: '${{ parameters.loggingArtifactName }}' + targetPath: '$(REPO_ROOT)/packages' + + # Install the .NET SDK + - template: /eng/pipelines/steps/install-dotnet.yml@self + + # Perform analysis before building, since this step will clobber build output + - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml@self + parameters: + abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' + loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' + mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + + # Perform the actual build + - template: /eng/pipelines/onebranch/steps/build-mds-step.yml@self + parameters: + abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' + loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' + mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + + # Copy the built DLLs and PDBs to the APIScan output folder for APIScanning post-build + - template: /eng/pipelines/onebranch/steps/copy-apiscan-files-mds-step.yml@self + parameters: + dllPath: '${{ parameters.apiScanDllPath }}' + pdbPath: '${{ parameters.apiScanPdbPath }}' + referenceType: 'Package' + + # Sign the DLLs + - ${{ if parameters.isOfficial }}: + - template: /eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml@self + parameters: + appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + authAkvName: '${{ parameters.signingAuthAkvName }}' + authSignCertName: '${{ parameters.signingAuthSignCertName }}' + esrpClientId: '${{ parameters.signingEsrpClientId }}' + esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + pattern: 'Microsoft.Data.SqlClient*.dll' + + # Package the build output into a NuGet package + - template: /eng/pipelines/onebranch/steps/pack-mds-step.yml + parameters: + abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' + loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' + mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + + # Sign the NuGet packages + - ${{ if parameters.isOfficial }}: + - template: /eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml@self + parameters: + appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + authAkvName: '${{ parameters.signingAuthAkvName }}' + authSignCertName: '${{ parameters.signingAuthSignCertName }}' + esrpClientId: '${{ parameters.signingEsrpClientId }}' + esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + searchPath: '$(PACK_OUTPUT)' + searchPattern: 'Microsoft.Data.SqlClient.*nupkg' + + - ${{ if parameters.publishSymbols }}: + - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self + parameters: + artifactName: 'Microsoft.Data.SqlClient_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.mdsPackageVersion }}_$(System.TimelineId)' + azureSubscription: '${{ parameters.symbolsAzureSubscription }}' + packageName: 'Microsoft.Data.SqlClient' + publishProjectName: '${{ parameters.symbolsPublishProjectName }}' + publishServer: '${{ parameters.symbolsPublishServer }}' + publishToInternal: true + publishToPublic: true + publishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + searchPattern: '**/Microsoft.Data.SqlClient*.pdb' # @TODO: This seems very heavy + uploadAccount: '${{ parameters.symbolsUploadAccount }}' + version: '${{ parameters.mdsPackageVersion }}' diff --git a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml deleted file mode 100644 index f3033b07f0..0000000000 --- a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml +++ /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. # -################################################################################# - -# This file is only included in MDS OneBranch Official pipelines. - -parameters: - # True to publish symbols to public and private feeds after the build completes. - - name: publishSymbols - type: boolean - - # True if this is a preview build, which uses the preview version numbers from - # common-variables.yml. - - name: isPreview - type: boolean - - # True to enable ESRP malware scanning and code signing steps, which should not - # be run on non-official pipelines as they access production resources. - - name: isOfficial - type: boolean - -jobs: - - job: build_package_SqlClient - displayName: "Build Microsoft.Data.SqlClient" - pool: - type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs - - variables: - ob_outputDirectory: $(PACK_OUTPUT) - # APIScan configuration for this Extension package - ob_sdl_apiscan_enabled: true - ob_sdl_apiscan_softwareFolder: $(Build.SourcesDirectory)/apiScan/SqlClient/dlls - ob_sdl_apiscan_symbolsFolder: $(Build.SourcesDirectory)/apiScan/SqlClient/pdbs - ob_sdl_apiscan_softwarename: Microsoft.Data.SqlClient - ob_sdl_apiscan_versionNumber: $(assemblyBuildNumber) - - ${{ if parameters.isPreview }}: - abstractionsPackageVersion: $(abstractionsPackagePreviewVersion) - loggingPackageVersion: $(loggingPackagePreviewVersion) - mdsPackageVersion: $(mdsPackagePreviewVersion) - - steps: - - script: SET - displayName: "Print Environment Variables" - - # Download the Abstractions and Logging packages from the previous stage into - # packages/ so that they're available via the local NuGet feed when restoring MDS. - # MDS depends on both Extensions.Abstractions and Internal.Logging. - - task: DownloadPipelineArtifact@2 - displayName: Download Abstractions Package - inputs: - artifactName: $(abstractionsArtifactsName) - targetPath: $(Build.SourcesDirectory)/packages - - - task: DownloadPipelineArtifact@2 - displayName: Download Logging Package - inputs: - artifactName: $(loggingArtifactsName) - targetPath: $(Build.SourcesDirectory)/packages - - # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self - - # Build our tooling, which is required by the analysis step below, but - # shouldn't be analyzed itself. - - task: MSBuild@1 - displayName: "Build Tooling" - inputs: - solution: "**/build.proj" - configuration: Release - msbuildArguments: -t:BuildTools - - # Perform analysis before building, since this step will clobber build output. - - template: /eng/pipelines/onebranch/steps/code-analyze-step.yml@self - - # Build MDS, producing signed DLLs. - - template: /eng/pipelines/onebranch/steps/build-all-configurations-signed-dlls-step.yml@self - parameters: - # These variables are sourced from common-variables.yml. - abstractionsAssemblyFileVersion: $(abstractionsAssemblyFileVersion) - abstractionsPackageVersion: $(abstractionsPackageVersion) - loggingAssemblyFileVersion: $(loggingAssemblyFileVersion) - loggingPackageVersion: $(loggingPackageVersion) - mdsAssemblyFileVersion: $(mdsAssemblyFileVersion) - mdsPackageVersion: $(mdsPackageVersion) - - - ${{ if eq(parameters.isOfficial, true) }}: - - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self - parameters: - artifactType: dll - sourceRoot: $(BUILD_OUTPUT) - dllPattern: "Microsoft.Data.SqlClient.dll" - - - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self - parameters: - artifactType: dll - sourceRoot: $(BUILD_OUTPUT) - dllPattern: "Microsoft.Data.SqlClient.resources.dll" - - - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self - parameters: - buildConfiguration: Release - displayName: "Create MDS NuGet Package" - generateSymbolsPackage: true - installNuget: false - nuspecPath: $(nuspecPath) - outputDirectory: $(PACK_OUTPUT) - packageVersion: $(mdsPackageVersion) - properties: "AbstractionsPackageVersion=$(abstractionsPackageVersion);LoggingPackageVersion=$(loggingPackageVersion)" - referenceType: Package - - - ${{ if eq(parameters.isOfficial, true) }}: - - template: /eng/pipelines/onebranch/steps/esrp-code-signing-step.yml@self - parameters: - artifactType: pkg - nupkgPattern: "Microsoft.Data.SqlClient.$(mdsPackageVersion).*nupkg" - - # Copy signed DLLs and PDBs to APIScan folders. - - task: CopyFiles@2 - displayName: Copy DLLs for APIScan - inputs: - SourceFolder: $(BUILD_OUTPUT)/Package/bin - Contents: "**/Microsoft.Data.SqlClient.dll" - TargetFolder: $(ob_sdl_apiscan_softwareFolder) - # We must preserve the folder structure since our C# projects may produce multiple - # identically named DLLs for different target frameworks (e.g. netstandard2.0, net5.0, - # etc.), and we need to keep those separate for APIScan to work correctly. - flattenFolders: false - - - task: CopyFiles@2 - displayName: Copy PDBs for APIScan - inputs: - SourceFolder: $(BUILD_OUTPUT)/Package/bin - Contents: "**/Microsoft.Data.SqlClient.pdb" - TargetFolder: $(ob_sdl_apiscan_symbolsFolder) - flattenFolders: false - - # Publish symbols to servers - - ${{ if eq(parameters.publishSymbols, true) }}: - - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self - parameters: - packageFullName: Microsoft.Data.SqlClient - packageVersion: $(mdsPackageVersion) diff --git a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml index 3b463d0b81..2a4bf08e97 100644 --- a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml @@ -12,7 +12,7 @@ parameters: - name: isPreview type: boolean - # True if this build is an official build. This will be used to gate some checks + # True if this build is an official build. This will be used to gate some checks # that only apply to official builds, such as signature verification. - name: isOfficial type: boolean @@ -210,7 +210,7 @@ jobs: } } displayName: 'Verify all DLLs unzipped match "expected" hierarchy' - + - ${{ if eq(parameters.isOfficial, true) }}: - powershell: | # Verify all dlls status are Valid @@ -235,6 +235,7 @@ jobs: # This will check each DLL's ProductVersion and FileVersion against # expected values. $failed = 0 + $expectedFileVersion = "$(mdsPackageVersion)".Split('-')[0] + ".$(assemblyBuildNumber)" foreach ( $pVersion in Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo ) { @@ -248,13 +249,13 @@ jobs: $failed = 1 } - if ($pVersion.FileVersion -eq '$(mdsAssemblyFileVersion)') + if ($pVersion.FileVersion -eq $expectedFileVersion) { Write-Host -ForegroundColor Green "Correct FileVersion detected for $($pVersion.FileName): $($pVersion.FileVersion)" } else { - Write-Host -ForegroundColor Red "Wrong FileVersion detected for $($pVersion.FileName); expected $(mdsAssemblyFileVersion); found: $($pVersion.FileVersion)" + Write-Host -ForegroundColor Red "Wrong FileVersion detected for $($pVersion.FileName); expected $expectedFileVersion; found: $($pVersion.FileVersion)" $failed = 1 } } @@ -266,23 +267,3 @@ jobs: Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object VersionInfo | Format-List displayName: 'Verify "File Version" matches expected values for DLLs' - - - powershell: | - # Check assembly versions. - # - # GOTCHA: This expects the Versions.props file having XML elements in a - # certain order. If the order changes, this check will fail! - # - # TODO: This also isn't checking the versions of the actual assemblies in - # the package, so it isn't terribly useful. - - [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" - $AssemblyFileVersion = $versionprops.Project.PropertyGroup[2].AssemblyFileVersion - $AssemblyVersion = $versionprops.Project.PropertyGroup[2].AssemblyVersion - - if($AssemblyFileVersion -eq $AssemblyVersion) - { - Write-Host AssemblyFileVersion: $AssemblyFileVersion should not be equal to: $AssemblyVersion - Exit -1 - } - displayName: 'Check "AssemblyFileVersion" is not same as "AssemblyVersion" in version.props' diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index fad5c9a699..738e91d26f 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -146,13 +146,30 @@ stages: dependsOn: build_abstractions jobs: - - template: /eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml@self + - template: /eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml@self parameters: - publishSymbols: ${{ parameters.publishSymbols }} - isPreview: ${{ parameters.isPreview }} + apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient/dlls' + apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient/pdbs' isOfficial: ${{ parameters.isOfficial }} - # TODO: This job should use the effective versions for Abstractions, Logging, - # SqlServer, and SqlClient. + publishSymbols: ${{ parameters.publishSymbols }} + signingAppRegistrationClientId: '$(AppRegistrationClientId)' + signingAppRegistrationTenantId: '$(AppRegistrationTenantId)' + signingAuthAkvName: '$(AuthAKVName)' + signingAuthSignCertName: '$(AuthSignCertName)' + signingEsrpClientId: '$(ESRPClientId)' + signingEsrpConnectedServiceName: '$(ESRPConnectedServiceName)' + symbolsAzureSubscription: 'Symbols publishing Workload Identity federation service-ADO.Net' + symbolsPublishProjectName: 'Microsoft.Data.SqlClient.SNI' + symbolsPublishServer: '$(SymbolServer)' + symbolsPublishTokenUri: '$(SymbolTokenUri)' + symbolsUploadAccount: 'SqlClientDrivers' + + abstractionsArtifactName: '$(abstractionsArtifactsName)' + abstractionsPackageVersion: '$(effectiveAbstractionsVersion)' + loggingArtifactName: '$(loggingArtifactsName)' + loggingPackageVersion: '$(effectiveLoggingVersion)' + mdsAssemblyFileVersion: '$(mdsAssemblyFileVersion)' + mdsPackageVersion: '$(effectiveSqlClientVersion)' - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self parameters: diff --git a/eng/pipelines/onebranch/stages/release-stages.yml b/eng/pipelines/onebranch/stages/release-stages.yml index 51eea6e5f0..a9810f1251 100644 --- a/eng/pipelines/onebranch/stages/release-stages.yml +++ b/eng/pipelines/onebranch/stages/release-stages.yml @@ -153,7 +153,7 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.SqlServer.Server - artifactName: drop_build_independent_build_package_SqlServer + artifactName: $(sqlServerArtifactsName) packagePath: Microsoft.SqlServer.Server.$(effectiveSqlServerVersion).nupkg nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} @@ -163,7 +163,7 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient.Internal.Logging - artifactName: drop_build_independent_build_package_Logging + artifactName: $(loggingArtifactsName) packagePath: Microsoft.Data.SqlClient.Internal.Logging.$(effectiveLoggingVersion).nupkg nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} @@ -173,7 +173,7 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient.Extensions.Abstractions - artifactName: drop_build_abstractions_build_package_Abstractions + artifactName: $(abstractionsArtifactsName) packagePath: Microsoft.Data.SqlClient.Extensions.Abstractions.$(effectiveAbstractionsVersion).nupkg nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} @@ -183,7 +183,7 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient - artifactName: drop_build_dependent_build_package_SqlClient + artifactName: $(sqlClientArtifactsName) packagePath: Microsoft.Data.SqlClient.$(effectiveSqlClientVersion).nupkg nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} @@ -193,7 +193,7 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient.Extensions.Azure - artifactName: drop_build_dependent_build_package_Azure + artifactName: $(azureArtifactsName) packagePath: Microsoft.Data.SqlClient.Extensions.Azure.$(effectiveAzureVersion).nupkg nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} @@ -203,7 +203,7 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider - artifactName: drop_build_addons_build_package_AkvProvider + artifactName: $(akvArtifactsName) packagePath: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.$(effectiveAkvProviderVersion).nupkg nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} diff --git a/eng/pipelines/onebranch/steps/build-all-configurations-signed-dlls-step.yml b/eng/pipelines/onebranch/steps/build-all-configurations-signed-dlls-step.yml deleted file mode 100644 index b1429e1fd3..0000000000 --- a/eng/pipelines/onebranch/steps/build-all-configurations-signed-dlls-step.yml +++ /dev/null @@ -1,58 +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: - - # The assembly file version to apply to the Abstractions package. - - name: abstractionsAssemblyFileVersion - type: string - - # The version to apply to the Abstractions package. - - name: abstractionsPackageVersion - type: string - - # The assembly file version to apply to the Logging package. - - name: loggingAssemblyFileVersion - type: string - - # The version to apply to the Logging package. - - name: loggingPackageVersion - type: string - - # The assembly file version to apply to the Mds package. - - name: mdsAssemblyFileVersion - type: string - - # The version to apply to the Mds package. - - name: mdsPackageVersion - type: string - -steps: - # Download our signing key. - - task: DownloadSecureFile@1 - displayName: 'Download Key Pair' - inputs: - secureFile: netfxKeypair.snk - name: keyFile - - # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self - - - task: MSBuild@1 - displayName: 'BuildAllConfigurations using build.proj' - inputs: - solution: '**/build.proj' - configuration: Release - msbuildArguments: >- - -t:BuildAllConfigurations - -p:ReferenceType=Package - -p:GenerateNuget=false - -p:SigningKeyPath=$(keyFile.secureFilePath) - -p:AssemblyFileVersion=${{ parameters.mdsAssemblyFileVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:LoggingAssemblyFileVersion=${{ parameters.loggingAssemblyFileVersion }} diff --git a/eng/pipelines/onebranch/steps/compound-build-csproj-step.yml b/eng/pipelines/onebranch/steps/build-csproj-step.yml similarity index 95% rename from eng/pipelines/onebranch/steps/compound-build-csproj-step.yml rename to eng/pipelines/onebranch/steps/build-csproj-step.yml index 1f1fee00c4..d1cea7c190 100644 --- a/eng/pipelines/onebranch/steps/compound-build-csproj-step.yml +++ b/eng/pipelines/onebranch/steps/build-csproj-step.yml @@ -7,7 +7,7 @@ # Generic build step for csproj-based packages. Each project uses a build.proj target that runs # Build only and produces assemblies within $(BUILD_OUTPUT). Downstream ESRP DLL signing must # locate the assemblies within $(BUILD_OUTPUT) for all target frameworks that the csproj targets. -# NuGet packaging is done separately via compound-pack-csproj-step.yml after DLL signing. +# NuGet packaging is done separately via pack-csproj-step.yml after DLL signing. parameters: # The MSBuild build target in build.proj (e.g. BuildLogging, BuildAbstractions, diff --git a/eng/pipelines/onebranch/steps/build-mds-step.yml b/eng/pipelines/onebranch/steps/build-mds-step.yml new file mode 100644 index 0000000000..4d5058f261 --- /dev/null +++ b/eng/pipelines/onebranch/steps/build-mds-step.yml @@ -0,0 +1,41 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This collection of steps builds Microsoft.Data.SqlClient via build2.proj + +parameters: + - name: abstractionsPackageVersion + type: string + + - name: loggingPackageVersion + type: string + + - name: mdsPackageVersion + type: string + +steps: + - task: DownloadSecureFile@1 + displayName: 'Download Signing Key' + inputs: + secureFile: 'netfxKeypair.snk' + name: keyFile + + - task: MSBuild@1 + displayName: 'Build2.proj - BuildMds' + inputs: + solution: '$(REPO_ROOT)/build2.proj' + configuration: 'Release' + msbuildArguments: >- + -t:BuildMds + -p:BuildNumber="$(Build.BuildNumber)" + -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" + -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" + -p:PackageVersionMds="${{ parameters.mdsPackageVersion }}" + -p:ReferenceType=Package + -p:SigningKeyPath="$(keyFile.secureFilePath)" + + - script: tree /a /f $(BUILD_OUTPUT) + displayName: Output Build Output Tree diff --git a/eng/pipelines/onebranch/steps/compound-nuget-pack-step.yml b/eng/pipelines/onebranch/steps/compound-nuget-pack-step.yml deleted file mode 100644 index ef1f3b946a..0000000000 --- a/eng/pipelines/onebranch/steps/compound-nuget-pack-step.yml +++ /dev/null @@ -1,86 +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: - # The C# build configuration to use (e.g. Debug or Release). - - name: buildConfiguration - type: string - values: - - Debug - - Release - - - name: generateSymbolsPackage - type: boolean - - - name: packageVersion - type: string - - - name: nuspecPath - type: string - - - name: outputDirectory - type: string - - # The C# project reference type to use when building and packing the packages. - - name: referenceType - type: string - values: - # Reference sibling packages as NuGet packages. - - Package - # Reference sibling packages as C# projects. - - Project - - # Semi-colon separated properties to pass to nuget via the -properties argument. - - name: properties - type: string - default: '' - -steps: - # This tool is failing on OneBranch pipelines, possibly due to new - # network isolation rules: - # - # ERR:Client network socket disconnected before secure TLS connection was established - # - # Our AKV Official build uses this 1ES image: - # - # Image: 1ES-OB-2022-D8-Netlock-V2_westus2_1_image - # - # An ICM for this issue exists: - # - # https://portal.microsofticm.com/imp/v5/incidents/details/690355343/summary - # - # Recommendation is to remove this step since NuGet is already present on - # the 1ES images. - # - # - 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 }};${{ parameters.properties }}" - - ${{ 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 }};${{ parameters.properties }}" diff --git a/eng/pipelines/onebranch/steps/compound-publish-symbols-step.yml b/eng/pipelines/onebranch/steps/compound-publish-symbols-step.yml deleted file mode 100644 index 1b2e3cb3b9..0000000000 --- a/eng/pipelines/onebranch/steps/compound-publish-symbols-step.yml +++ /dev/null @@ -1,162 +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. # -################################################################################# - -# 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 - - # The C# project reference type to use when building and packing the packages. - - name: referenceType - type: string - values: - # Reference sibling packages as NuGet packages. - - Package - # Reference sibling packages as C# projects. - - Project - - # 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/onebranch/steps/copy-apiscan-files-mds-step.yml b/eng/pipelines/onebranch/steps/copy-apiscan-files-mds-step.yml new file mode 100644 index 0000000000..653eb40f5d --- /dev/null +++ b/eng/pipelines/onebranch/steps/copy-apiscan-files-mds-step.yml @@ -0,0 +1,39 @@ +################################################################################# +# 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 more generic + +parameters: + # Path where dll files should be copied to for APIScan + - name: dllPath + type: string + + # Path where pdb files should be copied to for APIScan + - name: pdbPath + type: string + + - name: referenceType + type: string + values: + - Package + - Project + +steps: + - task: CopyFiles@2 + displayName: 'Copy DLLs for APIScan' + inputs: + contents: '**/Microsoft.Data.SqlClient.dll' + flattenFolders: false # Disabled to keep different frameworks and OS builds separate in the target + sourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/${{ parameters.referenceType }}-Release/' + targetFolder: '${{ parameters.dllPath }}/' + + - task: CopyFiles@2 + displayName: 'Copy PDBs for APIScan' + inputs: + contents: '**/Microsoft.Data.SqlClient.pdb' + flattenFolders: false # Disabled to keep different frameworks and OS builds separate in the target + sourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/${{ parameters.referenceType }}-Release/' + targetFolder: '${{ parameters.pdbPath }}/' diff --git a/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml b/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml deleted file mode 100644 index 09f7715145..0000000000 --- a/eng/pipelines/onebranch/steps/esrp-code-signing-step.yml +++ /dev/null @@ -1,168 +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: artifactType - values: - - dll - - pkg - - - name: sourceRoot - type: string - default: $(REPO_ROOT) - - - name: dllPattern - type: string - default: "Microsoft.Data.SqlClient*.dll" - - - name: nupkgPattern - type: string - default: "*.*nupkg" - - - name: artifactDirectory - type: string - default: $(PACK_OUTPUT) - - - name: ESRPConnectedServiceName - type: string - default: $(ESRPConnectedServiceName) - - - name: appRegistrationClientId - type: string - default: $(appRegistrationClientId) - - - name: appRegistrationTenantId - type: string - default: $(appRegistrationTenantId) - - - name: AuthAKVName - type: string - default: $(AuthAKVName) - - - name: AuthSignCertName - type: string - default: $(AuthSignCertName) - - - name: EsrpClientId - type: string - default: $(EsrpClientId) - -steps: - # ESRP scan and sign the DLLs or NuGet packages, depending on the artifact type. - - ${{ if eq(parameters.artifactType, 'dll') }}: - # See: https://aka.ms/esrp.scantask - - task: EsrpMalwareScanning@6 - displayName: "ESRP MalwareScanning" - inputs: - ConnectedServiceName: "${{parameters.ESRPConnectedServiceName }}" - AppRegistrationClientId: "${{parameters.appRegistrationClientId }}" - AppRegistrationTenantId: "${{parameters.appRegistrationTenantId }}" - EsrpClientId: "${{parameters.EsrpClientId }}" - UseMSIAuthentication: true - FolderPath: "${{parameters.sourceRoot }}" - Pattern: "${{ parameters.dllPattern }}" - CleanupTempStorage: 1 - VerboseLogin: 1 - - # See: https://aka.ms/esrp.signtask - - task: EsrpCodeSigning@6 - displayName: "ESRP CodeSigning" - inputs: - ConnectedServiceName: "${{parameters.ESRPConnectedServiceName }}" - AppRegistrationClientId: "${{parameters.appRegistrationClientId }}" - AppRegistrationTenantId: "${{parameters.appRegistrationTenantId }}" - EsrpClientId: "${{parameters.EsrpClientId }}" - UseMSIAuthentication: true - AuthAKVName: "${{parameters.AuthAKVName }}" - AuthSignCertName: "${{parameters.AuthSignCertName }}" - FolderPath: "${{parameters.sourceRoot }}" - Pattern: "${{ parameters.dllPattern }}" - signConfigType: inlineSignParams - 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" - } - ] - - - ${{ if eq(parameters.artifactType, 'pkg') }}: - # See: https://aka.ms/esrp.scantask - - task: EsrpMalwareScanning@6 - displayName: "ESRP MalwareScanning Nuget Package" - inputs: - ConnectedServiceName: "${{parameters.ESRPConnectedServiceName }}" - AppRegistrationClientId: "${{parameters.appRegistrationClientId }}" - AppRegistrationTenantId: "${{parameters.appRegistrationTenantId }}" - EsrpClientId: "${{parameters.EsrpClientId }}" - UseMSIAuthentication: true - FolderPath: "${{parameters.artifactDirectory }}" - Pattern: "${{ parameters.nupkgPattern }}" - CleanupTempStorage: 1 - VerboseLogin: 1 - - # See: https://aka.ms/esrp.signtask - - task: EsrpCodeSigning@6 - displayName: "ESRP CodeSigning Nuget Package" - inputs: - ConnectedServiceName: "${{parameters.ESRPConnectedServiceName }}" - AppRegistrationClientId: "${{parameters.appRegistrationClientId }}" - AppRegistrationTenantId: "${{parameters.appRegistrationTenantId }}" - EsrpClientId: "${{parameters.EsrpClientId }}" - UseMSIAuthentication: true - AuthAKVName: "${{parameters.AuthAKVName }}" - AuthSignCertName: "${{parameters.AuthSignCertName }}" - FolderPath: "${{parameters.artifactDirectory }}" - Pattern: "${{ parameters.nupkgPattern }}" - signConfigType: inlineSignParams - 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/onebranch/steps/compound-esrp-dll-signing-step.yml b/eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml similarity index 78% rename from eng/pipelines/onebranch/steps/compound-esrp-dll-signing-step.yml rename to eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml index 44649f94aa..073911e5fe 100644 --- a/eng/pipelines/onebranch/steps/compound-esrp-dll-signing-step.yml +++ b/eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml @@ -30,7 +30,7 @@ parameters: - name: esrpClientId type: string - # Globbing pattern for the files to sign. All files in $(BUILD_OUTPUT)/Package/bin + # Globbing pattern for the files to sign. All files in $(BUILD_OUTPUT) # that match this pattern will be scanned and signed. This should end with ".dll". - name: pattern type: string @@ -40,13 +40,13 @@ steps: - task: EsrpMalwareScanning@6 displayName: ESRP DLL Malware Scanning inputs: - AppRegistrationClientId: ${{ parameters.appRegistrationClientId }} - AppRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' CleanupTempStorage: 1 - ConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} - EsrpClientId: ${{ parameters.esrpClientId }} - FolderPath: $(BUILD_OUTPUT)/Package/bin - Pattern: ${{ parameters.pattern }} + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + FolderPath: '$(BUILD_OUTPUT)' + Pattern: '${{ parameters.pattern }}' UseMSIAuthentication: true VerboseLogin: 1 @@ -54,14 +54,14 @@ steps: - task: EsrpCodeSigning@6 displayName: ESRP DLL Signing inputs: - AppRegistrationClientId: ${{ parameters.appRegistrationClientId }} - AppRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} - AuthAKVName: ${{ parameters.authAkvName }} - AuthSignCertName: ${{ parameters.authSignCertName }} - ConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} - EsrpClientId: ${{ parameters.esrpClientId }} - FolderPath: $(BUILD_OUTPUT)/Package/bin - Pattern: ${{ parameters.pattern }} + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + AuthAKVName: '${{ parameters.authAkvName }}' + AuthSignCertName: '${{ parameters.authSignCertName }}' + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + FolderPath: '$(BUILD_OUTPUT)' + Pattern: '${{ parameters.pattern }}' signConfigType: inlineSignParams UseMSIAuthentication: true inlineOperation: | diff --git a/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml b/eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml similarity index 59% rename from eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml rename to eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml index 824b3fab65..bddbac517c 100644 --- a/eng/pipelines/onebranch/steps/compound-esrp-nuget-signing-step.yml +++ b/eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml @@ -30,39 +30,43 @@ parameters: - name: esrpClientId type: string - # Glob pattern to match NuGet packages for scanning and signing. - - name: pattern + # Folder path to search for NuGet packages to sign + - name: searchPath type: string - default: "*.*nupkg" + + # Globbing pattern to use to search for NuGet packages. If not provided, defaults to '*.*nupkg' + - name: searchPattern + type: string + default: '*.*nupkg' steps: # See: https://aka.ms/esrp.scantask - task: EsrpMalwareScanning@6 - displayName: "ESRP Nuget Malware Scanning" + displayName: 'ESRP Nuget Malware Scanning' inputs: - AppRegistrationClientId: "${{ parameters.appRegistrationClientId }}" - AppRegistrationTenantId: "${{ parameters.appRegistrationTenantId }}" + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' CleanupTempStorage: 1 - ConnectedServiceName: "${{ parameters.esrpConnectedServiceName }}" - EsrpClientId: "${{ parameters.esrpClientId }}" - FolderPath: "$(PACK_OUTPUT)" - Pattern: "${{ parameters.pattern }}" + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + FolderPath: '${{ parameters.searchPath }}' + Pattern: '${{ parameters.searchPattern }}' UseMSIAuthentication: true VerboseLogin: 1 # See: https://aka.ms/esrp.signtask - task: EsrpCodeSigning@6 - displayName: "ESRP Signing NuGet Package" + 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: "$(PACK_OUTPUT)" - Pattern: "${{ parameters.pattern }}" - signConfigType: "inlineSignParams" + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + AuthAKVName: '${{ parameters.authAkvName }}' + AuthSignCertName: '${{ parameters.authSignCertName }}' + FolderPath: '${{ parameters.searchPath }}' + Pattern: '${{ parameters.searchPattern }}' + signConfigType: 'inlineSignParams' UseMSIAuthentication: true inlineOperation: | [ diff --git a/eng/pipelines/onebranch/steps/compound-pack-csproj-step.yml b/eng/pipelines/onebranch/steps/pack-csproj-step.yml similarity index 100% rename from eng/pipelines/onebranch/steps/compound-pack-csproj-step.yml rename to eng/pipelines/onebranch/steps/pack-csproj-step.yml diff --git a/eng/pipelines/onebranch/steps/pack-mds-step.yml b/eng/pipelines/onebranch/steps/pack-mds-step.yml new file mode 100644 index 0000000000..f1277280ae --- /dev/null +++ b/eng/pipelines/onebranch/steps/pack-mds-step.yml @@ -0,0 +1,42 @@ +################################################################################# +# 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: + # Package version parameters ---- + - name: abstractionsPackageVersion + type: string + + - name: loggingPackageVersion + type: string + + - name: mdsPackageVersion + type: string + +steps: + - task: MSBuild@1 + displayName: 'Build2.proj - PackMds' + inputs: + solution: '$(REPO_ROOT)/build2.proj' + configuration: 'Release' + msbuildArguments: >- + -t:PackMds + -p:BuildNumber="$(Build.BuildNumber)" + -p:PackBuild=false + -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" + -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" + -p:PackageVersionMds="${{ parameters.mdsPackageVersion }}" + -p:ReferenceType=Package + + - script: tree /a /f $(BUILD_OUTPUT) + displayName: Output Build Output Tree + + - task: CopyFiles@2 + displayName: 'Copy NuGet Packages to PACK_OUTPUT' + inputs: + contents: '**/Microsoft.Data.SqlClient*.*nupkg' + flattenFolders: true + sourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/' + targetFolder: '$(PACK_OUTPUT)' diff --git a/eng/pipelines/onebranch/steps/publish-symbols-step.yml b/eng/pipelines/onebranch/steps/publish-symbols-step.yml index a23dbf4556..48ee30b9c2 100644 --- a/eng/pipelines/onebranch/steps/publish-symbols-step.yml +++ b/eng/pipelines/onebranch/steps/publish-symbols-step.yml @@ -1,119 +1,153 @@ -#################################################################################### -# 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. # -# # -# doc: https://www.osgwiki.com/wiki/Symbols_Publishing_Pipeline_to_SymWeb_and_MSDL # -#################################################################################### +################################################################################# +# 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 + + # 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 - # The full name of the package whose symbols are being published. - - name: packageFullName - type: string - - # The version of the package whose symbols are being published. - - name: packageVersion - type: string - - # Our symbols account name. - - name: symbolsAccount - type: string - default: SqlClientDrivers - - # The symbols server to publish to. - - name: symbolServer - type: string - default: $(SymbolServer) - - # The token URI for the symbol publishing service. - - name: symbolTokenUri - type: string - default: $(SymbolTokenUri) - - # A pair of flags indicating whether to publish to the internal and public symbol servers. Both - # default to true. - - name: publishToServers - type: object - default: - internal: true - public: true + # Version of the symbols to publish, typically the same as the NuGet package version + - name: version + type: string steps: -- pwsh: 'Write-Host "##vso[task.setvariable variable=ArtifactServices.Symbol.AccountName;]${{parameters.symbolsAccount}}"' - displayName: 'Set ArtifactServices.Symbol.AccountName to ${{parameters.symbolsAccount}}' - -- pwsh: 'Write-Host "##vso[task.setvariable variable=symbolsArtifactName;]${{ parameters.packageFullName }}_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.packageVersion }}_$(System.TimelineId)"' - displayName: 'Set symbolsArtifactName variable' - -- task: PublishSymbols@2 - displayName: 'Upload symbols to ${{parameters.symbolsAccount }} org' - inputs: - SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\Package\bin' - SearchPattern: '**/*.pdb' - IndexSources: false - SymbolServerType: TeamServices - SymbolsMaximumWaitTime: 60 - SymbolExpirationInDays: 1825 # 5 years - SymbolsProduct: ${{ parameters.packageFullName }} - SymbolsVersion: ${{ parameters.packageVersion }} - SymbolsArtifactName: $(symbolsArtifactName) - Pat: $(System.AccessToken) - -- task: AzureCLI@2 - displayName: 'Publish symbols' - inputs: - azureSubscription: 'Symbols publishing Workload Identity federation service-ADO.Net' - scriptType: ps - scriptLocation: inlineScript - inlineScript: | - $publishToInternalServer = "${{parameters.publishToServers.internal }}".ToLower() - $publishToPublicServer = "${{parameters.publishToServers.public }}".ToLower() - - echo "Publishing request name: $(symbolsArtifactName)" - echo "Publish to internal server: $publishToInternalServer" - echo "Publish to public server: $publishToPublicServer" - - $symbolServer = "${{parameters.symbolServer }}" - $tokenUri = "${{parameters.symbolTokenUri }}" - # Registered project name in the symbol publishing pipeline: https://portal.microsofticm.com/imp/v3/incidents/incident/520844254/summary - $projectName = "Microsoft.Data.SqlClient.SNI" - - # Get the access token for the symbol publishing service - $symbolPublishingToken = az account get-access-token --resource $tokenUri --query accessToken -o tsv - - echo "> 1.Symbol publishing token acquired." - - echo "Registering the request name ..." - $requestName = "$(symbolsArtifactName)" - $requestNameRegistrationBody = "{'requestName': '$requestName'}" - Invoke-RestMethod -Method POST -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" -Body $requestNameRegistrationBody - - echo "> 2.Registration of request name succeeded." - - echo "Publishing the symbols ..." - $publishSymbolsBody = "{'publishToInternalServer': $publishToInternalServer, 'publishToPublicServer': $publishToPublicServer}" - echo "Publishing symbols request body: $publishSymbolsBody" - Invoke-RestMethod -Method POST -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" -Body $publishSymbolsBody - - echo "> 3.Request to publish symbols succeeded." - - # The following REST calls are used to check publishing status. - echo "> 4.Checking the status of the request ..." - - Invoke-RestMethod -Method GET -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -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" + # 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)' + 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/onebranch/steps/code-analyze-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml similarity index 100% rename from eng/pipelines/onebranch/steps/code-analyze-step.yml rename to eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml diff --git a/eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml new file mode 100644 index 0000000000..5fe8009078 --- /dev/null +++ b/eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml @@ -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. # +################################################################################# + +# This template defines a step to run Roslyn Analyzers on the MDS build. It uses the +# RoslynAnalyzers@3 task from the Secure Development Team's SDL extension: +# +# https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/roslyn-analyzers-build-task +# +# GOTCHA: This step will clobber any existing build output. It should be run _before_ any build +# steps that perform versioning or signing. + +parameters: + - name: abstractionsPackageVersion + type: string + + - name: loggingPackageVersion + type: string + + - name: mdsPackageVersion + type: string + +steps: + - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 + displayName: 'Roslyn Analyzers: Build2.proj BuildMds' + inputs: + msBuildArchitecture: x64 + msBuildCommandLine: >- + msbuild + $(REPO_ROOT)/build2.proj + -t:BuildMds + -p:Configuration=Release + -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" + -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" + -p:PackageVersionMds="${{ parameters.mdsPackageVersion }}" + -p:ReferenceType=Package + msBuildVersion: 17.0 + setupCommandLinePicker: vs2022 diff --git a/eng/pipelines/onebranch/variables/common-variables.yml b/eng/pipelines/onebranch/variables/common-variables.yml index feddd2c6ce..dbbddefc51 100644 --- a/eng/pipelines/onebranch/variables/common-variables.yml +++ b/eng/pipelines/onebranch/variables/common-variables.yml @@ -28,8 +28,7 @@ variables: - name: CommitHead value: '' # the value will be extracted from the repo's head - # Aliases required by compound step templates (compound-esrp-dll-signing-step, - # compound-esrp-nuget-signing-step, compound-nuget-pack-step, etc.). + # "Well-Known" Variables that are ok to use directly, anywhere in the pipeline. # The root of our repo. - name: REPO_ROOT diff --git a/eng/pipelines/onebranch/variables/onebranch-variables.yml b/eng/pipelines/onebranch/variables/onebranch-variables.yml index 9c60ff4cf0..004786e25f 100644 --- a/eng/pipelines/onebranch/variables/onebranch-variables.yml +++ b/eng/pipelines/onebranch/variables/onebranch-variables.yml @@ -52,4 +52,4 @@ variables: # The SqlClient package artifacts. - name: sqlClientArtifactsName - value: drop_build_dependent_build_package_SqlClient + value: drop_build_dependent_build_package_Mds diff --git a/src/Microsoft.Data.SqlClient/MdsVersions.props b/src/Microsoft.Data.SqlClient/MdsVersions.props index e50e813f66..a9f8d1cc83 100644 --- a/src/Microsoft.Data.SqlClient/MdsVersions.props +++ b/src/Microsoft.Data.SqlClient/MdsVersions.props @@ -1,35 +1,89 @@ - + + + + + 7.0.0 - 0 - - $(MdsVersionDefault).$(BuildNumber.Split('.')[0]) - $(MdsPackageVersion.Split('-')[0]) + - + + + + + $(MdsPackageVersion.Split('-')[0]).$(AssemblyBuildNumber) + + - NOTE: NuGet versioning can handle arbitrary version strings, so we do not need to trim the - build number like we do for the assembly version. + + - $(MdsVersionDefault).$(BuildNumber)-dev + If a build suffix is provided, this is appended to the package version. This is meant to be + used by automated pre-release systems to indicate the source of the build. + + If a build suffix is not provided, no pre-release tag will be added to package version. If + the default version already has a pre-release tag added to it (eg, "7.0.0-preview1") this + will be retained for the package version, but will be stripped off for the file version + (letters are not allowed in file/assembly versions). This is meant to be used by official + build pipelines to generate production-ready builds. + --> + + $(MdsVersionDefault)-$(BuildSuffix)$(AssemblyBuildNumber) + $(MdsVersionDefault) + + $(MdsVersionDefault.Split('-')[0]).$(AssemblyBuildNumber) + + + + + + + dev + + $(MdsVersionDefault)-$(BuildSuffix) + $(MdsVersionDefault.Split('-')[0]).$(AssemblyBuildNumber) + + + + + + + $(MdsFileVersion.Split('.')[0]).0.0.0 + + diff --git a/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj index 6f5fc44f59..4099207511 100644 --- a/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj @@ -71,7 +71,7 @@ $(MdsAssemblyVersion) - $(MdsAssemblyVersion) + $(MdsFileVersion) $(MdsPackageVersion) diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index dbdf7e9338..dfad9e601c 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -9,7 +9,7 @@ $(MdsAssemblyVersion) - $(MdsAssemblyVersion) + $(MdsFileVersion) $(MdsPackageVersion) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 0f1c3c2d40..2f7c726500 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -13,7 +13,7 @@ $(MdsAssemblyVersion) - $(MdsAssemblyVersion) + $(MdsFileVersion) $(MdsPackageVersion) diff --git a/tools/targets/GenerateThisAssemblyCs.targets b/tools/targets/GenerateThisAssemblyCs.targets index 6dfd670c5c..8632d80dc4 100644 --- a/tools/targets/GenerateThisAssemblyCs.targets +++ b/tools/targets/GenerateThisAssemblyCs.targets @@ -8,8 +8,8 @@ - - $(AssemblyVersion) + + $(FileVersion) System From 75b33ad53c18d53fe8bcfbb8287f5b3ce78ee11b Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Tue, 31 Mar 2026 23:26:42 -0700 Subject: [PATCH 018/298] Trim docs using pwsh via dotnet tool to support cross-platform local development. (#4121) --- dotnet-tools.json | 7 +++++++ .../ref/Microsoft.Data.SqlClient.csproj | 9 ++++++--- tools/targets/TrimDocsForIntelliSense.targets | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/dotnet-tools.json b/dotnet-tools.json index 1f59e06063..11e3494512 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -15,6 +15,13 @@ "apicompat" ], "rollForward": false + }, + "powershell": { + "version": "7.6.0", + "commands": [ + "pwsh" + ], + "rollForward": false } } } \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index dfad9e601c..252e8ba0b0 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -36,6 +36,10 @@ $(ArtifactPath)$(AssemblyName).ref/$(ReferenceType)-$(Configuration)/ + + + + - powershell.exe - pwsh - $(PowerShellCommand) + dotnet tool run pwsh -NonInteractive -ExecutionPolicy Unrestricted -Command "$(RepoRoot)tools\intellisense\TrimDocs.ps1 -inputFile '$(DocumentationFile)' -outputFile '$(DocumentationFile)'" diff --git a/tools/targets/TrimDocsForIntelliSense.targets b/tools/targets/TrimDocsForIntelliSense.targets index 05feb26739..fd0ca40ea8 100644 --- a/tools/targets/TrimDocsForIntelliSense.targets +++ b/tools/targets/TrimDocsForIntelliSense.targets @@ -3,7 +3,7 @@ The .NET Foundation licenses this file to you under the MIT license. --> - - + + From a11341f5bcd78e28044a683c8f61877a7f132a57 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 1 Apr 2026 07:25:09 -0300 Subject: [PATCH 019/298] Address flaky DEBUG assertions (#4085) --- .../ManualTests/DataCommon/DataTestUtility.cs | 89 ++++--- .../SQL/AdapterTest/AdapterTest.cs | 6 +- .../SQL/DataStreamTest/DataStreamTest.cs | 222 ++++++++++-------- .../SQL/LocalDBTest/LocalDBTest.cs | 2 +- .../ManualTests/SQL/MARSTest/MARSTest.cs | 4 +- .../ParallelTransactionsTest.cs | 6 +- .../SQL/ParameterTest/ParametersTest.cs | 22 +- .../CopyAllFromReaderCancelAsync.cs | 2 +- .../CopyAllFromReaderConnectionCloseAsync.cs | 2 +- ...llFromReaderConnectionCloseOnEventAsync.cs | 3 +- .../SQL/SqlBulkCopyTest/CopyWithEvent1.cs | 2 +- .../SqlBulkCopyTest/MissingTargetColumn.cs | 2 +- .../SqlBulkCopyTest/MissingTargetColumns.cs | 2 +- .../OrderHintDuplicateColumn.cs | 2 +- .../OrderHintMissingTargetColumn.cs | 4 +- .../SQL/SqlBulkCopyTest/Transaction.cs | 2 +- .../SQL/SqlBulkCopyTest/Transaction1.cs | 2 +- .../SQL/SqlBulkCopyTest/Transaction3.cs | 4 +- .../SQL/SqlBulkCopyTest/Transaction4.cs | 2 +- .../SqlBulkCopyTest/TransactionTestAsync.cs | 2 +- .../SQL/SqlCommand/SqlCommandCancelTest.cs | 6 +- .../SqlNotificationTest.cs | 6 +- .../SQL/TransactionTest/TransactionTest.cs | 18 +- .../tests/ManualTests/SQL/UdtTest/UdtTest2.cs | 15 +- 24 files changed, 245 insertions(+), 182 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 3b4245d84f..5bceb81b58 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -302,9 +302,9 @@ private static Task AcquireTokenAsync(string authorityURL, string userID SecureString securePassword = new SecureString(); securePassword.MakeReadOnly(); -#pragma warning disable CS0618 // Type or member is obsolete + #pragma warning disable CS0618 // Type or member is obsolete result = app.AcquireTokenByUsernamePassword(scopes, userID, password).ExecuteAsync().Result; -#pragma warning restore CS0618 // Type or member is obsolete + #pragma warning restore CS0618 // Type or member is obsolete return result.AccessToken; }); @@ -389,7 +389,7 @@ public static string GetSqlServerProperty(SqlConnection connection, ServerProper } } - #nullable disable + #nullable restore private static bool GetSQLServerStatusOnTDS8(string connectionString) { @@ -951,35 +951,48 @@ public static void AssertEqualsWithDescription(object expectedValue, object actu } } - public static TException AssertThrowsWrapper(Action actionThatFails, string exceptionMessage = null, bool innerExceptionMustBeNull = false, Func customExceptionVerifier = null) where TException : Exception + #nullable enable + + /// + /// Asserts that throws an exception of type + /// and optionally verifies that its message contains + /// . + /// + public static TException AssertThrows( + Action actionThatFails, + string? exceptionMessage = null) + where TException : Exception { TException ex = Assert.Throws(actionThatFails); + if (exceptionMessage != null) { Assert.True(ex.Message.Contains(exceptionMessage), string.Format("FAILED: Exception did not contain expected message.\nExpected: {0}\nActual: {1}", exceptionMessage, ex.Message)); } - if (innerExceptionMustBeNull) - { - Assert.True(ex.InnerException == null, "FAILED: Expected InnerException to be null."); - } - - if (customExceptionVerifier != null) - { - Assert.True(customExceptionVerifier(ex), "FAILED: Custom exception verifier returned false for this exception."); - } - return ex; } - public static TException AssertThrowsWrapper(Action actionThatFails, string exceptionMessage = null, string innerExceptionMessage = null, bool innerExceptionMustBeNull = false, Func customExceptionVerifier = null) where TException : Exception + /// + /// Asserts that throws + /// whose is of type . + /// Optionally verifies message text on both the outer and inner exceptions. + /// + public static TException AssertThrowsInner( + Action actionThatFails, + string? exceptionMessage = null, + string? innerExceptionMessage = null) + where TException : Exception + where TInnerException : Exception { - TException ex = AssertThrowsWrapper(actionThatFails, exceptionMessage, innerExceptionMustBeNull, customExceptionVerifier); + TException ex = AssertThrows(actionThatFails, exceptionMessage); + + Assert.NotNull(ex.InnerException); + Assert.IsAssignableFrom(ex.InnerException); if (innerExceptionMessage != null) { - Assert.True(ex.InnerException != null, "FAILED: Cannot check innerExceptionMessage because InnerException is null."); Assert.True(ex.InnerException.Message.Contains(innerExceptionMessage), string.Format("FAILED: Inner Exception did not contain expected message.\nExpected: {0}\nActual: {1}", innerExceptionMessage, ex.InnerException.Message)); } @@ -987,26 +1000,40 @@ public static TException AssertThrowsWrapper(Action return ex; } - public static TException AssertThrowsWrapper(Action actionThatFails, string exceptionMessage = null, string innerExceptionMessage = null, string innerInnerExceptionMessage = null, bool innerInnerInnerExceptionMustBeNull = false) where TException : Exception where TInnerException : Exception where TInnerInnerException : Exception + /// + /// Asserts that throws + /// whose is either + /// or . Use this when a race condition + /// (e.g. disposal during an async read) may cause the inner exception type to vary + /// between runs. The is only verified when the + /// inner exception is . + /// + public static TException AssertThrowsInnerWithAlternate( + Action actionThatFails, + string? exceptionMessage = null, + string? innerExceptionMessage = null) + where TException : Exception + where TInnerException : Exception + where TAlternateInnerException : Exception { - TException ex = AssertThrowsWrapper(actionThatFails, exceptionMessage, innerExceptionMessage); - if (innerInnerInnerExceptionMustBeNull) - { - Assert.True(ex.InnerException != null, "FAILED: Cannot check innerInnerInnerExceptionMustBeNull since InnerException is null"); - Assert.True(ex.InnerException.InnerException == null, "FAILED: Expected InnerInnerException to be null."); - } + TException ex = AssertThrows(actionThatFails, exceptionMessage); - if (innerInnerExceptionMessage != null) + Assert.NotNull(ex.InnerException); + Assert.True( + ex.InnerException is TInnerException or TAlternateInnerException, + $"Expected {typeof(TInnerException).Name} or {typeof(TAlternateInnerException).Name}, got: {ex.InnerException?.GetType()}"); + + if (innerExceptionMessage != null && ex.InnerException is TInnerException) { - Assert.True(ex.InnerException != null, "FAILED: Cannot check innerInnerExceptionMessage since InnerException is null"); - Assert.True(ex.InnerException.InnerException != null, "FAILED: Cannot check innerInnerExceptionMessage since InnerInnerException is null"); - Assert.True(ex.InnerException.InnerException.Message.Contains(innerInnerExceptionMessage), - string.Format("FAILED: Inner Exception did not contain expected message.\nExpected: {0}\nActual: {1}", innerInnerExceptionMessage, ex.InnerException.InnerException.Message)); + Assert.True(ex.InnerException.Message.Contains(innerExceptionMessage), + string.Format("FAILED: Inner Exception did not contain expected message.\nExpected: {0}\nActual: {1}", innerExceptionMessage, ex.InnerException.Message)); } return ex; } + #nullable restore + public static TException ExpectFailure(Action actionThatFails, string[] exceptionMessages, bool innerExceptionMustBeNull = false, Func customExceptionVerifier = null) where TException : Exception { try @@ -1320,7 +1347,7 @@ public static string GetMachineFQDN(string hostname) } return fqdn.ToString(); } - } - #nullable disable + #nullable restore + } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs index ed9d9ab541..9bf5351a83 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs @@ -1318,13 +1318,13 @@ public void TestDeriveParameters() using (SqlCommand cmd = new SqlCommand(procName, connection)) { string errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_DeriveParametersNotSupported, "SqlCommand", cmd.CommandType); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => SqlCommandBuilder.DeriveParameters(cmd), errorMessage); errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_OpenConnectionRequired, "DeriveParameters", ""); cmd.CommandType = CommandType.StoredProcedure; - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => SqlCommandBuilder.DeriveParameters(cmd), errorMessage); @@ -1335,7 +1335,7 @@ public void TestDeriveParameters() cmd.CommandText = "Test_EmployeeSalesBy"; errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_NoStoredProcedureExists, cmd.CommandText); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => SqlCommandBuilder.DeriveParameters(cmd), errorMessage); 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 1c6d851ecc..2de13c98ac 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -276,7 +276,7 @@ static async Task LocalCopyTo(Stream source, Stream destination, int bufferSize, { await destination.WriteAsync(new ReadOnlyMemory(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false); } -#endif +#endif } finally { @@ -320,7 +320,7 @@ private static byte[] CreateBinaryTable(SqlConnection connection, string tableNa { cmd.CommandText = $@" IF OBJECT_ID('dbo.{tableName}', 'U') IS NOT NULL -DROP TABLE {tableName}; +DROP TABLE {tableName}; CREATE TABLE {tableName} (id INT, foo VARBINARY(MAX)) "; cmd.ExecuteNonQuery(); @@ -377,7 +377,7 @@ private static void MultipleResults(string connectionString) { Assert.True(numBatches < expectedResults.Length, "ERROR: Received more batches than were expected."); object[] values = new object[r1.FieldCount]; - // Current "column" in expected row is (valuesChecked MOD FieldCount), since + // Current "column" in expected row is (valuesChecked MOD FieldCount), since // expected rows for current batch are appended together for easy formatting int valuesChecked = 0; while (r1.Read()) @@ -410,7 +410,7 @@ private static void InvalidRead(string connectionString) using (SqlDataReader reader = cmd.ExecuteReader()) { string errorMessage = SystemDataResourceManager.Instance.SQL_InvalidRead; - DataTestUtility.AssertThrowsWrapper(() => reader.GetInt32(0), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetInt32(0), errorMessage); } } } @@ -497,7 +497,7 @@ private static void TypeRead(string connectionString) s = rdr.GetString(10); //ShipCity; // should get an exception here string errorMessage = SystemDataResourceManager.Instance.SqlMisc_NullValueMessage; - DataTestUtility.AssertThrowsWrapper(() => rdr.GetString(11), errorMessage); + DataTestUtility.AssertThrows(() => rdr.GetString(11), errorMessage); s = rdr.GetString(12); //ShipPostalCode; s = rdr.GetString(13); //ShipCountry; @@ -533,7 +533,7 @@ private static void GetValueOfTRead(string connectionString) rdr.IsDBNull(10); rdr.GetFieldValue(10); //ShipCity; // should get an exception here - DataTestUtility.AssertThrowsWrapper(() => rdr.GetFieldValue(11), errorMessage); + DataTestUtility.AssertThrows(() => rdr.GetFieldValue(11), errorMessage); rdr.IsDBNull(11); rdr.GetFieldValue(11); rdr.IsDBNull(11); @@ -542,7 +542,7 @@ private static void GetValueOfTRead(string connectionString) rdr.IsDBNull(12); rdr.GetFieldValue(13);//ShipCountry; rdr.GetFieldValue(14); - DataTestUtility.AssertThrowsWrapper(() => rdr.GetFieldValue(15), errorMessage); + DataTestUtility.AssertThrows(() => rdr.GetFieldValue(15), errorMessage); rdr.Read(); // read data out of buffer @@ -559,7 +559,7 @@ private static void GetValueOfTRead(string connectionString) Assert.False(rdr.IsDBNullAsync(10).Result, "FAILED: IsDBNull was true for a non-null value"); rdr.GetFieldValueAsync(10).Wait(); //ShipCity; // should get an exception here - DataTestUtility.AssertThrowsWrapper(() => rdr.GetFieldValueAsync(11).Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrowsInner(() => rdr.GetFieldValueAsync(11).Wait(), innerExceptionMessage: errorMessage); Assert.True(rdr.IsDBNullAsync(11).Result, "FAILED: IsDBNull was false for a null value"); rdr.IsDBNullAsync(11).Wait(); @@ -787,7 +787,7 @@ private static void OrphanReader(string connectionString) conn.Close(); errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_DataReaderClosed, "CheckDataIsReady"); - DataTestUtility.AssertThrowsWrapper(() => value = reader[0], errorMessage); + DataTestUtility.AssertThrows(() => value = reader[0], errorMessage); Assert.True(reader.IsClosed, "FAILED: Stream was not closed by connection close (Scenario: Read)"); conn.Open(); } @@ -798,7 +798,7 @@ private static void OrphanReader(string connectionString) value = reader[0]; conn.Close(); - DataTestUtility.AssertThrowsWrapper(() => value = reader[0], errorMessage); + DataTestUtility.AssertThrows(() => value = reader[0], errorMessage); Assert.True(reader.IsClosed, "FAILED: Stream was not closed by connection close (Scenario: Read Partial Data)"); conn.Open(); } @@ -817,7 +817,7 @@ private static void OrphanReader(string connectionString) } while (reader.NextResult()); conn.Close(); - DataTestUtility.AssertThrowsWrapper(() => value = reader[0], errorMessage); + DataTestUtility.AssertThrows(() => value = reader[0], errorMessage); Assert.True(reader.IsClosed, "FAILED: Stream was not closed by connection close (Scenario: Read All Data)"); } } @@ -866,7 +866,7 @@ private static void ExecuteXmlReaderTest(string connectionString) // make sure we get an exception if we try to get another reader errorMessage = SystemDataResourceManager.Instance.ADP_OpenReaderExists("Connection"); - DataTestUtility.AssertThrowsWrapper(() => xr = cmd.ExecuteXmlReader(), errorMessage); + DataTestUtility.AssertThrows(() => xr = cmd.ExecuteXmlReader(), errorMessage); } // use a big result to fill up the pipe and do a partial read @@ -943,12 +943,12 @@ private static void ExecuteXmlReaderTest(string connectionString) // multiple columns cmd.CommandText = "select * from customers"; errorMessage = SystemDataResourceManager.Instance.SQL_NonXmlResult; - DataTestUtility.AssertThrowsWrapper(() => xr = cmd.ExecuteXmlReader(), errorMessage); + DataTestUtility.AssertThrows(() => xr = cmd.ExecuteXmlReader(), errorMessage); // non-ntext column cmd.CommandText = "select employeeID from employees"; errorMessage = SystemDataResourceManager.Instance.SQL_NonXmlResult; - DataTestUtility.AssertThrowsWrapper(() => xr = cmd.ExecuteXmlReader(), errorMessage); + DataTestUtility.AssertThrows(() => xr = cmd.ExecuteXmlReader(), errorMessage); } } } @@ -1114,31 +1114,31 @@ private static void SequentialAccess(string connectionString) i = reader.GetOrdinal("notes"); reader.GetChars(i, 14, chars, 0, 14); string errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_NonSequentialColumnAccess, i, i + 1); - DataTestUtility.AssertThrowsWrapper(() => reader.GetString(i), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetString(i), errorMessage); // Tests GetValue before GetBytes\Chars reader.Read(); i = reader.GetOrdinal("photo"); reader.GetSqlBinary(i); errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_NonSequentialColumnAccess, i, i + 1); - DataTestUtility.AssertThrowsWrapper(() => reader.GetBytes(i, 0, data, 0, 13), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetBytes(i, 0, data, 0, 13), errorMessage); i = reader.GetOrdinal("notes"); reader.GetString(i); errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_NonSequentialColumnAccess, i, i + 1); - DataTestUtility.AssertThrowsWrapper(() => reader.GetChars(i, 0, chars, 0, 14), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetChars(i, 0, chars, 0, 14), errorMessage); // Tests GetBytes\GetChars re-reading same characters reader.Read(); i = reader.GetOrdinal("photo"); reader.GetBytes(i, 0, data, 0, 13); errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_NonSeqByteAccess, 0, 13, "GetBytes"); - DataTestUtility.AssertThrowsWrapper(() => reader.GetBytes(i, 0, data, 0, 13), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetBytes(i, 0, data, 0, 13), errorMessage); i = reader.GetOrdinal("notes"); reader.GetChars(i, 0, chars, 0, 14); errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_NonSeqByteAccess, 0, 14, "GetChars"); - DataTestUtility.AssertThrowsWrapper(() => reader.GetChars(i, 0, chars, 0, 14), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetChars(i, 0, chars, 0, 14), errorMessage); } using (reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) @@ -1150,12 +1150,12 @@ private static void SequentialAccess(string connectionString) int columnToTry = 0; string errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_NonSequentialColumnAccess, columnToTry, sqldata.Length); - DataTestUtility.AssertThrowsWrapper(() => reader.GetInt32(columnToTry), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetValue(columnToTry), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValue(columnToTry), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValue(columnToTry), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValueAsync(columnToTry).Wait(), innerExceptionMessage: errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValueAsync(columnToTry).Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrows(() => reader.GetInt32(columnToTry), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetValue(columnToTry), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetFieldValue(columnToTry), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetFieldValue(columnToTry), errorMessage); + DataTestUtility.AssertThrowsInner(() => reader.GetFieldValueAsync(columnToTry).Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrowsInner(() => reader.GetFieldValueAsync(columnToTry).Wait(), innerExceptionMessage: errorMessage); reader.Read(); columnToTry = 17; @@ -1163,12 +1163,12 @@ private static void SequentialAccess(string connectionString) s = reader.GetString(columnToTry); DataTestUtility.AssertEqualsWithDescription("http://accweb/emmployees/fuller.bmp", s, "FAILED: Did not receive expected string."); - DataTestUtility.AssertThrowsWrapper(() => reader.GetInt32(columnToTry), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetValue(columnToTry), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValue(columnToTry), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValue(columnToTry), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValueAsync(columnToTry).Wait(), innerExceptionMessage: errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValueAsync(columnToTry).Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrows(() => reader.GetInt32(columnToTry), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetValue(columnToTry), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetFieldValue(columnToTry), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetFieldValue(columnToTry), errorMessage); + DataTestUtility.AssertThrowsInner(() => reader.GetFieldValueAsync(columnToTry).Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrowsInner(() => reader.GetFieldValueAsync(columnToTry).Wait(), innerExceptionMessage: errorMessage); reader.Read(); // skip all columns up to photo, and read from it partially @@ -1189,14 +1189,14 @@ private static void SequentialAccess(string connectionString) // now try to read one more byte string errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_DataReaderClosed, "GetBytes"); - DataTestUtility.AssertThrowsWrapper(() => cb = reader.GetBytes(i, 51, data, 0, 1), errorMessage); + DataTestUtility.AssertThrows(() => cb = reader.GetBytes(i, 51, data, 0, 1), errorMessage); errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_DataReaderClosed, "CheckDataIsReady"); - DataTestUtility.AssertThrowsWrapper(() => reader.GetValue(i), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValue(i), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValue(i), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetValue(i), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetFieldValue(i), errorMessage); + DataTestUtility.AssertThrows(() => reader.GetFieldValue(i), errorMessage); errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_DataReaderClosed, "GetFieldValueAsync"); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValueAsync(i).Wait(), innerExceptionMessage: errorMessage); - DataTestUtility.AssertThrowsWrapper(() => reader.GetFieldValueAsync(i).Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrowsInnerWithAlternate(() => reader.GetFieldValueAsync(i).Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrowsInnerWithAlternate(() => reader.GetFieldValueAsync(i).Wait(), innerExceptionMessage: errorMessage); } } } @@ -1236,10 +1236,10 @@ private static void NumericRead(string connectionString) // Em object value; string errorMessage = SystemDataResourceManager.Instance.SqlMisc_ConversionOverflowMessage; - DataTestUtility.AssertThrowsWrapper(() => value = reader[0], errorMessage); - DataTestUtility.AssertThrowsWrapper(() => value = reader[1], errorMessage); - DataTestUtility.AssertThrowsWrapper(() => value = reader.GetDecimal(0), errorMessage); - DataTestUtility.AssertThrowsWrapper(() => value = reader.GetDecimal(1), errorMessage); + DataTestUtility.AssertThrows(() => value = reader[0], errorMessage); + DataTestUtility.AssertThrows(() => value = reader[1], errorMessage); + DataTestUtility.AssertThrows(() => value = reader.GetDecimal(0), errorMessage); + DataTestUtility.AssertThrows(() => value = reader.GetDecimal(1), errorMessage); } } finally @@ -1297,7 +1297,7 @@ private static void HasRowsTest(string connectionString) bool result; string errorMessage = string.Format(SystemDataResourceManager.Instance.ADP_DataReaderClosed, "HasRows"); - DataTestUtility.AssertThrowsWrapper(() => result = reader.HasRows, errorMessage); + DataTestUtility.AssertThrows(() => result = reader.HasRows, errorMessage); } } } @@ -1382,7 +1382,7 @@ private static void SeqAccessFailureWrapper(Action action, CommandBe { if (behavior == CommandBehavior.SequentialAccess) { - DataTestUtility.AssertThrowsWrapper(action); + DataTestUtility.AssertThrows(action); } else { @@ -1390,6 +1390,24 @@ private static void SeqAccessFailureWrapper(Action action, CommandBe } } + /// + /// Waits for a task that may or may not throw due to a race condition, and if they do + /// throw, they may throw one of a few acceptable exceptions. + /// + /// See: https://github.com/dotnet/SqlClient/issues/4088 + /// + private static void WaitIgnoringFlakyException(Task task) + { + try { task.Wait(); } + catch (AggregateException) + { + // A faulted Task stores its exception permanently. Calling .Wait() again is + // guaranteed to re-throw the same AggregateException, so we can safely pass it to + // the assert helper for inner-exception validation. + DataTestUtility.AssertThrowsInnerWithAlternate(() => task.Wait()); + } + } + private static void GetStream(string connectionString) { using (SqlConnection connection = new SqlConnection(connectionString)) @@ -1410,7 +1428,7 @@ private static void GetStream(string connectionString) reader.GetStream(1); // Bad values - DataTestUtility.AssertThrowsWrapper(() => reader.GetStream(2)); + DataTestUtility.AssertThrows(() => reader.GetStream(2)); // Null stream Stream stream = reader.GetStream(3); Assert.False(stream.Read(buffer, 0, buffer.Length) > 0, "FAILED: Read more than 0 bytes from a null stream"); @@ -1446,12 +1464,12 @@ private static void GetStream(string connectionString) { t = reader.ReadAsync(); Assert.False(t.Wait(1), "FAILED: Read completed immediately"); - DataTestUtility.AssertThrowsWrapper(() => reader.GetStream(8)); + DataTestUtility.AssertThrows(() => reader.GetStream(8)); } - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + WaitIgnoringFlakyException(t); - // GetStream after Read - DataTestUtility.AssertThrowsWrapper(() => reader.GetStream(0)); + // GetStream after Read + DataTestUtility.AssertThrows(() => reader.GetStream(0)); #endif } @@ -1486,8 +1504,7 @@ private static void GetStream(string connectionString) Assert.True(t.IsCompleted, "FAILED: Failed to get stream within 1 second"); t = reader.ReadAsync(); } - // TODO(GH-3604): Fix this failing assertion. - // DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + WaitIgnoringFlakyException(t); } #endif } @@ -1523,7 +1540,7 @@ private static void GetTextReader(string connectionString) reader.GetTextReader(1); // Bad values - DataTestUtility.AssertThrowsWrapper(() => reader.GetTextReader(2)); + DataTestUtility.AssertThrows(() => reader.GetTextReader(2)); // Null stream TextReader textReader = reader.GetTextReader(3); Assert.False(textReader.Read(buffer, 0, buffer.Length) > 0, "FAILED: Read more than 0 chars from a null TextReader"); @@ -1559,13 +1576,12 @@ private static void GetTextReader(string connectionString) { t = reader.ReadAsync(); Assert.False(t.IsCompleted, "FAILED: Read completed immediately"); - DataTestUtility.AssertThrowsWrapper(() => reader.GetTextReader(8)); + DataTestUtility.AssertThrows(() => reader.GetTextReader(8)); } - // TODO(GH-3604): Fix this failing assertion. - // DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + WaitIgnoringFlakyException(t); - // GetTextReader after Read - DataTestUtility.AssertThrowsWrapper(() => reader.GetTextReader(0)); + // GetTextReader after Read + DataTestUtility.AssertThrows(() => reader.GetTextReader(0)); #endif } @@ -1601,8 +1617,7 @@ private static void GetTextReader(string connectionString) Assert.True(t.IsCompleted, "FAILED: Failed to get TextReader within 1 second"); t = reader.ReadAsync(); } - // TODO(GH-3604): Fix this failing assertion. - // DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + WaitIgnoringFlakyException(t); } #endif } @@ -1631,7 +1646,7 @@ private static void GetXmlReader(string connectionString) reader.GetXmlReader(1); // Bad values - DataTestUtility.AssertThrowsWrapper(() => reader.GetXmlReader(2)); + DataTestUtility.AssertThrows(() => reader.GetXmlReader(2)); // Null stream XmlReader xmlReader = reader.GetXmlReader(3); Assert.False(xmlReader.Read(), "FAILED: Successfully read on a null XmlReader"); @@ -1651,13 +1666,12 @@ private static void GetXmlReader(string connectionString) { t = reader.ReadAsync(); Assert.False(t.IsCompleted, "FAILED: Read completed immediately"); - DataTestUtility.AssertThrowsWrapper(() => reader.GetXmlReader(6)); + DataTestUtility.AssertThrows(() => reader.GetXmlReader(6)); } - // TODO(GH-3604): Fix this failing assertion. - // DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + WaitIgnoringFlakyException(t); - // GetXmlReader after Read - DataTestUtility.AssertThrowsWrapper(() => reader.GetXmlReader(0)); + // GetXmlReader after Read + DataTestUtility.AssertThrows(() => reader.GetXmlReader(0)); #endif } } @@ -1691,16 +1705,16 @@ private static void ReadStream(string connectionString) // Testing stream properties stream.Flush(); - DataTestUtility.AssertThrowsWrapper(() => stream.SetLength(1)); + DataTestUtility.AssertThrows(() => stream.SetLength(1)); Action performOnStream = ((s) => { int i = s.WriteTimeout; }); - DataTestUtility.AssertThrowsWrapper(() => performOnStream(stream)); + DataTestUtility.AssertThrows(() => performOnStream(stream)); if (behavior == CommandBehavior.SequentialAccess) { - DataTestUtility.AssertThrowsWrapper(() => stream.Seek(0, SeekOrigin.Begin)); + DataTestUtility.AssertThrows(() => stream.Seek(0, SeekOrigin.Begin)); performOnStream = ((s) => { long i = s.Position; }); - DataTestUtility.AssertThrowsWrapper(() => performOnStream(stream)); + DataTestUtility.AssertThrows(() => performOnStream(stream)); performOnStream = ((s) => { long i = s.Length; }); - DataTestUtility.AssertThrowsWrapper(() => performOnStream(stream)); + DataTestUtility.AssertThrows(() => performOnStream(stream)); } else { @@ -1711,7 +1725,7 @@ private static void ReadStream(string connectionString) } // Once Stream is closed - DataTestUtility.AssertThrowsWrapper(() => { _ = stream.Read(buffer, 0, buffer.Length); }); + DataTestUtility.AssertThrows(() => { _ = stream.Read(buffer, 0, buffer.Length); }); } using (SqlDataReader reader = cmd.ExecuteReader(behavior)) @@ -1723,9 +1737,9 @@ private static void ReadStream(string connectionString) _ = stream.Read(buffer, 0, buffer.Length); // Argument exceptions - DataTestUtility.AssertThrowsWrapper(() => { _ = stream.Read(null, 0, 1); }); - DataTestUtility.AssertThrowsWrapper(() => { _ = stream.Read(buffer, -1, 2); }); - DataTestUtility.AssertThrowsWrapper(() => { _ = stream.Read(buffer, 2, -1); }); + DataTestUtility.AssertThrows(() => { _ = stream.Read(null, 0, 1); }); + DataTestUtility.AssertThrows(() => { _ = stream.Read(buffer, -1, 2); }); + DataTestUtility.AssertThrows(() => { _ = stream.Read(buffer, 2, -1); }); // Prior to net6 comment:ArgumentException is thrown in net5 and earlier. ArgumentOutOfRangeException in net6 and later ArgumentException ex = Assert.ThrowsAny(() => { _ = stream.Read(buffer, buffer.Length, buffer.Length); }); @@ -1777,10 +1791,10 @@ private static void ReadStream(string connectionString) { // Read during async t = stream.ReadAsync(largeBuffer, 0, largeBuffer.Length); - DataTestUtility.AssertThrowsWrapper(() => { _ = stream.Read(largeBuffer, 0, largeBuffer.Length); }); - DataTestUtility.AssertThrowsWrapper(() => reader.Read()); + DataTestUtility.AssertThrows(() => { _ = stream.Read(largeBuffer, 0, largeBuffer.Length); }); + DataTestUtility.AssertThrows(() => reader.Read()); } - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + DataTestUtility.AssertThrowsInnerWithAlternate(() => t.Wait()); } using (SqlDataReader reader = cmd.ExecuteReader(behavior)) { @@ -1796,7 +1810,7 @@ private static void ReadStream(string connectionString) // Guarantee that timeout occurs: Thread.Sleep(stream.ReadTimeout * 4); } - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + DataTestUtility.AssertThrowsInnerWithAlternate(() => t.Wait()); } using (SqlDataReader reader = cmd.ExecuteReader(behavior)) @@ -1811,7 +1825,10 @@ private static void ReadStream(string connectionString) t = stream.ReadAsync(largeBuffer, 0, largeBuffer.Length, tokenSource.Token); tokenSource.Cancel(); } - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + // Normally the cancellation wins (TaskCanceledException), but if the + // PendAsyncReadsScope disposal completes the read first, the inner + // exception may be InvalidOperationException instead (GH-4088). + DataTestUtility.AssertThrowsInnerWithAlternate(() => t.Wait()); } using (SqlDataReader reader = cmd.ExecuteReader(behavior)) @@ -1824,7 +1841,18 @@ private static void ReadStream(string connectionString) // Error during read t = stream.ReadAsync(largeBuffer, 0, largeBuffer.Length); } - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + // PendAsyncReadsScope(errorCode: 11) injects a network error, normally producing + // AggregateException -> IOException -> SqlException. In rare race conditions + // the inner exception may be ObjectDisposedException instead (GH-4088). + AggregateException aex = Assert.Throws(() => t.Wait()); + if (aex.InnerException is IOException ioEx) + { + Assert.IsAssignableFrom(ioEx.InnerException); + } + else + { + Assert.IsAssignableFrom(aex.InnerException); + } } #endif } @@ -1876,7 +1904,7 @@ private static void ReadTextReader(string connectionString) } // Once Reader is closed - DataTestUtility.AssertThrowsWrapper(() => textReader.Read(buffer, 0, buffer.Length)); + DataTestUtility.AssertThrows(() => textReader.Read(buffer, 0, buffer.Length)); } using (SqlDataReader reader = cmd.ExecuteReader(behavior)) @@ -1890,11 +1918,11 @@ private static void ReadTextReader(string connectionString) textReader.Peek(); // Argument exceptions - DataTestUtility.AssertThrowsWrapper(() => textReader.Read(null, 0, 1)); - DataTestUtility.AssertThrowsWrapper(() => textReader.Read(buffer, -1, 2)); - DataTestUtility.AssertThrowsWrapper(() => textReader.Read(buffer, 2, -1)); - DataTestUtility.AssertThrowsWrapper(() => textReader.Read(buffer, buffer.Length, buffer.Length)); - DataTestUtility.AssertThrowsWrapper(() => textReader.Read(buffer, int.MaxValue, int.MaxValue)); + DataTestUtility.AssertThrows(() => textReader.Read(null, 0, 1)); + DataTestUtility.AssertThrows(() => textReader.Read(buffer, -1, 2)); + DataTestUtility.AssertThrows(() => textReader.Read(buffer, 2, -1)); + DataTestUtility.AssertThrows(() => textReader.Read(buffer, buffer.Length, buffer.Length)); + DataTestUtility.AssertThrows(() => textReader.Read(buffer, int.MaxValue, int.MaxValue)); } // Once Reader is closed @@ -1936,11 +1964,10 @@ private static void ReadTextReader(string connectionString) { // Read during async t = textReader.ReadAsync(largeBuffer, 0, largeBuffer.Length); - DataTestUtility.AssertThrowsWrapper(() => textReader.Read(largeBuffer, 0, largeBuffer.Length)); - DataTestUtility.AssertThrowsWrapper(() => reader.Read()); + DataTestUtility.AssertThrows(() => textReader.Read(largeBuffer, 0, largeBuffer.Length)); + DataTestUtility.AssertThrows(() => reader.Read()); } - // TODO(GH-3604): Fix this failing assertion. - // DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + WaitIgnoringFlakyException(t); } using (SqlDataReader reader = cmd.ExecuteReader(behavior)) @@ -1953,7 +1980,18 @@ private static void ReadTextReader(string connectionString) // Error during read t = textReader.ReadAsync(largeBuffer, 0, largeBuffer.Length); } - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + // PendAsyncReadsScope(errorCode: 11) injects a network error, normally producing + // AggregateException -> IOException -> SqlException. In rare race conditions + // the inner exception may be ObjectDisposedException instead (GH-4088). + AggregateException aex = Assert.Throws(() => t.Wait()); + if (aex.InnerException is IOException ioEx) + { + Assert.IsAssignableFrom(ioEx.InnerException); + } + else + { + Assert.IsAssignableFrom(aex.InnerException); + } } #endif } @@ -2118,7 +2156,7 @@ private void TestXEventsStreaming(string connectionString) byte[] bytes = new byte[cb]; long read = reader.GetBytes(1, 0, bytes, 0, cb); - // Don't send data on the first read because there is already data in the buffer. + // Don't send data on the first read because there is already data in the buffer. // Don't send data on the last iteration. We will not be reading that data. if (i == 0 || i == streamXeventCount - 1) { @@ -2164,7 +2202,7 @@ private static void TimeoutDuringReadAsyncWithClosedReaderTest(string connection // Wait for the task to see the timeout string errorMessage = SystemDataResourceManager.Instance.SQL_Timeout_Execution; - DataTestUtility.AssertThrowsWrapper(() => task.Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrowsInnerWithAlternate(() => task.Wait(), innerExceptionMessage: errorMessage); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs index ad84ac7881..c3aed471c0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs @@ -54,7 +54,7 @@ public static void LocalDBMarsTest() public static void InvalidLocalDBTest() { using var connection = new SqlConnection(s_badConnectionString); - DataTestUtility.AssertThrowsWrapper(() => connection.Open()); + DataTestUtility.AssertThrows(() => connection.Open()); } #endregion diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs index 69265c4e75..1213c5bfa4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs @@ -591,7 +591,7 @@ public static void MARSMultiDataReaderErrTest() { using (SqlDataReader reader1 = command.ExecuteReader()) { - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { SqlDataReader reader2 = command.ExecuteReader(); }, openReaderExistsMessage); @@ -611,7 +611,7 @@ public static void MARSMultiDataReaderErrTest() { using (SqlDataReader reader1 = command1.ExecuteReader()) { - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { SqlDataReader reader2 = command2.ExecuteReader(); }, openReaderExistsMessage); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs index efc99ea757..73fbd8bdc6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs @@ -20,7 +20,7 @@ public static void BasicParallelTest_shouldThrowsUnsupported() try { tempTableName = CreateTempTable(connectionString); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( actionThatFails: () => { BasicParallelTest(connectionString, tempTableName); }, exceptionMessage: expectedErrorMessage); } @@ -77,7 +77,7 @@ public static void MultipleExecutesInSameTransactionTest_shouldThrowsUnsupported try { tempTableName = CreateTempTable(connectionString); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( actionThatFails: () => { MultipleExecutesInSameTransactionTest(connectionString, tempTableName); }, exceptionMessage: expectedErrorMessage); } @@ -157,5 +157,3 @@ private static void DropTempTable(string connectionString, string tempTableName) } } } - - 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 a4cd63b0c1..d53209e912 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -42,14 +42,14 @@ public static void CodeCoverageSqlClient() { string failValue; - DataTestUtility.AssertThrowsWrapper(() => failValue = opc[0].ParameterName, "Invalid index 0 for this SqlParameterCollection with Count=0."); + DataTestUtility.AssertThrows(() => failValue = opc[0].ParameterName, "Invalid index 0 for this SqlParameterCollection with Count=0."); - DataTestUtility.AssertThrowsWrapper(() => failValue = opc["@p1"].ParameterName, "A SqlParameter with ParameterName '@p1' is not contained by this SqlParameterCollection."); + DataTestUtility.AssertThrows(() => failValue = opc["@p1"].ParameterName, "A SqlParameter with ParameterName '@p1' is not contained by this SqlParameterCollection."); - DataTestUtility.AssertThrowsWrapper(() => opc["@p1"] = null, "A SqlParameter with ParameterName '@p1' is not contained by this SqlParameterCollection."); + DataTestUtility.AssertThrows(() => opc["@p1"] = null, "A SqlParameter with ParameterName '@p1' is not contained by this SqlParameterCollection."); } - DataTestUtility.AssertThrowsWrapper(() => opc.Add(null), "The SqlParameterCollection only accepts non-null SqlParameter type objects."); + DataTestUtility.AssertThrows(() => opc.Add(null), "The SqlParameterCollection only accepts non-null SqlParameter type objects."); opc.Add((object)new SqlParameter()); IEnumerator enm = opc.GetEnumerator(); @@ -70,11 +70,11 @@ public static void CodeCoverageSqlClient() SqlParameter p = opc[0]; - DataTestUtility.AssertThrowsWrapper(() => opc.Add((object)p), "The SqlParameter is already contained by another SqlParameterCollection."); + DataTestUtility.AssertThrows(() => opc.Add((object)p), "The SqlParameter is already contained by another SqlParameterCollection."); - DataTestUtility.AssertThrowsWrapper(() => new SqlCommand().Parameters.Add(p), "The SqlParameter is already contained by another SqlParameterCollection."); + DataTestUtility.AssertThrows(() => new SqlCommand().Parameters.Add(p), "The SqlParameter is already contained by another SqlParameterCollection."); - DataTestUtility.AssertThrowsWrapper(() => opc.Remove(null), "The SqlParameterCollection only accepts non-null SqlParameter type objects."); + DataTestUtility.AssertThrows(() => opc.Remove(null), "The SqlParameterCollection only accepts non-null SqlParameter type objects."); string pname = p.ParameterName; p.ParameterName = pname; @@ -106,13 +106,13 @@ public static void CodeCoverageSqlClient() new SqlCommand().Parameters.CopyTo(new object[0], 0); Assert.False(new SqlCommand().Parameters.GetEnumerator().MoveNext(), "FAILED: Expected MoveNext to be false"); - DataTestUtility.AssertThrowsWrapper(() => new SqlCommand().Parameters.Add(0), "The SqlParameterCollection only accepts non-null Microsoft.Data.SqlClient.SqlParameter type objects, not System.Int32 objects."); + DataTestUtility.AssertThrows(() => new SqlCommand().Parameters.Add(0), "The SqlParameterCollection only accepts non-null Microsoft.Data.SqlClient.SqlParameter type objects, not System.Int32 objects."); - DataTestUtility.AssertThrowsWrapper(() => new SqlCommand().Parameters.Insert(0, 0), "The SqlParameterCollection only accepts non-null Microsoft.Data.SqlClient.SqlParameter type objects, not System.Int32 objects."); + DataTestUtility.AssertThrows(() => new SqlCommand().Parameters.Insert(0, 0), "The SqlParameterCollection only accepts non-null Microsoft.Data.SqlClient.SqlParameter type objects, not System.Int32 objects."); - DataTestUtility.AssertThrowsWrapper(() => new SqlCommand().Parameters.Remove(0), "The SqlParameterCollection only accepts non-null Microsoft.Data.SqlClient.SqlParameter type objects, not System.Int32 objects."); + DataTestUtility.AssertThrows(() => new SqlCommand().Parameters.Remove(0), "The SqlParameterCollection only accepts non-null Microsoft.Data.SqlClient.SqlParameter type objects, not System.Int32 objects."); - DataTestUtility.AssertThrowsWrapper(() => new SqlCommand().Parameters.Remove(new SqlParameter()), "Attempted to remove an SqlParameter that is not contained by this SqlParameterCollection."); + DataTestUtility.AssertThrows(() => new SqlCommand().Parameters.Remove(new SqlParameter()), "Attempted to remove an SqlParameter that is not contained by this SqlParameterCollection."); } // TODO Synapse: Parse error at line: 1, column: 12: Incorrect syntax near 'IF'. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderCancelAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderCancelAsync.cs index 3ae79dbe31..3fb3d051a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderCancelAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderCancelAsync.cs @@ -19,7 +19,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) cts = new CancellationTokenSource(); cts.Cancel(); Task t = TestAsync(srcConstr, dstConstr, dstTable, cts.Token); - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + DataTestUtility.AssertThrowsInner(() => t.Wait()); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseAsync.cs index fd6062659a..ede16437d3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseAsync.cs @@ -14,7 +14,7 @@ public class CopyAllFromReaderConnectionClosedAsync public static void Test(string srcConstr, string dstConstr, string dstTable) { Task t = TestAsync(srcConstr, dstConstr, dstTable); - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + DataTestUtility.AssertThrowsInner(() => t.Wait()); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseOnEventAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseOnEventAsync.cs index 1afd2d93d0..3d832925a6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseOnEventAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseOnEventAsync.cs @@ -4,6 +4,7 @@ using System; using System.Data.Common; +using System.IO; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -49,7 +50,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) // Check that the copying fails string message = string.Format(SystemDataResourceManager.Instance.ADP_OpenConnectionRequired, "WriteToServer", SystemDataResourceManager.Instance.ADP_ConnectionStateMsg_Closed); - DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServerAsync(reader).Wait(5000), innerExceptionMessage: message); + DataTestUtility.AssertThrowsInnerWithAlternate(() => bulkcopy.WriteToServerAsync(reader).Wait(5000), innerExceptionMessage: message); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent1.cs index e89f2f83a3..8bc72435d0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent1.cs @@ -66,7 +66,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) ColumnMappings.Add("ShipName", "shipname"); bulkcopy.NotifyAfter = 3; - DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader)); + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); bulkcopy.SqlRowsCopied -= new SqlRowsCopiedEventHandler(OnRowCopied); bulkcopy.Close(); } 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 d8fe8f66da..e4df366137 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs @@ -38,7 +38,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; errorMsg = string.Format(errorMsg, "col2"); - DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); + DataTestUtility.AssertThrows(() => 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 index 2696770f69..5fb47f44c1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs @@ -38,7 +38,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; errorMsg = string.Format(errorMsg, "col3,col4"); - DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs index 9e535ccae1..ebed5e9918 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs @@ -45,7 +45,7 @@ public static void Test(string connStr, string dstTable) string expectedErrorMsg = string.Format( SystemDataResourceManager.Instance.SQL_BulkLoadOrderHintDuplicateColumn, destColumn); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => bulkcopy.ColumnOrderHints.Add(destColumn, SortOrder.Ascending), exceptionMessage: expectedErrorMsg); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs index 46a7f39eee..cc486e9e4e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs @@ -44,7 +44,7 @@ public static void Test(string connStr, string dstTable) string expectedErrorMsg = string.Format( SystemDataResourceManager.Instance.SQL_BulkLoadOrderHintInvalidColumn, nonexistentColumn); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => bulkcopy.WriteToServer(reader), exceptionMessage: expectedErrorMsg); @@ -56,7 +56,7 @@ public static void Test(string connStr, string dstTable) expectedErrorMsg = string.Format( SystemDataResourceManager.Instance.SQL_BulkLoadOrderHintInvalidColumn, sourceColumn); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => bulkcopy.WriteToServer(reader), exceptionMessage: expectedErrorMsg); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction.cs index e7632044b0..0738f32010 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction.cs @@ -34,7 +34,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) SqlTransaction myTrans = dstConn.BeginTransaction(); try { - DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader)); + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); } finally { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction1.cs index 1f6ba5e823..930e1ac6f5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction1.cs @@ -37,7 +37,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) try { - DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader)); + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); } finally { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction3.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction3.cs index 8e53989ed5..4b65f2a5d2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction3.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction3.cs @@ -37,13 +37,13 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) bulkcopy.DestinationTableName = dstTable; string exceptionMsg = SystemDataResourceManager.Instance.ADP_TransactionConnectionMismatch; - DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader), exceptionMessage: exceptionMsg); + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader), exceptionMessage: exceptionMsg); SqlCommand myCmd = dstConn.CreateCommand(); myCmd.CommandText = "select * from " + dstTable; myCmd.Transaction = myTrans; - DataTestUtility.AssertThrowsWrapper(() => myCmd.ExecuteReader(), exceptionMessage: exceptionMsg); + DataTestUtility.AssertThrows(() => myCmd.ExecuteReader(), exceptionMessage: exceptionMsg); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction4.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction4.cs index c8028ca5c5..40b2d2c251 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction4.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction4.cs @@ -31,7 +31,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) // Start a local transaction on the wrong connection. SqlTransaction myTrans = conn3.BeginTransaction(); string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadConflictingTransactionOption; - DataTestUtility.AssertThrowsWrapper(() => new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, myTrans), exceptionMessage: errorMsg); + DataTestUtility.AssertThrows(() => new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, myTrans), exceptionMessage: errorMsg); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TransactionTestAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TransactionTestAsync.cs index ac28a9d883..729dac74a9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TransactionTestAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TransactionTestAsync.cs @@ -14,7 +14,7 @@ public class TransactionTestAsync public static void Test(string srcConstr, string dstConstr, string dstTable) { Task t = TestAsync(srcConstr, dstConstr, dstTable); - DataTestUtility.AssertThrowsWrapper(() => t.Wait()); + DataTestUtility.AssertThrowsInner(() => t.Wait()); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs index 97c225c9ca..552fd4cea5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs @@ -65,7 +65,7 @@ private static void PlainCancel(string connString) using (SqlDataReader reader = cmd.ExecuteReader()) { cmd.Cancel(); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => { do @@ -88,7 +88,7 @@ private static void PlainCancelAsync(string connString) { conn.Open(); Task readerTask = cmd.ExecuteReaderAsync(); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => { readerTask.Wait(2000); @@ -214,7 +214,7 @@ private static void CancelFollowedByTransaction(string constr) private static void CancelFollowedByAlert(string constr) { var alertName = "myAlert" + Guid.NewGuid().ToString(); - // Since Alert conditions are randomly generated, + // Since Alert conditions are randomly generated, // we will retry on unexpected error messages to avoid collision in pipelines. var n = new Random().Next(1, 100); bool retry = true; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs index 98fa8997de..e4bd1133ac 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs @@ -71,7 +71,7 @@ public void Test_DoubleStart_DifferentConnStr() try { - DataTestUtility.AssertThrowsWrapper(() => SqlDependency.Start(cb.ToString())); + DataTestUtility.AssertThrows(() => SqlDependency.Start(cb.ToString())); } finally { @@ -116,7 +116,7 @@ public void Test_SingleDependency_NoStart() Console.WriteLine("4 Notification callback. Type={0}, Info={1}, Source={2}", args.Type, args.Info, args.Source); }; - DataTestUtility.AssertThrowsWrapper(() => cmd.ExecuteReader()); + DataTestUtility.AssertThrows(() => cmd.ExecuteReader()); } } @@ -138,7 +138,7 @@ public void Test_SingleDependency_Stopped() Console.WriteLine("5 Notification callback. Type={0}, Info={1}, Source={2}", args.Type, args.Info, args.Source); }; - DataTestUtility.AssertThrowsWrapper(() => cmd.ExecuteReader()); + DataTestUtility.AssertThrows(() => cmd.ExecuteReader()); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs index 1a97d6df31..0adff2c8ae 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs @@ -339,13 +339,13 @@ private void ExceptionTest() string executeCommandWithoutTransactionMessage = SystemDataResourceManager.Instance.ADP_TransactionRequired("ExecuteNonQuery"); string transactionConflictErrorMessage = SystemDataResourceManager.Instance.ADP_TransactionConnectionMismatch; string parallelTransactionErrorMessage = SystemDataResourceManager.Instance.ADP_ParallelTransactionsNotSupported("SqlConnection"); - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { SqlCommand command = new SqlCommand("sql", connection); command.ExecuteNonQuery(); }, executeCommandWithoutTransactionMessage); - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { using (SqlConnection con1 = new SqlConnection(_connectionString)) { @@ -357,32 +357,32 @@ private void ExceptionTest() } }, transactionConflictErrorMessage); - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { connection.BeginTransaction(null); }, parallelTransactionErrorMessage); - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { connection.BeginTransaction(""); }, parallelTransactionErrorMessage); - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { tx.Rollback(null); }, invalidSaveStateMessage); - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { tx.Rollback(""); }, invalidSaveStateMessage); - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { tx.Save(null); }, invalidSaveStateMessage); - DataTestUtility.AssertThrowsWrapper(() => + DataTestUtility.AssertThrows(() => { tx.Save(""); }, invalidSaveStateMessage); @@ -456,7 +456,7 @@ private void ReadCommitedIsolationLevel_ShouldReceiveTimeoutExceptionBecauseItWa SqlTransaction tx2 = connection2.BeginTransaction(IsolationLevel.ReadCommitted); command2.Transaction = tx2; - DataTestUtility.AssertThrowsWrapper(() => command2.ExecuteReader(), SystemDataResourceManager.Instance.SQL_Timeout_Execution as string); + DataTestUtility.AssertThrows(() => command2.ExecuteReader(), SystemDataResourceManager.Instance.SQL_Timeout_Execution as string); tx2.Rollback(); connection2.Close(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs index 79f38262cc..1dae314730 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs @@ -75,7 +75,7 @@ public void UDTParams_Binary() value[7] = 0; p.Value = new SqlBinary(value); - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => cmd.ExecuteReader(), "Error converting data type varbinary to Point."); } @@ -110,7 +110,7 @@ public void UDTParams_Invalid2() p.Value = addr; pName.Value = addr; - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => cmd.ExecuteReader(), "Failed to convert parameter value from a Address to a String."); } @@ -134,7 +134,7 @@ public void UDTParams_Invalid() p.UdtTypeName = "UdtTestDb.dbo.Point"; p.Value = 32; - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => cmd.ExecuteReader(), "Specified type is not registered on the target server. System.Int32"); } @@ -221,7 +221,7 @@ public void UDTParams_NullInput() string errorMsg = "Procedure or function '" + spInsertCustomerNoBrackets + "' expects parameter '@addr', which was not supplied."; - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => cmd.ExecuteNonQuery(), errorMsg); } @@ -317,7 +317,7 @@ public void UDTFields_WrongType() reader.Read(); // retrieve the UDT as a string - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => reader.GetString(1), "Unable to cast object of type 'System.Byte[]' to type 'System.String'."); } @@ -584,12 +584,12 @@ Func create string udtError = SystemDataResourceManager.Instance.SQLUDT_MaxByteSizeValue; string errorMessage = (new ArgumentOutOfRangeException("MaxByteSize", 8001, udtError)).Message; - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => create(SqlUserDefinedAggregateAttribute.MaxByteSizeValue + 1), errorMessage); errorMessage = (new ArgumentOutOfRangeException("MaxByteSize", -2, udtError)).Message; - DataTestUtility.AssertThrowsWrapper( + DataTestUtility.AssertThrows( () => create(-2), errorMessage); } @@ -702,4 +702,3 @@ public void UDTParams_DeriveParameters_CheckAutoFixOverride() } } } - From 4e1d0d49af40d00a151150a751db0fc626b6d5c0 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 1 Apr 2026 11:20:48 -0700 Subject: [PATCH 020/298] =?UTF-8?q?Revert=20"Trim=20docs=20using=20pwsh=20?= =?UTF-8?q?via=20dotnet=20tool=20to=20support=20cross-platform=20local=20?= =?UTF-8?q?=E2=80=A6"=20(#4127)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 75b33ad53c18d53fe8bcfbb8287f5b3ce78ee11b. --- dotnet-tools.json | 7 ------- .../ref/Microsoft.Data.SqlClient.csproj | 9 +++------ tools/targets/TrimDocsForIntelliSense.targets | 4 ++-- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/dotnet-tools.json b/dotnet-tools.json index 11e3494512..1f59e06063 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -15,13 +15,6 @@ "apicompat" ], "rollForward": false - }, - "powershell": { - "version": "7.6.0", - "commands": [ - "pwsh" - ], - "rollForward": false } } } \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index 252e8ba0b0..dfad9e601c 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -36,10 +36,6 @@ $(ArtifactPath)$(AssemblyName).ref/$(ReferenceType)-$(Configuration)/ - - - - + powershell.exe + pwsh - dotnet tool run pwsh + $(PowerShellCommand) -NonInteractive -ExecutionPolicy Unrestricted -Command "$(RepoRoot)tools\intellisense\TrimDocs.ps1 -inputFile '$(DocumentationFile)' -outputFile '$(DocumentationFile)'" diff --git a/tools/targets/TrimDocsForIntelliSense.targets b/tools/targets/TrimDocsForIntelliSense.targets index fd0ca40ea8..05feb26739 100644 --- a/tools/targets/TrimDocsForIntelliSense.targets +++ b/tools/targets/TrimDocsForIntelliSense.targets @@ -3,7 +3,7 @@ The .NET Foundation licenses this file to you under the MIT license. --> - - + + From 90463215f3d9c69993d547d02287ba0b55aa9802 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 2 Apr 2026 15:39:24 -0700 Subject: [PATCH 021/298] Test | Connection pool transaction tests (#3805) --- .../prompts/generate-doc-comments.prompt.md | 2 +- .github/prompts/generate-prompt.prompt.md | 63 +- .github/prompts/refine-test-overlap.prompt.md | 12 +- .../ConnectionPool/ChannelDbConnectionPool.cs | 4 + .../ConnectionPool/DbConnectionPoolOptions.cs | 3 + .../ConnectionPool/IDbConnectionPool.cs | 7 +- .../TransactedConnectionPool.cs | 31 +- .../WaitHandleDbConnectionPool.cs | 8 +- .../SqlClientTestGroup.cs | 1 + .../TransactedConnectionPoolTest.cs | 5 +- ...itHandleDbConnectionPoolTransactionTest.cs | 951 ++++++++++++++++++ 11 files changed, 1059 insertions(+), 28 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs diff --git a/.github/prompts/generate-doc-comments.prompt.md b/.github/prompts/generate-doc-comments.prompt.md index 33ced936de..9361d3c7ad 100644 --- a/.github/prompts/generate-doc-comments.prompt.md +++ b/.github/prompts/generate-doc-comments.prompt.md @@ -1,5 +1,5 @@ --- -name: doc-comments +name: generate-doc-comments description: Generate XML documentation comments for C# code following .NET best practices. argument-hint: agent: agent diff --git a/.github/prompts/generate-prompt.prompt.md b/.github/prompts/generate-prompt.prompt.md index a4aa8cd40f..73d083947b 100644 --- a/.github/prompts/generate-prompt.prompt.md +++ b/.github/prompts/generate-prompt.prompt.md @@ -34,6 +34,7 @@ Before generating the prompt, review the available skills in the `.github/skills * `name`: A concise, kebab-case name for the prompt. * `description`: A clear, short description of what the prompt does. * `argument-hint`: (Optional) A hint for what arguments the user can provide when using the prompt. + * `tools`: (Recommended) A list of tool identifiers the prompt is allowed to use. See the **Tool Scoping** section below. * **Body Structure**: * **Role**: Define the AI's persona (e.g., "You are an expert C# developer..."). * **Context**: Include specific context instructions or references. @@ -50,12 +51,70 @@ Before generating the prompt, review the available skills in the `.github/skills * Use `${input:variableName}` for user inputs (e.g., `${input:methodName}`). * Use built-in variables like `${selection}`, `${file}`, or `${workspaceFolder}` where appropriate context is needed. -6. **Best Practices**: +6. **Scope Tools**: Restrict the tools available to each prompt using the `tools` frontmatter field. See the **Tool Scoping** section below for detailed guidance. + +7. **Best Practices**: * Be specific and explicit. * Encourage chain-of-thought reasoning if the task is complex. * Reference workspace files using Markdown links `[path/to/file.cs](path/to/file.cs)` only if they are static and necessary for *all* invocations of this prompt. * Prefer referencing skills over duplicating instructions that already exist in skills. +## Tool Scoping + +Every generated prompt **should** include a `tools` list in its YAML frontmatter. Scoping tools keeps the model focused by limiting it to approved, known-effective tools for the task. Without tool scoping, the model may invoke irrelevant tools, waste context, or produce unpredictable results. + +### Why scope tools? +- **Focus**: Fewer tools means the model spends less reasoning on tool selection and more on the task. +- **Reliability**: Restricting to tested tools avoids unexpected side effects (e.g., a read-only review prompt shouldn't have edit tools). +- **Safety**: Prevents prompts from accidentally running terminal commands or making file changes when they shouldn't. + +### How to choose tools +Apply the **principle of least privilege** — include only the tools the prompt actually needs: + +| Prompt type | Recommended tools | +|---|---| +| **Read-only analysis** (review, triage, explain) | `read/readFile`, `search/codebase`, `search/textSearch` | +| **Code editing** (bug fix, feature, refactor) | `edit/editFiles`, `edit/createFile`, `read/readFile`, `search/codebase` | +| **Needs terminal** (build, test, scripts) | All of the above + `execute/runInTerminal`, `execute/getTerminalOutput` | +| **Needs GitHub data** (triage, release notes) | All of the above + `github/search_issues` or other GitHub tools | +| **Needs web content** (docs lookup) | `web/fetch` | + +### Available built-in tool identifiers + +You can specify individual tools or tool sets (which include all tools in that group). + +**Tool sets** (use these to include all tools in a category): +- `edit` — File creation and editing tools +- `read` — File and notebook reading tools +- `search` — Codebase, text, and file search tools +- `execute` — Terminal, task, and notebook execution tools +- `web` — Web content fetching tools + +**Commonly used individual tools:** + +| Tool identifier | Purpose | +|---|---| +| `edit/editFiles` | Apply edits to existing files | +| `edit/createFile` | Create a new file | +| `read/readFile` | Read file contents | +| `read/problems` | Get workspace problems/diagnostics | +| `search/codebase` | Semantic code search | +| `search/textSearch` | Text/regex search in files | +| `search/fileSearch` | Search for files by glob pattern | +| `search/listDirectory` | List directory contents | +| `search/usages` | Find references and implementations | +| `execute/runInTerminal` | Run a shell command | +| `execute/getTerminalOutput` | Get terminal output | +| `execute/testFailure` | Get test failure details | +| `web/fetch` | Fetch a web page | + +**Extension / MCP tools** can also be included using their identifier (e.g., `github/search_issues`). Use `/*` to include all tools from an MCP server. + +### Frontmatter syntax +```yaml +tools: ['read/readFile', 'search/codebase', 'edit/editFiles'] +``` + ## Example Output Structure (with skill reference) ```markdown @@ -63,6 +122,7 @@ Before generating the prompt, review the available skills in the `.github/skills name: my-new-prompt description: specialized task description argument-hint: input parameter hint +tools: ['edit/editFiles', 'read/readFile', 'search/codebase', 'execute/runInTerminal'] --- You are a specialized agent for... @@ -89,6 +149,7 @@ Use ${input:param1} to... name: my-new-prompt description: specialized task description argument-hint: input parameter hint +tools: ['read/readFile', 'search/codebase'] --- You are a specialized agent for... diff --git a/.github/prompts/refine-test-overlap.prompt.md b/.github/prompts/refine-test-overlap.prompt.md index 824e18f145..f4d0339c27 100644 --- a/.github/prompts/refine-test-overlap.prompt.md +++ b/.github/prompts/refine-test-overlap.prompt.md @@ -1,7 +1,9 @@ --- -name: test-minimize-overlap +name: refine-test-overlap description: Run coverage overlap analysis and suggest test suite optimizations argument-hint: Test filter (e.g. FullyQualifiedName~MyTests) or describe the tests you want to analyze +agent: agent +tools: ['edit/editFiles', 'read/readFile', 'search/codebase', 'execute/runInTerminal', 'execute/getTerminalOutput'] --- You are an expert .NET Test Engineer specialized in optimizing test coverage and reducing technical debt. @@ -10,19 +12,19 @@ Your task is to analyze the user's test suite using the `AnalyzeTestOverlap.ps1` ## Skills This prompt leverages the following skills for specific sub-tasks: -- [generate-mstest-filter](../skills/generate-mstest-filter/SKILL.md) - For generating well-formed MSTest filter expressions +- [generate-mstest-filter](.github/skills/generate-mstest-filter/SKILL.md) - For generating well-formed MSTest filter expressions ## Tools -You have access to the analysis script at `[AnalyzeTestOverlap.ps1](./scripts/AnalyzeTestOverlap.ps1)`. +You have access to the analysis script at [AnalyzeTestOverlap.ps1](.github/prompts/scripts/AnalyzeTestOverlap.ps1). ## Workflow 1. **Parse or Generate Test Filter**: * If `${input:filter}` is a valid MSTest filter expression (e.g., `FullyQualifiedName~MyTests`), use it directly. - * If `${input:filter}` is a loose description (e.g., "connection tests" or "SqlCommand class"), follow the instructions in the [generate-mstest-filter](../skills/generate-mstest-filter/SKILL.md) skill to generate a proper filter expression. + * If `${input:filter}` is a loose description (e.g., "connection tests" or "SqlCommand class"), follow the instructions in the [generate-mstest-filter](.github/skills/generate-mstest-filter/SKILL.md) skill to generate a proper filter expression. * If `${input:filter}` is empty, ask the user for a test filter or description to target specific tests. 2. **Run Analysis**: - * Run the script using the filter: `.\scripts\AnalyzeTestOverlap.ps1 -Filter ""`. + * Run the script from the workspace root: `.\.github\prompts\scripts\AnalyzeTestOverlap.ps1 -Filter ""`. * *Note*: The script produces a console summary and a `test-coverage-analysis.json` file. 3. **Review Overlap**: 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 index 63815e8200..553e6cd395 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -96,6 +96,7 @@ internal ChannelDbConnectionPool( Identity = identity; AuthenticationContexts = new(); MaxPoolSize = Convert.ToUInt32(PoolGroupOptions.MaxPoolSize); + TransactedConnectionPool = new(this); _connectionSlots = new(MaxPoolSize); @@ -148,6 +149,9 @@ public ConcurrentDictionary< /// public DbConnectionPoolState State { get; private set; } + /// + public TransactedConnectionPool TransactedConnectionPool { get; } + /// public bool UseLoadBalancing => PoolGroupOptions.UseLoadBalancing; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs index 7adf8abdc2..5ac6f4d565 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs @@ -39,6 +39,9 @@ bool hasTransactionAffinity _hasTransactionAffinity = hasTransactionAffinity; } + /// + /// The time (in milliseconds) to wait for a connection to be created/returned before terminating the attempt. + /// public int CreationTimeout { get { return _creationTimeout; } 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 index b684bb24bb..bfc5789d3f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -85,6 +85,11 @@ internal interface IDbConnectionPool /// DbConnectionPoolState State { get; } + /// + /// Holds connections that are currently enlisted in a transaction. + /// + TransactedConnectionPool TransactedConnectionPool { get; } + /// /// Indicates whether the connection pool is using load balancing. /// @@ -106,7 +111,7 @@ internal interface IDbConnectionPool /// 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); + 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. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/TransactedConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/TransactedConnectionPool.cs index 7245164f2c..3ce203c071 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/TransactedConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/TransactedConnectionPool.cs @@ -31,7 +31,7 @@ internal class TransactedConnectionPool /// A specialized list that holds database connections associated with a specific transaction. /// Maintains a reference to the transaction for proper cleanup when the transaction completes. /// - private sealed class TransactedConnectionList : List + internal sealed class TransactedConnectionList : List { private readonly Transaction _transaction; @@ -60,9 +60,6 @@ internal void Dispose() } #region Fields - - private readonly Dictionary _transactedCxns; - private static int _objectTypeCount; internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); @@ -80,7 +77,7 @@ internal void Dispose() internal TransactedConnectionPool(IDbConnectionPool pool) { Pool = pool; - _transactedCxns = new Dictionary(); + TransactedConnections = new Dictionary(); SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed for connection pool {1}", Id, Pool.Id); } @@ -98,6 +95,8 @@ internal TransactedConnectionPool(IDbConnectionPool pool) /// The IDbConnectionPool instance that owns this transacted pool. internal IDbConnectionPool Pool { get; } + internal Dictionary TransactedConnections { get; } + #endregion #region Methods @@ -123,9 +122,9 @@ internal TransactedConnectionPool(IDbConnectionPool pool) TransactedConnectionList? connections; bool txnFound = false; - lock (_transactedCxns) + lock (TransactedConnections) { - txnFound = _transactedCxns.TryGetValue(transaction, out connections); + txnFound = TransactedConnections.TryGetValue(transaction, out connections); } // NOTE: GetTransactedObject is only used when AutoEnlist = True and the ambient transaction @@ -182,10 +181,10 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal // NOTE: because TransactionEnded is an asynchronous notification, there's no guarantee // around the order in which PutTransactionObject and TransactionEnded are called. - lock (_transactedCxns) + lock (TransactedConnections) { // Check if a transacted pool has been created for this transaction - if ((txnFound = _transactedCxns.TryGetValue(transaction, out connections)) + if ((txnFound = TransactedConnections.TryGetValue(transaction, out connections)) && connections is not null) { // synchronize multi-threaded access with GetTransactedObject @@ -213,14 +212,14 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal transactionClone = transaction.Clone(); newConnections = new TransactedConnectionList(2, transactionClone); // start with only two connections in the list; most times we won't need that many. - lock (_transactedCxns) + lock (TransactedConnections) { // NOTE: in the interim between the locks on the transacted pool (this) during // execution of this method, another thread (threadB) may have attempted to // add a different connection to the transacted pool under the same // transaction. As a result, threadB may have completed creating the // transacted pool while threadA was processing the above instructions. - if (_transactedCxns.TryGetValue(transaction, out connections) + if (TransactedConnections.TryGetValue(transaction, out connections) && connections is not null) { // synchronize multi-threaded access with GetTransactedObject @@ -238,7 +237,7 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal // add the connection/transacted object to the list newConnections.Add(transactedObject); - _transactedCxns.Add(transactionClone, newConnections); + TransactedConnections.Add(transactionClone, newConnections); transactionClone = null; // we've used it -- don't throw it or the TransactedConnectionList that references it away. } } @@ -297,9 +296,9 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra // TODO: that the pending creation of a transacted pool for this transaction is aborted when // TODO: PutTransactedObject finally gets some CPU time? - lock (_transactedCxns) + lock (TransactedConnections) { - if (_transactedCxns.TryGetValue(transaction, out connections) + if (TransactedConnections.TryGetValue(transaction, out connections) && connections is not null) { bool shouldDisposeConnections = false; @@ -319,7 +318,7 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra if (0 >= connections.Count) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Removing List from transacted pool.", Id, transaction.GetHashCode()); - _transactedCxns.Remove(transaction); + TransactedConnections.Remove(transaction); // we really need to dispose our connection list; it may have // native resources via the tx and GC may not happen soon enough. @@ -351,4 +350,4 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra } #endregion -} \ No newline at end of file +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 194054e58e..8d2311b5cd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -305,9 +305,9 @@ public bool IsRunning get { return State is Running; } } - private int MaxPoolSize => PoolGroupOptions.MaxPoolSize; + internal int MaxPoolSize => PoolGroupOptions.MaxPoolSize; - private int MinPoolSize => PoolGroupOptions.MinPoolSize; + internal int MinPoolSize => PoolGroupOptions.MinPoolSize; public DbConnectionPoolGroup PoolGroup => _connectionPoolGroup; @@ -324,6 +324,8 @@ public bool IsRunning private bool UsingIntegrateSecurity => _identity != null && DbConnectionPoolIdentity.NoIdentity != _identity; + public TransactedConnectionPool TransactedConnectionPool => _transactedConnectionPool; + private void CleanupCallback(object state) { // Called when the cleanup-timer ticks over. @@ -940,6 +942,8 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj // If automatic transaction enlistment is required, then we try to // get the connection from the transacted connection pool first. + // If automatic enlistment is not enabled, then we cannot vend connections + // from the transacted pool. if (HasTransactionAffinity) { obj = GetFromTransactedPool(out transaction); diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs index e48c13d49b..7c7597ba8a 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using System.Diagnostics; using System.Data; +using System.Transactions; using Microsoft.Data.SqlClient; using System.Xml; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs index 3fc31338cd..18bd9c5ea3 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs @@ -668,13 +668,14 @@ internal class MockDbConnectionPool : IDbConnectionPool public DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException(); public DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException(); public DbConnectionPoolState State => throw new NotImplementedException(); + public TransactedConnectionPool TransactedConnectionPool => throw new NotImplementedException(); public bool UseLoadBalancing => throw new NotImplementedException(); public ConcurrentBag ReturnedConnections { get; } = new(); public void Clear() => throw new NotImplementedException(); - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection) { throw new NotImplementedException(); } @@ -739,4 +740,4 @@ internal override void ResetConnection() } #endregion -} \ No newline at end of file +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs new file mode 100644 index 0000000000..cafea0dad4 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs @@ -0,0 +1,951 @@ +// 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.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool; + +/// +/// Deterministic tests for WaitHandleDbConnectionPool transaction functionality. +/// These tests exercise transacted connection pathways with controlled synchronization +/// to verify correct behavior without relying on probabilistic concurrency. +/// +public class WaitHandleDbConnectionPoolTransactionTest : IDisposable +{ + private const int DefaultMaxPoolSize = 50; + private const int DefaultMinPoolSize = 0; + private const int DefaultCreationTimeoutInMilliseconds = 15000; + + private IDbConnectionPool _pool = null!; + + public WaitHandleDbConnectionPoolTransactionTest() + { + _pool = CreatePool(); + } + + public void Dispose() + { + // Verify no leaked transactions before cleanup + Assert.Empty(_pool.TransactedConnectionPool.TransactedConnections); + + _pool?.Shutdown(); + _pool?.Clear(); + } + + #region Helper Methods + + private WaitHandleDbConnectionPool CreatePool( + int maxPoolSize = DefaultMaxPoolSize, + int minPoolSize = DefaultMinPoolSize, + bool hasTransactionAffinity = true) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: minPoolSize, + maxPoolSize: maxPoolSize, + creationTimeout: DefaultCreationTimeoutInMilliseconds, + loadBalanceTimeout: 0, + hasTransactionAffinity: hasTransactionAffinity + ); + + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new DbConnectionOptions("DataSource=localhost;", null), + new DbConnectionPoolKey("TestDataSource"), + poolGroupOptions + ); + + var connectionFactory = new MockSqlConnectionFactory(); + + var pool = new WaitHandleDbConnectionPool( + connectionFactory, + dbConnectionPoolGroup, + DbConnectionPoolIdentity.NoIdentity, + new DbConnectionPoolProviderInfo() + ); + + pool.Startup(); + return pool; + } + + private DbConnectionInternal GetConnection(SqlConnection owner) + { + _pool.TryGetConnection( + owner, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? connection); + return connection!; + } + + private async Task GetConnectionAsync( + SqlConnection owner, + Transaction? transaction = null) + { + var tcs = new TaskCompletionSource(transaction); + _pool.TryGetConnection( + owner, + taskCompletionSource: tcs, + new DbConnectionOptions("", null), + out DbConnectionInternal? connection); + return connection ?? await tcs.Task; + } + + private void ReturnConnection(DbConnectionInternal connection, SqlConnection owner) + { + _pool.ReturnInternalConnection(connection, owner); + } + + private void AssertPoolMetrics() + { + Assert.True(_pool.Count <= _pool.PoolGroupOptions.MaxPoolSize, + $"Pool count ({_pool.Count}) exceeded max pool size ({_pool.PoolGroupOptions.MaxPoolSize})"); + Assert.True(_pool.Count >= 0, + $"Pool count ({_pool.Count}) is negative"); + Assert.Empty(_pool.TransactedConnectionPool.TransactedConnections); + } + + #endregion + + #region Transaction Routing Tests + + [Fact] + public void GetConnection_UnderTransaction_RoutesToTransactedPool() + { + // Arrange & Act + using var scope = new TransactionScope(); + var transaction = Transaction.Current; + Assert.NotNull(transaction); + + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + + ReturnConnection(conn, owner); + + // Assert - connection should be in the transacted pool + Assert.True(_pool.TransactedConnectionPool.TransactedConnections.ContainsKey(transaction)); + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections[transaction]); + + scope.Complete(); + } + + [Fact] + public void GetConnection_WithoutTransaction_RoutesToGeneralPool() + { + // Arrange & Act (no TransactionScope) + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + + ReturnConnection(conn, owner); + + // Assert - transacted pool should be empty + Assert.Empty(_pool.TransactedConnectionPool.TransactedConnections); + } + + [Fact] + public void GetConnection_UnderTransaction_ReturnsSameConnectionFromTransactedPool() + { + // Arrange + using var scope = new TransactionScope(); + + // Act - first call creates a new connection + var owner1 = new SqlConnection(); + var conn1 = GetConnection(owner1); + Assert.NotNull(conn1); + ReturnConnection(conn1, owner1); + + // Second call should retrieve the SAME connection from the transacted pool (LIFO) + var owner2 = new SqlConnection(); + var conn2 = GetConnection(owner2); + Assert.NotNull(conn2); + Assert.Same(conn1, conn2); + + ReturnConnection(conn2, owner2); + scope.Complete(); + } + + [Fact] + public async Task GetConnectionAsync_UnderTransaction_ReturnsSameConnectionFromTransactedPool() + { + // Arrange + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + var transaction = Transaction.Current; + + // Act - first call creates a new connection + var owner1 = new SqlConnection(); + var conn1 = await GetConnectionAsync(owner1, transaction: transaction); + Assert.NotNull(conn1); + ReturnConnection(conn1, owner1); + + // Second call should retrieve the SAME connection from the transacted pool + var owner2 = new SqlConnection(); + var conn2 = await GetConnectionAsync(owner2, transaction: transaction); + Assert.NotNull(conn2); + Assert.Same(conn1, conn2); + + ReturnConnection(conn2, owner2); + scope.Complete(); + } + + [Fact] + public void GetConnection_WithTransactionAffinityDisabled_SkipsTransactedPool() + { + // Arrange + _pool.Shutdown(); + _pool.Clear(); + _pool = CreatePool(hasTransactionAffinity: false); + + using var scope = new TransactionScope(); + + // Act + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + + // Assert - even though a transaction is active, transacted pool is not used + Assert.Empty(_pool.TransactedConnectionPool.TransactedConnections); + + scope.Complete(); + } + + #endregion + + #region Transaction Lifecycle Tests + + [Fact] + public void TransactionCommit_ClearsTransactedPool() + { + // Arrange & Act + using (var scope = new TransactionScope()) + { + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + + // While transaction is active, connection should be in transacted pool + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections); + + scope.Complete(); + } + + // Assert - after transaction completes, transacted pool should be empty + AssertPoolMetrics(); + } + + [Fact] + public void TransactionRollback_ClearsTransactedPool() + { + // Arrange & Act + using (var scope = new TransactionScope()) + { + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections); + + // Don't call scope.Complete() — triggers rollback + } + + // Assert - transacted pool should be empty after rollback too + AssertPoolMetrics(); + } + + [Fact] + public void MultipleGetReturn_SameTransaction_ReusesConnection() + { + // Arrange + using var scope = new TransactionScope(); + var transaction = Transaction.Current; + Assert.NotNull(transaction); + + // Act - get and return multiple times within same transaction + for (int i = 0; i < 10; i++) + { + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + } + + // Assert - only one connection should be in the transacted pool + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections[transaction]); + + scope.Complete(); + } + + [Fact] + public async Task MultipleGetReturn_SameTransaction_Async_ReusesConnection() + { + // Arrange + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + var transaction = Transaction.Current; + Assert.NotNull(transaction); + + // Act - get and return multiple times within same transaction + for (int i = 0; i < 10; i++) + { + var owner = new SqlConnection(); + var conn = await GetConnectionAsync(owner, transaction: transaction); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + } + + // Assert - only one connection should be in the transacted pool + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections[transaction]); + + scope.Complete(); + } + + [Fact] + public void AlternatingCommitAndRollback_MaintainsConsistentState() + { + // Act - alternate between commit and rollback + for (int i = 0; i < 20; i++) + { + using var scope = new TransactionScope(); + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + + if (i % 2 == 0) + { + scope.Complete(); + } + // else: rollback (no Complete) + } + + // Assert + AssertPoolMetrics(); + } + + #endregion + + #region Nested Transaction Tests + + [Fact] + public void NestedTransaction_Required_SharesSameTransactedEntry() + { + // Arrange + using var outerScope = new TransactionScope(); + var outerTxn = Transaction.Current; + Assert.NotNull(outerTxn); + + var owner1 = new SqlConnection(); + var conn1 = GetConnection(owner1); + Assert.NotNull(conn1); + ReturnConnection(conn1, owner1); + + // Act - nested scope with Required shares the same transaction + using (var innerScope = new TransactionScope(TransactionScopeOption.Required)) + { + Assert.Same(outerTxn, Transaction.Current); + + var owner2 = new SqlConnection(); + var conn2 = GetConnection(owner2); + Assert.NotNull(conn2); + Assert.Same(conn1, conn2); // Same transaction -> same connection from transacted pool + ReturnConnection(conn2, owner2); + + // Only one transaction tracked + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections); + + innerScope.Complete(); + } + + outerScope.Complete(); + } + + [Fact] + public void NestedTransaction_RequiresNew_CreatesSeparateTransactedEntry() + { + // Arrange + using var outerScope = new TransactionScope(); + var outerTxn = Transaction.Current; + Assert.NotNull(outerTxn); + + var owner1 = new SqlConnection(); + var conn1 = GetConnection(owner1); + Assert.NotNull(conn1); + ReturnConnection(conn1, owner1); + + // Act - nested scope with RequiresNew creates a new transaction + using (var innerScope = new TransactionScope(TransactionScopeOption.RequiresNew)) + { + var innerTxn = Transaction.Current; + Assert.NotNull(innerTxn); + Assert.NotEqual(outerTxn, innerTxn); + + var owner2 = new SqlConnection(); + var conn2 = GetConnection(owner2); + Assert.NotNull(conn2); + Assert.NotSame(conn1, conn2); // Different transaction -> different connection + ReturnConnection(conn2, owner2); + + // Two separate transactions tracked + Assert.Equal(2, _pool.TransactedConnectionPool.TransactedConnections.Count); + + innerScope.Complete(); + } + + outerScope.Complete(); + } + + [Fact] + public void NestedTransaction_RequiresNew_CompletesIndependently() + { + // Arrange & Act + using (var outerScope = new TransactionScope()) + { + var owner1 = new SqlConnection(); + var conn1 = GetConnection(owner1); + Assert.NotNull(conn1); + ReturnConnection(conn1, owner1); + + using (var innerScope = new TransactionScope(TransactionScopeOption.RequiresNew)) + { + var owner2 = new SqlConnection(); + var conn2 = GetConnection(owner2); + Assert.NotNull(conn2); + ReturnConnection(conn2, owner2); + innerScope.Complete(); + } + + // Inner transaction completed - its entry should be cleared + // Outer transaction entry should still exist + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections); + + outerScope.Complete(); + } + + // Both completed + AssertPoolMetrics(); + } + + [Fact] + public void DeeplyNestedTransactions_RequiresNew_AllTrackedSeparately() + { + // Arrange & Act + using var scope1 = new TransactionScope(); + var owner1 = new SqlConnection(); + var conn1 = GetConnection(owner1); + ReturnConnection(conn1, owner1); + + using var scope2 = new TransactionScope(TransactionScopeOption.RequiresNew); + var owner2 = new SqlConnection(); + var conn2 = GetConnection(owner2); + ReturnConnection(conn2, owner2); + + using var scope3 = new TransactionScope(TransactionScopeOption.RequiresNew); + var owner3 = new SqlConnection(); + var conn3 = GetConnection(owner3); + ReturnConnection(conn3, owner3); + + // Assert - three separate transactions tracked + Assert.Equal(3, _pool.TransactedConnectionPool.TransactedConnections.Count); + + scope3.Complete(); + scope2.Complete(); + scope1.Complete(); + } + + [Fact] + public void DeeplyNestedTransactions_Required_AllShareOneEntry() + { + // Arrange & Act + using var scope1 = new TransactionScope(); + var txn = Transaction.Current; + var owner1 = new SqlConnection(); + var conn1 = GetConnection(owner1); + ReturnConnection(conn1, owner1); + + using var scope2 = new TransactionScope(TransactionScopeOption.Required); + Assert.Same(txn, Transaction.Current); + var owner2 = new SqlConnection(); + var conn2 = GetConnection(owner2); + Assert.Same(conn1, conn2); + ReturnConnection(conn2, owner2); + + using var scope3 = new TransactionScope(TransactionScopeOption.Required); + Assert.Same(txn, Transaction.Current); + var owner3 = new SqlConnection(); + var conn3 = GetConnection(owner3); + Assert.Same(conn1, conn3); + ReturnConnection(conn3, owner3); + + // Assert - single transaction entry + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections); + + scope3.Complete(); + scope2.Complete(); + scope1.Complete(); + } + + #endregion + + #region Mixed Transacted and Non-Transacted Tests + + [Fact] + public void MixedWorkload_AlternatingTransactedAndNonTransacted() + { + // Act - alternate between transacted and non-transacted + for (int i = 0; i < 10; i++) + { + if (i % 2 == 0) + { + using var scope = new TransactionScope(); + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + scope.Complete(); + } + else + { + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + } + } + + // Assert + AssertPoolMetrics(); + } + + #endregion + + #region Shared Transaction Tests + + [Fact] + public void SharedTransaction_DependentScopes_UseTransactedPool() + { + // Arrange + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + var transaction = Transaction.Current; + Assert.NotNull(transaction); + + // Act - first connection + var owner1 = new SqlConnection(); + var conn1 = GetConnection(owner1); + Assert.NotNull(conn1); + ReturnConnection(conn1, owner1); + + // Use dependent scope on same transaction + using (var innerScope = new TransactionScope(transaction)) + { + Assert.Same(transaction, Transaction.Current); + var owner2 = new SqlConnection(); + var conn2 = GetConnection(owner2); + Assert.NotNull(conn2); + Assert.Same(conn1, conn2); // Same transaction -> same connection + ReturnConnection(conn2, owner2); + innerScope.Complete(); + } + + // Assert - still one transaction entry + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections); + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections[transaction]); + + scope.Complete(); + } + + #endregion + + #region Pool Saturation with Transactions Tests + + [Fact] + public void PoolSaturation_BlocksUntilConnectionAvailable() + { + // Arrange - small pool + _pool.Shutdown(); + _pool.Clear(); + _pool = CreatePool(maxPoolSize: 1); + + using var allAcquired = new ManualResetEventSlim(false); + using var releaseFirst = new ManualResetEventSlim(false); + + var saturatingTask = Task.Run(() => + { + using var scope = new TransactionScope(); + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + + allAcquired.Set(); // Signal that this connection is held + + Assert.True(releaseFirst.Wait(TimeSpan.FromSeconds(15)), + "Timed out waiting for releaseFirst signal."); + + ReturnConnection(conn, owner); + scope.Complete(); + }); + + Assert.True(allAcquired.Wait(TimeSpan.FromSeconds(10)), + "Timed out waiting for connection to be acquired."); + Assert.Equal(1, _pool.Count); + + using var acquired = new ManualResetEventSlim(false); + var waitingTask = Task.Run(() => + { + using var scope = new TransactionScope(); + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + acquired.Set(); + ReturnConnection(conn, owner); + scope.Complete(); + }); + + // Give the waiting task time to block — it should NOT complete yet + Assert.False(acquired.Wait(TimeSpan.FromMilliseconds(500)), + "Waiting task should not have acquired a connection while pool is saturated"); + + // Release one connection to unblock the waiting task + releaseFirst.Set(); + + // Now the waiting task should complete + Assert.True(waitingTask.Wait(TimeSpan.FromSeconds(15)), + "Waiting task should have completed after a connection was released"); + Assert.True(acquired.IsSet); + + // Cleanup remaining held connections + Task.WaitAll(saturatingTask); + } + + #endregion + + #region Controlled Concurrency Tests + + [Fact] + public void TwoThreads_SharedTransaction_AccessSameTransactedEntry() + { + // Arrange + // Use 3-phase synchronization so task1 gets AND returns before task2 requests. + // This ensures the connection is back in the transacted pool for task2 to reuse. + using var task1Returned = new ManualResetEventSlim(false); + using var task2Done = new ManualResetEventSlim(false); + DbConnectionInternal? connFromTask1 = null; + DbConnectionInternal? connFromTask2 = null; + + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + var transaction = Transaction.Current; + Assert.NotNull(transaction); + + // Act - two threads sharing the same transaction, sequenced so the + // transacted pool can vend the same connection to both. + var task1 = Task.Run(() => + { + using var innerScope = new TransactionScope(transaction); + var owner = new SqlConnection(); + connFromTask1 = GetConnection(owner); + Assert.NotNull(connFromTask1); + + // Return the connection so it's available in the transacted pool + ReturnConnection(connFromTask1, owner); + innerScope.Complete(); + + task1Returned.Set(); // Signal: connection is back in the transacted pool + }); + + var task2 = Task.Run(() => + { + // Wait until task1 has returned the connection to the transacted pool + Assert.True(task1Returned.Wait(TimeSpan.FromSeconds(10)), + "Timed out waiting for task1 to return its connection."); + + using var innerScope = new TransactionScope(transaction); + var owner = new SqlConnection(); + connFromTask2 = GetConnection(owner); + Assert.NotNull(connFromTask2); + ReturnConnection(connFromTask2, owner); + innerScope.Complete(); + }); + + Task.WaitAll(task1, task2); + + // Both tasks should have received the same connection via the transacted pool + Assert.Same(connFromTask1, connFromTask2); + scope.Complete(); + } + + [Fact] + public async Task TwoThreads_SeparateTransactions_Async_IsolatedTransactedEntries() + { + // Arrange + using var barrier = new SemaphoreSlim(0, 2); + + // Act + var task1 = Task.Run(async () => + { + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + var transaction = Transaction.Current; + var owner = new SqlConnection(); + var conn = await GetConnectionAsync(owner, transaction: transaction); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + + barrier.Release(); // Signal ready + await barrier.WaitAsync(); // Wait for other task + + scope.Complete(); + }); + + var task2 = Task.Run(async () => + { + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + var transaction = Transaction.Current; + var owner = new SqlConnection(); + var conn = await GetConnectionAsync(owner, transaction: transaction); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + + barrier.Release(); // Signal ready + await barrier.WaitAsync(); // Wait for other task + + scope.Complete(); + }); + + await Task.WhenAll(task1, task2); + + // Assert + AssertPoolMetrics(); + } + + #endregion + + #region Pool Shutdown with Transactions Tests + + [Fact] + public void PoolShutdown_AfterTransactionComplete_NoLeaks() + { + // Arrange + using (var scope = new TransactionScope()) + { + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + scope.Complete(); + } + + // Act + _pool.Shutdown(); + + // Assert + AssertPoolMetrics(); + } + + [Fact] + public void PoolShutdown_WhileConnectionHeld_NoException() + { + // Arrange + using var scope = new TransactionScope(); + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + + // Act - shutdown while connection is held (not yet returned) + _pool.Shutdown(); + + // Return after shutdown — the pool deactivates and disposes the connection + // rather than returning it to the pool. Verify this doesn't throw. + ReturnConnection(conn, owner); + + // Assert + // The connection should have been deactivated and disposed (not returned to the pool). + // After Dispose(), IsConnectionDoomed is set to true and Pool is set to null. + Assert.True(conn.IsConnectionDoomed, + "Connection should be doomed after returning to a shut-down pool."); + Assert.Null(conn.Pool); + } + + #endregion + + #region Transaction Complete Before Return Tests + + [Fact] + public void TransactionComplete_ThenReturn_ConnectionStillReturned() + { + // Arrange + var owner = new SqlConnection(); + DbConnectionInternal conn; + + using (var scope = new TransactionScope()) + { + conn = GetConnection(owner); + Assert.NotNull(conn); + scope.Complete(); + } + // Transaction is fully disposed here + + // Act - return connection after transaction ended + ReturnConnection(conn, owner); + + // Assert - no leak, pool metrics consistent + AssertPoolMetrics(); + Assert.True(_pool.Count > 0, "Pool should still have the connection"); + } + + #endregion + + #region Sequential Transaction Isolation Tests + + [Fact] + public void SequentialTransactions_EachGetsOwnTransactedEntry() + { + // Act - create multiple sequential transactions + for (int i = 0; i < 5; i++) + { + using var scope = new TransactionScope(); + var transaction = Transaction.Current; + Assert.NotNull(transaction); + + var owner = new SqlConnection(); + var conn = GetConnection(owner); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + + // Only the current transaction should be tracked + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections); + Assert.True(_pool.TransactedConnectionPool.TransactedConnections.ContainsKey(transaction)); + + scope.Complete(); + } + + // Assert - after all are done, pool should be clean + AssertPoolMetrics(); + } + + [Fact] + public async Task SequentialTransactions_Async_EachGetsOwnTransactedEntry() + { + // Act - create multiple sequential transactions + for (int i = 0; i < 5; i++) + { + using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); + var transaction = Transaction.Current; + Assert.NotNull(transaction); + + var owner = new SqlConnection(); + var conn = await GetConnectionAsync(owner, transaction: transaction); + Assert.NotNull(conn); + ReturnConnection(conn, owner); + + Assert.Single(_pool.TransactedConnectionPool.TransactedConnections); + Assert.True(_pool.TransactedConnectionPool.TransactedConnections.ContainsKey(transaction)); + + scope.Complete(); + } + + // Assert + AssertPoolMetrics(); + } + + [Fact] + public void SequentialTransactions_CanReuseConnections() + { + // Act + DbConnectionInternal conn1; + DbConnectionInternal conn2; + Transaction? txn1; + Transaction? txn2; + + using (var scope1 = new TransactionScope()) + { + txn1 = Transaction.Current; + var owner1 = new SqlConnection(); + conn1 = GetConnection(owner1); + Assert.NotNull(conn1); + ReturnConnection(conn1, owner1); + scope1.Complete(); + } + + using (var scope2 = new TransactionScope()) + { + txn2 = Transaction.Current; + var owner2 = new SqlConnection(); + conn2 = GetConnection(owner2); + Assert.NotNull(conn2); + ReturnConnection(conn2, owner2); + scope2.Complete(); + } + + // Assert + // The connection was returned to the general pool and picked up by the second transaction + Assert.NotSame(txn1, txn2); + Assert.Same(conn1, conn2); + AssertPoolMetrics(); + } + + #endregion + + #region Mock Classes + + internal class MockSqlConnectionFactory : SqlConnectionFactory + { + protected override DbConnectionInternal CreateConnection( + DbConnectionOptions options, + DbConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + DbConnectionOptions userOptions) + { + return new MockDbConnectionInternal(); + } + } + + internal class MockDbConnectionInternal : DbConnectionInternal + { + private static int s_nextId = 1; + public int MockId { get; } = Interlocked.Increment(ref s_nextId); + + public override string ServerVersion => "Mock"; + + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) + { + throw new NotImplementedException(); + } + + public override void EnlistTransaction(Transaction? transaction) + { + if (transaction != null) + { + EnlistedTransaction = transaction; + } + } + + protected override void Activate(Transaction? transaction) + { + EnlistedTransaction = transaction; + } + + protected override void Deactivate() + { + } + + public override string ToString() => $"MockConnection_{MockId}"; + + internal override void ResetConnection() + { + } + } + + #endregion +} From f8183c1aadad0879520dfd3d565bd26f7f58e3ad Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:25:11 -0300 Subject: [PATCH 022/298] Add GitHub Actions workflow to automate cherry-pick hotfix PRs (#4106) --- .github/copilot-instructions.md | 4 + .github/scripts/cherry-pick-to-release.sh | 202 ++++++++++++ .github/scripts/extract-hotfix-versions.sh | 136 ++++++++ .github/scripts/tests/README.md | 176 +++++++++++ .../scripts/tests/cherry-pick-to-release.bats | 294 ++++++++++++++++++ .../tests/extract-hotfix-versions.bats | 244 +++++++++++++++ .github/workflows/cherry-pick-hotfix.yml | 113 +++++++ .github/workflows/codeql.yml | 1 + AGENTS.md | 9 + ...qlclient-ci-package-reference-pipeline.yml | 16 +- ...qlclient-ci-project-reference-pipeline.yml | 16 +- .../sqlclient-pr-package-ref-pipeline.yml | 6 +- .../sqlclient-pr-project-ref-pipeline.yml | 6 +- 13 files changed, 1205 insertions(+), 18 deletions(-) create mode 100755 .github/scripts/cherry-pick-to-release.sh create mode 100755 .github/scripts/extract-hotfix-versions.sh create mode 100644 .github/scripts/tests/README.md create mode 100644 .github/scripts/tests/cherry-pick-to-release.bats create mode 100644 .github/scripts/tests/extract-hotfix-versions.bats create mode 100644 .github/workflows/cherry-pick-hotfix.yml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 272a0c2164..4c80da66ff 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -129,6 +129,10 @@ When a new issue is created, follow these steps: - Suggest release note entries for fixes by updating files under `release-notes/` or by using the `release-notes` prompt (instead of editing `CHANGELOG.md` directly). - Tag reviewers based on `CODEOWNERS` file +## 🌿 Branch Naming +- All branches created by AI agents **must** use the `dev/automation/` prefix (e.g. `dev/automation/fix-connection-timeout`). +- Do **not** create branches directly under `main`, `dev/`, or any other top-level prefix. + ## 🧠 Contextual Awareness - All source code is in `src/Microsoft.Data.SqlClient/src/`. Do NOT add code to legacy `netfx/src/` or `netcore/src/` directories. - Only `ref/` folders in `netcore/ref/` and `netfx/ref/` remain active for defining the public API surface. diff --git a/.github/scripts/cherry-pick-to-release.sh b/.github/scripts/cherry-pick-to-release.sh new file mode 100755 index 0000000000..8b3172cc50 --- /dev/null +++ b/.github/scripts/cherry-pick-to-release.sh @@ -0,0 +1,202 @@ +#!/usr/bin/env bash +################################################################################# +# 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. # +################################################################################# +# +# cherry-pick-to-release.sh +# +# Cherry-picks a merge commit from the default branch onto a release branch +# and opens a pull request for the result. If the cherry-pick conflicts, an +# empty-commit placeholder PR is created with manual resolution instructions. +# +# OVERVIEW +# -------- +# This script performs the following steps: +# +# 1. Derive the target release branch from the version's major.minor +# (e.g. "7.0.1" → release/7.0). +# +# 2. Check whether the commit's patch is already present on the target +# branch (via 'git cherry'). If so, exit cleanly — nothing to do. +# +# 3. Detect whether the merge commit is a true merge (2+ parents) or a +# squash-merge (1 parent). True merges require '--mainline 1'. +# +# 4. Attempt the cherry-pick: +# - On success: push the branch, look up the milestone, create a PR. +# - On conflict: abort, push an empty-commit placeholder, create a +# "CONFLICTS" PR with manual resolution instructions. +# +# 5. Milestone lookup is best-effort. If the milestone doesn't exist yet +# the PR is created without one and a warning note is added to the body. +# +# REQUIRED ENVIRONMENT VARIABLES +# ------------------------------ +# VERSION Full hotfix version, e.g. "7.0.1". +# MERGE_COMMIT_SHA SHA of the merge commit on the default branch. +# PR_NUMBER Number of the original PR that was merged. +# PR_TITLE Title of the original PR (used in cherry-pick PR title). +# GH_TOKEN GitHub token for 'gh' CLI authentication. +# GITHUB_REPOSITORY Owner/repo (e.g. "dotnet/SqlClient"). Set by Actions. +# +# OUTPUTS +# ------- +# On success or conflict, a new PR is created on GitHub. +# On already-applied, the script exits 0 with a notice. +# +# USAGE +# Typically called from the cherry-pick-hotfix.yml workflow. +# The git working directory must have full history (fetch-depth: 0) and +# user.name / user.email must be configured before calling this script. +# +# Local testing example (dry-run — comment out 'gh pr create' calls): +# +# export VERSION="7.0.1" +# export MERGE_COMMIT_SHA="abc123" +# export PR_NUMBER=42 +# export PR_TITLE="Fix connection timeout" +# export GH_TOKEN="ghp_..." +# export GITHUB_REPOSITORY="dotnet/SqlClient" +# bash .github/scripts/cherry-pick-to-release.sh +# +################################################################################# +set -euo pipefail + +# -- Runtime help ------------------------------------------------------------- +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + # Print the header comment block (between the license banner and the + # closing banner), stripping the leading '# ' prefix. + awk '/^#{2,}$/ { n++; next } n == 2 { sub(/^# ?/, ""); print }' "$0" + exit 0 +fi + +# -- Input validation --------------------------------------------------------- +: "${VERSION:?VERSION environment variable is required}" +: "${MERGE_COMMIT_SHA:?MERGE_COMMIT_SHA environment variable is required}" +: "${PR_NUMBER:?PR_NUMBER environment variable is required}" +: "${PR_TITLE:?PR_TITLE environment variable is required}" +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY environment variable is required}" + +# -- Step 1: Derive target branch from major.minor --------------------------- +# "7.0.1" → "7.0", so target branch is "release/7.0". +# Use a bash regex for portability (grep -P is not available on macOS BSD grep). +if [[ "${VERSION}" =~ ^([0-9]+)\.([0-9]+) ]]; then + BRANCH_VERSION="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" +else + BRANCH_VERSION="" +fi +if [[ -z "${BRANCH_VERSION}" ]]; then + echo "::error::Could not parse major.minor from version '${VERSION}'." + exit 1 +fi + +TARGET_BRANCH="release/${BRANCH_VERSION}" +CHERRY_PICK_BRANCH="dev/automation/pr-${PR_NUMBER}-to-${VERSION}" + +echo "Version: ${VERSION}" +echo "Target branch: ${TARGET_BRANCH}" +echo "Cherry-pick branch: ${CHERRY_PICK_BRANCH}" +echo "Merge commit: ${MERGE_COMMIT_SHA}" + +# Ensure the target branch ref is available locally. +git fetch origin "${TARGET_BRANCH}" + +# -- Step 2: Check if the patch is already applied ---------------------------- +# 'git cherry ' lists commits in .. and +# marks each with '-' (patch already on ) or '+' (not yet applied). +# By passing =MERGE_COMMIT_SHA and =MERGE_COMMIT_SHA^ we scope +# the check to exactly one commit — the PR being cherry-picked. +if git cherry "origin/${TARGET_BRANCH}" "${MERGE_COMMIT_SHA}" "${MERGE_COMMIT_SHA}^" \ + | grep -q '^-'; then + echo "::notice::Commit ${MERGE_COMMIT_SHA} is already applied on" \ + "${TARGET_BRANCH}. Skipping cherry-pick." + exit 0 +fi + +# Create the cherry-pick working branch from the target release branch. +git checkout -b "${CHERRY_PICK_BRANCH}" "origin/${TARGET_BRANCH}" + +# -- Step 3: Detect merge commit type ----------------------------------------- +# True merge commits have 2+ parents and require '--mainline 1' to tell git +# which parent's tree to diff against (the first parent = the target branch). +# Squash-merge commits have exactly 1 parent and must NOT use --mainline. +# +# 'git rev-list --parents -n1 ' outputs: [ ...] +# awk counts fields and subtracts 1 (the SHA itself) to get the parent count. +PARENT_COUNT=$(git rev-list --parents -n1 "${MERGE_COMMIT_SHA}" \ + | awk '{print NF - 1}') +MAINLINE_FLAG="" +if [[ "${PARENT_COUNT}" -gt 1 ]]; then + MAINLINE_FLAG="--mainline 1" + echo "Merge commit has ${PARENT_COUNT} parents — using --mainline 1." +else + echo "Squash-merge commit (single parent) — no --mainline flag needed." +fi + +# -- Helper: look up milestone ------------------------------------------------ +# Milestone assignment is best-effort. If the milestone doesn't exist yet, the +# PR is created without one and a note is appended to the PR body. +lookup_milestone() { + local version="$1" + MILESTONE_ARG="" + MILESTONE_NOTE="" + + if gh api "repos/${GITHUB_REPOSITORY}/milestones" --paginate \ + --field state=open --jq '.[].title' | grep -qx "${version}"; then + MILESTONE_ARG="--milestone ${version}" + echo "Milestone '${version}' found." + else + echo "::warning::Milestone '${version}' does not exist." \ + "PR will be created without a milestone." + MILESTONE_NOTE=$'\n\n> **Note:** Milestone `'"${version}"'` does not exist yet. Please create it and assign this PR manually.' + fi +} + +# -- Step 4: Attempt the cherry-pick ------------------------------------------ +# Options (--mainline) must precede the commit operand. +if git cherry-pick ${MAINLINE_FLAG} "${MERGE_COMMIT_SHA}"; then + # --- Success path --- + echo "Cherry-pick succeeded. Pushing branch and creating PR." + git push origin "${CHERRY_PICK_BRANCH}" + + lookup_milestone "${VERSION}" + + gh pr create \ + --base "${TARGET_BRANCH}" \ + --head "${CHERRY_PICK_BRANCH}" \ + --title "[${VERSION} Cherry-pick] ${PR_TITLE}" \ + --body "Cherry-pick of #${PR_NUMBER} (${MERGE_COMMIT_SHA}) into \`${TARGET_BRANCH}\`.${MILESTONE_NOTE}" \ + ${MILESTONE_ARG} +else + # --- Conflict path --- + echo "::error::Cherry-pick of ${MERGE_COMMIT_SHA} failed due to conflicts." + git cherry-pick --abort + + # Build the cherry-pick command for inclusion in the conflict-resolution + # instructions. Options must precede the commit SHA. + CHERRY_PICK_CMD="git cherry-pick" + if [[ -n "${MAINLINE_FLAG}" ]]; then + CHERRY_PICK_CMD="${CHERRY_PICK_CMD} ${MAINLINE_FLAG}" + fi + CHERRY_PICK_CMD="${CHERRY_PICK_CMD} ${MERGE_COMMIT_SHA}" + + # Create a branch with an empty commit so a PR can be opened. The PR body + # contains step-by-step instructions for manual conflict resolution. + git checkout "origin/${TARGET_BRANCH}" + git checkout -B "${CHERRY_PICK_BRANCH}" + git commit --allow-empty \ + -m "Cherry-pick of #${PR_NUMBER} requires manual resolution" \ + -m "To resolve, run: ${CHERRY_PICK_CMD}" + git push origin "${CHERRY_PICK_BRANCH}" + + lookup_milestone "${VERSION}" + + gh pr create \ + --base "${TARGET_BRANCH}" \ + --head "${CHERRY_PICK_BRANCH}" \ + --title "[${VERSION} Cherry-pick - CONFLICTS] ${PR_TITLE}" \ + ${MILESTONE_ARG} \ + --body $'Cherry-pick of #'"${PR_NUMBER}"' ('"${MERGE_COMMIT_SHA}"') into `'"${TARGET_BRANCH}"'` **failed due to merge conflicts**.'"${MILESTONE_NOTE}"$'\n\nPlease resolve manually:\n```bash\ngit fetch origin\ngit checkout '"${CHERRY_PICK_BRANCH}"'\n'"${CHERRY_PICK_CMD}"$'\n# resolve conflicts\ngit push origin '"${CHERRY_PICK_BRANCH}"' --force\n```' +fi diff --git a/.github/scripts/extract-hotfix-versions.sh b/.github/scripts/extract-hotfix-versions.sh new file mode 100755 index 0000000000..b7c702a8f8 --- /dev/null +++ b/.github/scripts/extract-hotfix-versions.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +################################################################################# +# 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. # +################################################################################# +# +# extract-hotfix-versions.sh +# +# Parses "Hotfix X.Y.Z" labels from a merged GitHub PR and emits a JSON array +# of version strings suitable for use as a GitHub Actions matrix dimension. +# +# OVERVIEW +# -------- +# This script handles two distinct trigger scenarios: +# +# 1. 'closed' event — The PR was just merged. ALL "Hotfix X.Y.Z" labels on +# the PR are processed, emitting one version per valid label. +# +# 2. 'labeled' event — A label was added to an already-merged PR. Only the +# NEWLY ADDED label is considered, and only if a cherry-pick for that +# version hasn't already been created (branch or PR exists). +# +# Label names must match the exact pattern "Hotfix .." +# (e.g. "Hotfix 7.0.1"). All other labels are silently ignored. +# +# REQUIRED ENVIRONMENT VARIABLES +# ------------------------------ +# LABELS Comma-separated list of all label names on the PR. +# EVENT_ACTION The GitHub event action: "closed" or "labeled". +# EVENT_LABEL For 'labeled' events, the name of the label that was added. +# Empty or unset for 'closed' events. +# PR_NUMBER The pull request number (used to derive cherry-pick branch names). +# GH_TOKEN GitHub token for API calls (gh CLI auth). +# GITHUB_REPOSITORY Owner/repo (e.g. "dotnet/SqlClient"). Set automatically by Actions. +# +# OUTPUTS +# ------- +# Writes to $GITHUB_OUTPUT: +# versions= e.g. versions=["7.0.1","8.0.0"] +# +# An empty array (versions=[]) means no work is needed. +# The script exits with code 1 if the 'closed' event has no valid labels. +# +# USAGE +# Called from the cherry-pick-hotfix.yml workflow. Can also be run locally +# for testing by setting the required environment variables and providing a +# writable GITHUB_OUTPUT file: +# +# export LABELS="Hotfix 7.0.1,bug" +# export EVENT_ACTION="closed" +# export PR_NUMBER=42 +# export GITHUB_OUTPUT=$(mktemp) +# bash .github/scripts/extract-hotfix-versions.sh +# cat "$GITHUB_OUTPUT" +# +################################################################################# +set -euo pipefail + +# -- Runtime help ------------------------------------------------------------- +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + # Print the header comment block (between the license banner and the + # closing banner), stripping the leading '# ' prefix. + awk '/^#{2,}$/ { n++; next } n == 2 { sub(/^# ?/, ""); print }' "$0" + exit 0 +fi + +# -- Input validation --------------------------------------------------------- +: "${LABELS:?LABELS environment variable is required}" +: "${EVENT_ACTION:?EVENT_ACTION environment variable is required}" +: "${PR_NUMBER:?PR_NUMBER environment variable is required}" +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY environment variable is required}" + +# -- 'labeled' event: process only the newly added label ---------------------- +if [[ "${EVENT_ACTION}" == "labeled" ]]; then + # Extract version from the new label. If it doesn't match "Hotfix X.Y.Z", + # this is a non-hotfix label — emit empty matrix and exit cleanly. + if [[ "${EVENT_LABEL:-}" =~ ^Hotfix\ ([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + CANDIDATE="${BASH_REMATCH[1]}" + else + CANDIDATE="" + fi + + if [[ -z "${CANDIDATE}" ]]; then + echo "Label '${EVENT_LABEL:-}' is not a valid 'Hotfix X.Y.Z' label. Skipping." + echo "versions=[]" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + # Guard against duplicate cherry-picks. If the cherry-pick branch already + # exists on the remote, or a PR (open, closed, or merged) was already created + # from it, there is nothing left to do. + # + # NOTE: We use the GitHub API rather than 'git ls-remote' because the + # detect-versions job does not check out the repository (no .git directory). + CHERRY_PICK_BRANCH="dev/automation/pr-${PR_NUMBER}-to-${CANDIDATE}" + + if gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/${CHERRY_PICK_BRANCH}" \ + --silent 2>/dev/null; then + echo "Cherry-pick branch '${CHERRY_PICK_BRANCH}' already exists. Skipping." + echo "versions=[]" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + EXISTING_PR=$(gh pr list --repo "${GITHUB_REPOSITORY}" --head "${CHERRY_PICK_BRANCH}" --state all \ + --json number --jq 'length') + if [[ "${EXISTING_PR}" -gt 0 ]]; then + echo "A cherry-pick PR from '${CHERRY_PICK_BRANCH}' already exists. Skipping." + echo "versions=[]" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + VERSIONS="${CANDIDATE}" +else + # -- 'closed' event: process all hotfix labels on the PR -------------------- + # Split by comma, keep only labels matching "Hotfix X.Y.Z", extract the version. + # Use sed -E for portable extended regex (works on both GNU and BSD sed). + VERSIONS=$(echo "${LABELS}" | tr ',' '\n' \ + | sed -nE 's/^Hotfix ([0-9]+\.[0-9]+\.[0-9]+)$/\1/p') +fi + +# -- Validate that at least one version was found ---------------------------- +if [[ -z "${VERSIONS}" ]]; then + echo "::error::No valid 'Hotfix X.Y.Z' label found. " \ + "Labels must match 'Hotfix ..'." + exit 1 +fi + +# -- Emit JSON array for the matrix strategy ---------------------------------- +# Convert the newline-separated version list into a compact JSON array. +# e.g. "7.0.1\n8.0.0" → ["7.0.1","8.0.0"] +JSON=$(echo "${VERSIONS}" \ + | jq -R -s -c 'split("\n") | map(select(length > 0))') + +echo "versions=${JSON}" >> "${GITHUB_OUTPUT}" +echo "Detected hotfix versions: ${JSON}" diff --git a/.github/scripts/tests/README.md b/.github/scripts/tests/README.md new file mode 100644 index 0000000000..9aac1c6c88 --- /dev/null +++ b/.github/scripts/tests/README.md @@ -0,0 +1,176 @@ +# Cherry-Pick Workflow Tests + +This directory contains tests for the shell scripts used by the +[cherry-pick-hotfix](./../../../.github/workflows/cherry-pick-hotfix.yml) GitHub Actions workflow. +These tests are intended to be run manually by developers when they are changing the associated +scripts, and not as part of any CI runs. + +## What is Bats? + +**[Bats](https://github.com/bats-core/bats-core)** (Bash Automated Testing System) is a +TAP-compliant testing framework for Bash scripts. Each `.bats` file contains one or more `@test` +blocks that run shell commands and assert outcomes using the built-in `run` helper. A test passes +when every command exits with code 0; it fails on the first non-zero exit. + +Key concepts: + +| Concept | Description | +| ------- | ----------- | +| `@test "name" { ... }` | Defines a single test case | +| `run ` | Captures stdout, stderr, and exit code into `$output` and `$status` | +| `setup()` | Runs before every `@test` — used to create temp files and set env vars | +| `teardown()` | Runs after every `@test` — used to clean up temp files | +| `[[ "$status" -eq 0 ]]` | Assert exit code | +| `[[ "$output" == *"text"* ]]` | Assert output contains a string | + +## Installing Bats + +### Linux (apt) + +```bash +sudo apt-get update && sudo apt-get install -y bats +``` + +### macOS (Homebrew) + +```bash +brew install bats-core +``` + +### From source (any platform) + +```bash +git clone https://github.com/bats-core/bats-core.git +cd bats-core +sudo ./install.sh /usr/local +``` + +## Additional Prerequisites + +The scripts under test also require: + +- **jq** — used to build JSON matrix output +- **gh** (GitHub CLI) — used for API calls and PR creation (mocked during tests, but must be on + `$PATH` for the test stubs to shadow it) + +Most CI runners and development machines have these pre-installed. If not: + +```bash +# Linux (apt) +sudo apt-get install -y jq gh + +# macOS (Homebrew) +brew install jq gh +``` + +### Verify installation + +```bash +bats --version +# Expected output: Bats 1.x.x +``` + +## Running the Tests + +All commands assume you are at the **repository root**. + +### Run all tests + +```bash +bats .github/scripts/tests/ +``` + +### Run a single test file + +```bash +bats .github/scripts/tests/extract-hotfix-versions.bats +bats .github/scripts/tests/cherry-pick-to-release.bats +``` + +### Run a specific test by name + +```bash +bats .github/scripts/tests/extract-hotfix-versions.bats \ + --filter "single Hotfix label" +``` + +### Verbose output (show each test name) + +```bash +bats --tap .github/scripts/tests/ +``` + +### Pretty output (requires bats-core 1.5+) + +```bash +bats --formatter pretty .github/scripts/tests/ +``` + +## Test Files + +| File | Tests | Covers | +| ---- | ----- | ------ | +| `extract-hotfix-versions.bats` | 18 | Label parsing, version extraction, matrix JSON output, edge cases (malformed labels, duplicates, `labeled` vs `closed` events) | +| `cherry-pick-to-release.bats` | 15 | Branch derivation, already-applied detection, clean cherry-pick, conflict handling, milestone lookup, PR creation, duplicate skip logic | + +## How the Tests Work + +Both test files use the same general approach: + +1. **`setup()`** creates a temporary directory and populates it with mock `git` and `gh` executables + — simple shell scripts that echo predetermined responses. Environment variables (`VERSION`, + `MERGE_COMMIT_SHA`, etc.) are set to known values. + +2. **`@test` blocks** call `run bash "$SCRIPT"` to execute the script under test in a subshell. The + mocks intercept all `git` and `gh` invocations, so no real repository or GitHub API access is + needed. + +3. **Assertions** check `$status` (exit code) and `$output` (combined stdout/stderr) for expected + values, error messages, GitHub Actions output file writes via `$GITHUB_OUTPUT`, or other + workflow commands such as `::error::` and `::notice::`. + +4. **`teardown()`** removes the temporary directory and mock binaries. + +### Example mock + +```bash +# Mock git that reports 2 parents (a merge commit) +cat > "${STUB_DIR}/git" <<'MOCK' +#!/usr/bin/env bash +case "$*" in + "rev-list --parents -n1 "*) echo "abc123 parent1 parent2" ;; + "cherry "*) echo "+ abc123" ;; + *) echo "git mock: $*" ;; +esac +MOCK +chmod +x "${STUB_DIR}/git" +``` + +The mock sits earlier on `$PATH` than the real `git`, so the script under test calls the mock +transparently. + +## Troubleshooting + +### `bats: command not found` + +Bats is not installed. See [Installing Bats](#installing-bats) above. + +### Tests fail with `permission denied` + +The scripts under `.github/scripts/` must be executable: + +```bash +chmod +x .github/scripts/*.sh +``` + +### A test fails unexpectedly + +Run with `set -x` tracing to see each command: + +```bash +bats --tap .github/scripts/tests/cherry-pick-to-release.bats \ + --filter "name of failing test" 2>&1 +``` + +Or add `echo "DEBUG: $variable" >&3` inside a test to print to the terminal (file descriptor 3 is +bats's "direct to terminal" channel). diff --git a/.github/scripts/tests/cherry-pick-to-release.bats b/.github/scripts/tests/cherry-pick-to-release.bats new file mode 100644 index 0000000000..b8d40a3b3e --- /dev/null +++ b/.github/scripts/tests/cherry-pick-to-release.bats @@ -0,0 +1,294 @@ +#!/usr/bin/env bats +################################################################################# +# 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. # +################################################################################# +# +# Tests for cherry-pick-to-release.sh +# +# Run with: bats .github/scripts/tests/cherry-pick-to-release.bats +# +# NOTE: These tests mock git and gh commands to validate the script's logic +# without requiring a real repository or GitHub API access. +# +# Dependencies: bats-core (https://github.com/bats-core/bats-core) +# +################################################################################# + +# Path to the script under test (relative to repo root). +SCRIPT=".github/scripts/cherry-pick-to-release.sh" + +# ── Helpers ────────────────────────────────────────────────────────────────── + +setup() { + # Create a directory for mock binaries that override real git/gh. + STUB_DIR="$(mktemp -d)" + export PATH="${STUB_DIR}:${PATH}" + + # Defaults — individual tests override as needed. + export VERSION="7.0.1" + export MERGE_COMMIT_SHA="abc123def456" + export PR_NUMBER="42" + export PR_TITLE="Fix connection timeout" + export GH_TOKEN="fake-token" + export GITHUB_REPOSITORY="dotnet/SqlClient" + export GITHUB_OUTPUT="$(mktemp)" +} + +teardown() { + rm -rf "${STUB_DIR}" + rm -f "${GITHUB_OUTPUT}" +} + +# Write a mock 'git' script. Each call to the mock appends a log line so +# tests can verify which git subcommands were executed and with what args. +write_git_mock() { + local body="$1" + cat > "${STUB_DIR}/git" <> "${STUB_DIR}/git.log" +${body} +STUB + chmod +x "${STUB_DIR}/git" +} + +# Write a mock 'gh' script. +write_gh_mock() { + local body="$1" + cat > "${STUB_DIR}/gh" <> "${STUB_DIR}/gh.log" +${body} +STUB + chmod +x "${STUB_DIR}/gh" +} + +# ── --help flag ────────────────────────────────────────────────────────────── + +@test "prints help text with --help" { + run bash "${SCRIPT}" --help + [ "$status" -eq 0 ] + [[ "$output" == *"Cherry-picks a merge commit"* ]] + [[ "$output" == *"REQUIRED ENVIRONMENT VARIABLES"* ]] +} + +@test "prints help text with -h" { + run bash "${SCRIPT}" -h + [ "$status" -eq 0 ] + [[ "$output" == *"Cherry-picks"* ]] +} + +# ── Input validation ───────────────────────────────────────────────────────── + +@test "fails when VERSION is unset" { + unset VERSION + run bash "${SCRIPT}" + [ "$status" -ne 0 ] + [[ "$output" == *"VERSION"* ]] +} + +@test "fails when MERGE_COMMIT_SHA is unset" { + unset MERGE_COMMIT_SHA + run bash "${SCRIPT}" + [ "$status" -ne 0 ] + [[ "$output" == *"MERGE_COMMIT_SHA"* ]] +} + +@test "fails when PR_NUMBER is unset" { + unset PR_NUMBER + run bash "${SCRIPT}" + [ "$status" -ne 0 ] + [[ "$output" == *"PR_NUMBER"* ]] +} + +@test "fails when PR_TITLE is unset" { + unset PR_TITLE + run bash "${SCRIPT}" + [ "$status" -ne 0 ] + [[ "$output" == *"PR_TITLE"* ]] +} + +@test "fails when GITHUB_REPOSITORY is unset" { + unset GITHUB_REPOSITORY + run bash "${SCRIPT}" + [ "$status" -ne 0 ] + [[ "$output" == *"GITHUB_REPOSITORY"* ]] +} + +# ── Version parsing ───────────────────────────────────────────────────────── + +@test "derives release/7.0 from version 7.0.1" { + write_git_mock ' + if [[ "$1" == "fetch" ]]; then exit 0; fi + if [[ "$1" == "cherry" ]]; then echo "+ abc123"; exit 0; fi + if [[ "$1" == "checkout" ]]; then exit 0; fi + if [[ "$1" == "rev-list" ]]; then echo "abc123 parent1"; exit 0; fi + if [[ "$1" == "cherry-pick" ]]; then exit 0; fi + if [[ "$1" == "push" ]]; then exit 0; fi + exit 0 + ' + write_gh_mock ' + if [[ "$1" == "api" ]]; then echo "7.0.1"; exit 0; fi + if [[ "$1" == "pr" ]]; then exit 0; fi + exit 0 + ' + + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + # Verify the git fetch targeted release/7.0. + grep -q "GIT: fetch origin release/7.0" "${STUB_DIR}/git.log" +} + +@test "derives release/8.0 from version 8.0.0" { + export VERSION="8.0.0" + write_git_mock ' + if [[ "$1" == "fetch" ]]; then exit 0; fi + if [[ "$1" == "cherry" ]]; then echo "+ abc123"; exit 0; fi + if [[ "$1" == "checkout" ]]; then exit 0; fi + if [[ "$1" == "rev-list" ]]; then echo "abc123 parent1"; exit 0; fi + if [[ "$1" == "cherry-pick" ]]; then exit 0; fi + if [[ "$1" == "push" ]]; then exit 0; fi + exit 0 + ' + write_gh_mock ' + if [[ "$1" == "api" ]]; then echo "8.0.0"; exit 0; fi + if [[ "$1" == "pr" ]]; then exit 0; fi + exit 0 + ' + + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + grep -q "GIT: fetch origin release/8.0" "${STUB_DIR}/git.log" +} + +@test "fails on unparseable version" { + export VERSION="bad" + run bash "${SCRIPT}" + [ "$status" -eq 1 ] + [[ "$output" == *"Could not parse"* ]] +} + +# ── Already-applied detection ─────────────────────────────────────────────── + +@test "exits cleanly when commit is already applied" { + write_git_mock ' + if [[ "$1" == "fetch" ]]; then exit 0; fi + # git cherry: "-" prefix means patch is already applied. + if [[ "$1" == "cherry" ]]; then echo "- abc123def456"; exit 0; fi + exit 0 + ' + + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [[ "$output" == *"already applied"* ]] + # Should NOT have attempted a cherry-pick. + ! grep -q "GIT: cherry-pick" "${STUB_DIR}/git.log" +} + +# ── Squash-merge detection (single parent) ────────────────────────────────── + +@test "does not use --mainline for squash merges" { + write_git_mock ' + if [[ "$1" == "fetch" ]]; then exit 0; fi + if [[ "$1" == "cherry" ]]; then echo "+ abc123"; exit 0; fi + if [[ "$1" == "checkout" ]]; then exit 0; fi + # Single parent: rev-list outputs "sha parent1" (2 fields → 1 parent). + if [[ "$1" == "rev-list" ]]; then echo "abc123def456 parent1"; exit 0; fi + if [[ "$1" == "cherry-pick" ]]; then exit 0; fi + if [[ "$1" == "push" ]]; then exit 0; fi + exit 0 + ' + write_gh_mock ' + if [[ "$1" == "api" ]]; then echo "7.0.1"; exit 0; fi + if [[ "$1" == "pr" ]]; then exit 0; fi + exit 0 + ' + + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [[ "$output" == *"single parent"* ]] + # cherry-pick should NOT include --mainline. + grep "GIT: cherry-pick" "${STUB_DIR}/git.log" | grep -qv "\-\-mainline" +} + +# ── True merge detection (multiple parents) ───────────────────────────────── + +@test "uses --mainline 1 for true merge commits" { + write_git_mock ' + if [[ "$1" == "fetch" ]]; then exit 0; fi + if [[ "$1" == "cherry" ]]; then echo "+ abc123"; exit 0; fi + if [[ "$1" == "checkout" ]]; then exit 0; fi + # Two parents: rev-list outputs "sha parent1 parent2" (3 fields → 2 parents). + if [[ "$1" == "rev-list" ]]; then echo "abc123def456 parent1 parent2"; exit 0; fi + if [[ "$1" == "cherry-pick" ]]; then exit 0; fi + if [[ "$1" == "push" ]]; then exit 0; fi + exit 0 + ' + write_gh_mock ' + if [[ "$1" == "api" ]]; then echo "7.0.1"; exit 0; fi + if [[ "$1" == "pr" ]]; then exit 0; fi + exit 0 + ' + + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [[ "$output" == *"--mainline 1"* ]] +} + +# ── Milestone lookup ──────────────────────────────────────────────────────── + +@test "warns when milestone does not exist" { + write_git_mock ' + if [[ "$1" == "fetch" ]]; then exit 0; fi + if [[ "$1" == "cherry" ]]; then echo "+ abc123"; exit 0; fi + if [[ "$1" == "checkout" ]]; then exit 0; fi + if [[ "$1" == "rev-list" ]]; then echo "abc123def456 parent1"; exit 0; fi + if [[ "$1" == "cherry-pick" ]]; then exit 0; fi + if [[ "$1" == "push" ]]; then exit 0; fi + if [[ "$1" == "config" ]]; then exit 0; fi + exit 0 + ' + # gh api returns a milestone that does NOT match VERSION (7.0.1). + write_gh_mock ' + if [[ "$1" == "api" ]]; then echo "6.0.0"; exit 0; fi + if [[ "$1" == "pr" ]]; then exit 0; fi + exit 0 + ' + + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [[ "$output" == *"does not exist"* ]] +} + +# ── Conflict handling ─────────────────────────────────────────────────────── + +@test "creates CONFLICTS PR when cherry-pick fails" { + write_git_mock ' + if [[ "$1" == "fetch" ]]; then exit 0; fi + if [[ "$1" == "cherry" ]]; then echo "+ abc123"; exit 0; fi + if [[ "$1" == "checkout" ]]; then exit 0; fi + if [[ "$1" == "rev-list" ]]; then echo "abc123def456 parent1"; exit 0; fi + # cherry-pick fails with conflicts. + if [[ "$1" == "cherry-pick" ]]; then + if [[ "$2" == "--abort" ]]; then exit 0; fi + exit 1 + fi + if [[ "$1" == "commit" ]]; then exit 0; fi + if [[ "$1" == "push" ]]; then exit 0; fi + exit 0 + ' + write_gh_mock ' + if [[ "$1" == "api" ]]; then echo "7.0.1"; exit 0; fi + if [[ "$1" == "pr" ]]; then exit 0; fi + exit 0 + ' + + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [[ "$output" == *"failed due to conflicts"* ]] + # Should have called cherry-pick --abort. + grep -q "GIT: cherry-pick --abort" "${STUB_DIR}/git.log" + # Should have created an empty commit. + grep -q "GIT: commit --allow-empty" "${STUB_DIR}/git.log" +} diff --git a/.github/scripts/tests/extract-hotfix-versions.bats b/.github/scripts/tests/extract-hotfix-versions.bats new file mode 100644 index 0000000000..e0767ffd04 --- /dev/null +++ b/.github/scripts/tests/extract-hotfix-versions.bats @@ -0,0 +1,244 @@ +#!/usr/bin/env bats +################################################################################# +# 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. # +################################################################################# +# +# Tests for extract-hotfix-versions.sh +# +# Run with: bats .github/scripts/tests/extract-hotfix-versions.bats +# +# Dependencies: bats-core (https://github.com/bats-core/bats-core) +# +################################################################################# + +# Path to the script under test (relative to repo root). +SCRIPT=".github/scripts/extract-hotfix-versions.sh" + +# ── Helpers ────────────────────────────────────────────────────────────────── + +setup() { + # Create a temporary GITHUB_OUTPUT file for each test. + export GITHUB_OUTPUT + GITHUB_OUTPUT="$(mktemp)" + + # Defaults — individual tests override as needed. + export EVENT_ACTION="closed" + export EVENT_LABEL="" + export PR_NUMBER="42" + export GH_TOKEN="fake-token" + export GITHUB_REPOSITORY="dotnet/SqlClient" +} + +teardown() { + rm -f "${GITHUB_OUTPUT}" +} + +# Read the 'versions' output written to GITHUB_OUTPUT. +get_versions() { + grep '^versions=' "${GITHUB_OUTPUT}" | head -1 | cut -d= -f2- +} + +# ── --help flag ────────────────────────────────────────────────────────────── + +@test "prints help text with --help" { + run bash "${SCRIPT}" --help + [ "$status" -eq 0 ] + [[ "$output" == *"Parses"* ]] + [[ "$output" == *"REQUIRED ENVIRONMENT VARIABLES"* ]] +} + +@test "prints help text with -h" { + run bash "${SCRIPT}" -h + [ "$status" -eq 0 ] + [[ "$output" == *"Parses"* ]] +} + +# ── Input validation ───────────────────────────────────────────────────────── + +@test "fails when LABELS is unset" { + unset LABELS + run bash "${SCRIPT}" + [ "$status" -ne 0 ] + [[ "$output" == *"LABELS"* ]] +} + +@test "fails when EVENT_ACTION is unset" { + export LABELS="Hotfix 7.0.1" + unset EVENT_ACTION + run bash "${SCRIPT}" + [ "$status" -ne 0 ] + [[ "$output" == *"EVENT_ACTION"* ]] +} + +@test "fails when PR_NUMBER is unset" { + export LABELS="Hotfix 7.0.1" + unset PR_NUMBER + run bash "${SCRIPT}" + [ "$status" -ne 0 ] + [[ "$output" == *"PR_NUMBER"* ]] +} + +# ── Closed event: single label ────────────────────────────────────────────── + +@test "closed event: extracts single Hotfix label" { + export LABELS="Hotfix 7.0.1" + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [ "$(get_versions)" = '["7.0.1"]' ] +} + +@test "closed event: ignores non-hotfix labels" { + export LABELS="bug,Hotfix 7.0.1,enhancement" + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [ "$(get_versions)" = '["7.0.1"]' ] +} + +# ── Closed event: multiple labels ─────────────────────────────────────────── + +@test "closed event: extracts multiple Hotfix labels" { + export LABELS="Hotfix 7.0.1,Hotfix 8.0.0" + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [ "$(get_versions)" = '["7.0.1","8.0.0"]' ] +} + +@test "closed event: extracts hotfix labels mixed with other labels" { + export LABELS="bug,Hotfix 7.0.1,enhancement,Hotfix 8.0.0,docs" + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [ "$(get_versions)" = '["7.0.1","8.0.0"]' ] +} + +# ── Closed event: malformed labels ────────────────────────────────────────── + +@test "closed event: rejects malformed Hotfix labels (no patch)" { + export LABELS="Hotfix 7.0" + run bash "${SCRIPT}" + [ "$status" -eq 1 ] + [[ "$output" == *"No valid"* ]] +} + +@test "closed event: rejects Hotfix label with text suffix" { + export LABELS="Hotfix 7.0.1-beta" + run bash "${SCRIPT}" + [ "$status" -eq 1 ] + [[ "$output" == *"No valid"* ]] +} + +@test "closed event: rejects Hotfix label with non-numeric version" { + export LABELS="Hotfix abc" + run bash "${SCRIPT}" + [ "$status" -eq 1 ] + [[ "$output" == *"No valid"* ]] +} + +@test "closed event: fails when no labels present" { + export LABELS="" + run bash "${SCRIPT}" + [ "$status" -eq 1 ] +} + +# ── Labeled event: basic behavior ─────────────────────────────────────────── + +@test "labeled event: processes valid newly added label" { + export EVENT_ACTION="labeled" + export EVENT_LABEL="Hotfix 7.0.1" + export LABELS="Hotfix 7.0.1,Hotfix 8.0.0" + + # Mock gh to report no existing branch or PR. + # The script now uses 'gh api' for branch checks (no git checkout in this job). + local stub_dir + stub_dir="$(mktemp -d)" + cat > "${stub_dir}/gh" <<'STUB' +#!/usr/bin/env bash +# gh api repos/.../git/ref/heads/...: exit 1 (branch not found) +# gh pr list: return "0" PRs +if [[ "$1" == "api" && "$2" == repos/*/git/ref/heads/* ]]; then + exit 1 +fi +echo "0" +STUB + chmod +x "${stub_dir}/gh" + + export PATH="${stub_dir}:${PATH}" + run bash "${SCRIPT}" + rm -rf "${stub_dir}" + + [ "$status" -eq 0 ] + [ "$(get_versions)" = '["7.0.1"]' ] +} + +@test "labeled event: skips non-hotfix label" { + export EVENT_ACTION="labeled" + export EVENT_LABEL="bug" + export LABELS="bug,Hotfix 7.0.1" + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [ "$(get_versions)" = '[]' ] +} + +@test "labeled event: skips malformed hotfix label" { + export EVENT_ACTION="labeled" + export EVENT_LABEL="Hotfix 7.0" + export LABELS="Hotfix 7.0" + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + [ "$(get_versions)" = '[]' ] +} + +# ── Labeled event: duplicate detection ────────────────────────────────────── + +@test "labeled event: skips when cherry-pick branch already exists" { + export EVENT_ACTION="labeled" + export EVENT_LABEL="Hotfix 7.0.1" + export LABELS="Hotfix 7.0.1" + + local stub_dir + stub_dir="$(mktemp -d)" + # gh api returns success — branch exists on the remote. + cat > "${stub_dir}/gh" <<'STUB' +#!/usr/bin/env bash +if [[ "$1" == "api" && "$2" == repos/*/git/ref/heads/* ]]; then + exit 0 +fi +echo "0" +STUB + chmod +x "${stub_dir}/gh" + export PATH="${stub_dir}:${PATH}" + + run bash "${SCRIPT}" + rm -rf "${stub_dir}" + + [ "$status" -eq 0 ] + [ "$(get_versions)" = '[]' ] + [[ "$output" == *"already exists"* ]] +} + +@test "labeled event: skips when cherry-pick PR already exists" { + export EVENT_ACTION="labeled" + export EVENT_LABEL="Hotfix 7.0.1" + export LABELS="Hotfix 7.0.1" + + local stub_dir + stub_dir="$(mktemp -d)" + # gh api for branch check returns 1 (not found), but pr list returns 1 PR. + cat > "${stub_dir}/gh" <<'STUB' +#!/usr/bin/env bash +if [[ "$1" == "api" && "$2" == repos/*/git/ref/heads/* ]]; then + exit 1 +fi +echo "1" +STUB + chmod +x "${stub_dir}/gh" + export PATH="${stub_dir}:${PATH}" + + run bash "${SCRIPT}" + rm -rf "${stub_dir}" + + [ "$status" -eq 0 ] + [ "$(get_versions)" = '[]' ] + [[ "$output" == *"already exists"* ]] +} diff --git a/.github/workflows/cherry-pick-hotfix.yml b/.github/workflows/cherry-pick-hotfix.yml new file mode 100644 index 0000000000..9110a58fa2 --- /dev/null +++ b/.github/workflows/cherry-pick-hotfix.yml @@ -0,0 +1,113 @@ +################################################################################# +# 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. # +################################################################################# +# +# Cherry-pick Hotfix to Release Branch +# +# Automatically cherry-picks merged PRs into release branches when a +# "Hotfix " label is present. Supports multiple hotfix labels on +# a single PR — each one produces an independent cherry-pick PR targeting +# the corresponding release/ branch. +# +# Usage: +# 1. Merge a PR to the default branch. +# 2. Add a "Hotfix " label (e.g. "Hotfix 7.0.1") either before +# or after merging. +# 3. The workflow derives the release branch from the label's major.minor +# version (e.g. "Hotfix 7.0.1" → release/7.0) and creates a cherry-pick +# PR prefixed with "[ Cherry-pick]". +# 4. If the cherry-pick has conflicts, a placeholder PR is opened with +# "[ Cherry-pick - CONFLICTS]" and manual resolution steps. +# +################################################################################# + +name: Cherry-pick Hotfix to release branch + +# Triggers: +# - 'closed': fires at merge time — if a "Hotfix " label is already present, +# the cherry-pick runs immediately. +# - 'labeled': fires when a label is added after merge — allows retroactive cherry-picks +# by adding the label to an already-merged PR. +on: + pull_request_target: + types: [closed, labeled] + +# 'contents: write' is needed to push the cherry-pick branch. +# 'pull-requests: write' is needed to create the new PR via the GitHub CLI. +permissions: + contents: write + pull-requests: write + +jobs: + # First job: extract all hotfix versions from the PR labels and emit them as + # a JSON array so the matrix strategy can fan out one job per version. + detect-versions: + runs-on: ubuntu-latest + # Only fire for merged PRs targeting the default branch that have at least + # one "Hotfix *" label. The default-branch guard prevents recursive + # cherry-picks when a cherry-pick PR is merged into a release branch. + if: >- + github.event.pull_request.merged == true && + github.event.pull_request.base.ref == github.event.repository.default_branch && + join(github.event.pull_request.labels.*.name, ' ') != '' && + contains(join(github.event.pull_request.labels.*.name, ','), 'Hotfix ') + outputs: + versions: ${{ steps.extract.outputs.versions }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # Only the scripts directory is needed; skip full history. + sparse-checkout: .github/scripts + sparse-checkout-cone-mode: false + + - name: Extract hotfix versions from labels + id: extract + env: + # Pass label names via env to avoid script injection from label text. + LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} + # For the 'labeled' event, this is the single label that was just added. + # For the 'closed' event this is empty, meaning all labels are processed. + EVENT_LABEL: ${{ github.event.label.name || '' }} + EVENT_ACTION: ${{ github.event.action }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: bash "${GITHUB_WORKSPACE}/.github/scripts/extract-hotfix-versions.sh" + + # Second job: runs once per detected version, cherry-picking the merge commit + # into each target release branch. + cherry-pick: + needs: detect-versions + if: needs.detect-versions.outputs.versions != '[]' + runs-on: ubuntu-latest + strategy: + # Don't cancel other cherry-picks if one version fails. + fail-fast: false + matrix: + version: ${{ fromJson(needs.detect-versions.outputs.versions) }} + name: Cherry-pick to release branch (${{ matrix.version }}) + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # Full history is required so the merge commit and target branch are available + # for the cherry-pick operation. + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Cherry-pick and create PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MERGE_COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + VERSION: ${{ matrix.version }} + run: bash "${GITHUB_WORKSPACE}/.github/scripts/cherry-pick-to-release.sh" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 464c0401f0..c1e985ea03 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,6 +23,7 @@ on: - main - feat/** - dev/** + - release/** # Scan weekly on Saturdays at 23:33 UTC schedule: diff --git a/AGENTS.md b/AGENTS.md index 56d7555c99..690e9c9c8d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,6 +54,15 @@ This repository provides reusable prompts in `.github/prompts/` for common maint 6. **Performance Optimization**: Use pooling, async, efficient allocations 7. **Observability**: EventSource tracing, meaningful errors +## Branch Naming + +All branches created by AI agents **must** live under the `dev/automation/` prefix. Use a descriptive suffix, for example: + +- `dev/automation/fix-connection-timeout` +- `dev/automation/add-json-type-tests` + +Do **not** create branches directly under `main`, `dev/`, or any other top-level prefix. + ## Common Tasks ### Bug Fix Workflow diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index d38fb14d57..98cac1bcdd 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -14,8 +14,8 @@ # It runs via CI push triggers and schedules and uses the Release build # configuration: # -# - Commits to GitHub main -# - Commits to ADO internal/main +# - Commits to GitHub main and release/* branches +# - Commits to ADO internal/main and internal/release/* branches # - Weekdays at 03:00 UTC on GitHub main # - Thursdays at 07:00 UTC on ADO internal/main # @@ -55,13 +55,15 @@ trigger: branches: include: - # GitHub main branch. - - main + # GitHub main and release branches. + - main + - release/* - # ADO internal/main branch. - - internal/main + # ADO main and release branches. + - internal/main + - internal/release/* -# Trigger this pipline on a schedule. +# Trigger this pipeline on a schedule. schedules: # GitHub main on weekdays diff --git a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml index 7fef476e59..7068c9693c 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml @@ -14,8 +14,8 @@ # It runs via CI push triggers and schedules and uses the Release build # configuration: # -# - Commits to GitHub main -# - Commits to ADO internal/main +# - Commits to GitHub main and release/* branches +# - Commits to ADO internal/main and internal/release/* branches # - Weekdays at 01:00 UTC on GitHub main # - Thursdays at 05:00 UTC on ADO internal/main # @@ -55,13 +55,15 @@ trigger: branches: include: - # GitHub main branch. - - main + # GitHub main and release branches. + - main + - release/* - # ADO internal/main branch. - - internal/main + # ADO main and release branches. + - internal/main + - internal/release/* -# Trigger this pipline on a schedule. +# Trigger this pipeline on a schedule. schedules: # GitHub main on weekdays diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index c454da710a..32921a733e 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -11,8 +11,9 @@ # - Microsoft.Data.SqlClient # - Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider # -# It is triggered by pushes to PRs that target dev/ and feature/ branches, and -# the main branch in GitHub. +# It is triggered by pushes to PRs that target the main, dev/*, feat/*, and +# release/* branches in GitHub. The dev/* pattern includes dev/automation/* +# branches created by AI agents. # # It maps to the "PR-SqlClient-Package" pipeline in the Public project: # @@ -33,6 +34,7 @@ pr: - dev/* - feat/* - main + - release/* paths: include: diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index 539d03fe82..738f8c1b6a 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -11,8 +11,9 @@ # - Microsoft.Data.SqlClient # - Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider # -# It is triggered by pushes to PRs that target dev/ and feature/ branches, and -# the main branch in GitHub. +# It is triggered by pushes to PRs that target the main, dev/*, feat/*, and +# release/* branches in GitHub. The dev/* pattern includes dev/automation/* +# branches created by AI agents. # # It maps to the "PR-SqlClient-Project" pipeline in the Public project: # @@ -33,6 +34,7 @@ pr: - dev/* - feat/* - main + - release/* paths: include: From 1ec76754d94bc2b452a4cc3a055a0c7312063efd Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 7 Apr 2026 06:54:39 -0300 Subject: [PATCH 023/298] Extract SqlBulkCopy column names using dynamic SQL (#4092) Co-authored-by: Edward Neal <55035479+edwardneal@users.noreply.github.com> --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 78 ++++++++++++------- .../ManualTests/DataCommon/DataTestUtility.cs | 15 +++- .../CertificateTest.cs | 24 +++++- .../CertificateTestWithTdsServer.cs | 2 +- .../SQL/SqlBulkCopyTest/CopyAllFromReader.cs | 4 +- .../SQL/SqlBulkCopyTest/SqlGraphTables.cs | 2 +- .../SqlBulkCopyTest/TestBulkCopyWithUTF8.cs | 21 ++++- 7 files changed, 103 insertions(+), 43 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 710d81045f..75cde1c56f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -472,29 +472,51 @@ private string CreateInitialQuery() } else if (!string.IsNullOrEmpty(CatalogName)) { - CatalogName = SqlServerEscapeHelper.EscapeIdentifier(CatalogName); + CatalogName = SqlServerEscapeHelper.EscapeStringAsLiteral(SqlServerEscapeHelper.EscapeIdentifier(CatalogName)); } string objectName = ADP.BuildMultiPartName(parts); string escapedObjectName = SqlServerEscapeHelper.EscapeStringAsLiteral(objectName); - // Specify the column names explicitly. This is to ensure that we can map to hidden columns (e.g. columns in temporal tables.) - // If the target table doesn't exist, OBJECT_ID will return NULL and @Column_Names will remain non-null. The subsequent SELECT * - // query will then continue to fail with "Invalid object name" rather than with an unusual error because the query being executed - // is NULL. - // Some hidden columns (e.g. SQL Graph columns) cannot be selected, so we need to exclude them explicitly. + // Specify the column names explicitly. This is to ensure that we can map to hidden + // columns (e.g. columns in temporal tables.) If the target table doesn't exist, + // OBJECT_ID will return NULL and @Column_Names will remain non-null. The subsequent + // SELECT * query will then continue to fail with "Invalid object name" rather than with + // an unusual error because the query being executed is NULL. + // + // Some hidden columns (e.g. SQL Graph columns) cannot be selected, so we need to + // exclude them explicitly. The graph_type values excluded below are internal graph + // columns that cannot be selected directly: + // + // 1 = GRAPH_ID + // 3 = GRAPH_FROM_ID + // 4 = GRAPH_FROM_OBJ_ID + // 6 = GRAPH_TO_ID + // 7 = GRAPH_TO_OBJ_ID + // + // See: https://learn.microsoft.com/sql/relational-databases/graphs/sql-graph-architecture#syscolumns + // + // The column-name query is built as dynamic SQL and executed via sp_executesql so + // that it is not compiled (and rejected) on SQL Server versions that lack the + // graph_type column (e.g. SQL 2016). CatalogName and escapedObjectName are + // interpolated directly into the SQL string because SQL Server does not allow + // identifiers (database/schema/table names) to be passed as parameters. Both + // values are escaped via SqlServerEscapeHelper before interpolation. return $""" SELECT @@TRANCOUNT; +DECLARE @Object_ID INT = OBJECT_ID('{escapedObjectName}'); +DECLARE @Column_Name_Query NVARCHAR(MAX); DECLARE @Column_Names NVARCHAR(MAX) = NULL; IF EXISTS (SELECT TOP 1 * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.all_columns') AND [name] = 'graph_type') BEGIN - SELECT @Column_Names = COALESCE(@Column_Names + ', ', '') + QUOTENAME([name]) FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = OBJECT_ID('{escapedObjectName}') AND COALESCE([graph_type], 0) NOT IN (1, 3, 4, 6, 7) ORDER BY [column_id] ASC; + SET @Column_Name_Query = N'SELECT @Column_Names = COALESCE(@Column_Names + '', '', '''') + QUOTENAME([name]) FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) NOT IN (1, 3, 4, 6, 7) ORDER BY [column_id] ASC;'; END ELSE BEGIN - SELECT @Column_Names = COALESCE(@Column_Names + ', ', '') + QUOTENAME([name]) FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = OBJECT_ID('{escapedObjectName}') ORDER BY [column_id] ASC; + SET @Column_Name_Query = N'SELECT @Column_Names = COALESCE(@Column_Names + '', '', '''') + QUOTENAME([name]) FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID ORDER BY [column_id] ASC;'; END +EXEC sp_executesql @Column_Name_Query, N'@Object_ID INT, @Column_Names NVARCHAR(MAX) OUTPUT', @Object_ID = @Object_ID, @Column_Names = @Column_Names OUTPUT; SELECT @Column_Names = COALESCE(@Column_Names, '*'); SET FMTONLY ON; @@ -624,7 +646,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i bool matched = false; bool rejected = false; - + // Look for a local match for the remote column. for (int j = 0; j < _localColumnMappings.Count; ++j) { @@ -644,7 +666,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i // 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. // @@ -1437,7 +1459,7 @@ private void RunParserReliably(BulkCopySimpleResultSet bulkCopyHandler = null) try { // @TODO: CER Exception Handling was removed here (see GH#3581) - _parser.Run(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); + _parser.Run(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); } finally { @@ -1760,7 +1782,7 @@ public void WriteToServer(DbDataReader reader) try { statistics = SqlStatistics.StartTimer(Statistics); - + ResetWriteToServerGlobalVariables(); _rowSource = reader; _dbDataReaderRowSource = reader; @@ -1796,13 +1818,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 @@ -1918,7 +1940,7 @@ public Task WriteToServerAsync(DataRow[] rows, CancellationToken cancellationTok try { statistics = SqlStatistics.StartTimer(Statistics); - + ResetWriteToServerGlobalVariables(); if (rows.Length == 0) { @@ -1935,9 +1957,9 @@ public Task WriteToServerAsync(DataRow[] rows, CancellationToken cancellationTok _rowSourceType = ValueSourceType.RowArray; _rowEnumerator = rows.GetEnumerator(); _isAsyncBulkCopy = true; - + // It returns Task since _isAsyncBulkCopy = true; - return WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); + return WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); } finally { @@ -1964,19 +1986,19 @@ 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; - + // It returns Task since _isAsyncBulkCopy = true; return WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); } @@ -2016,7 +2038,7 @@ public Task WriteToServerAsync(IDataReader reader, CancellationToken cancellatio _dbDataReaderRowSource = _rowSource as DbDataReader; _rowSourceType = ValueSourceType.IDataReader; _isAsyncBulkCopy = true; - + // It returns Task since _isAsyncBulkCopy = true; return WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); } @@ -2056,7 +2078,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; @@ -2064,7 +2086,7 @@ public Task WriteToServerAsync(DataTable table, DataRowState rowState, Cancellat _rowSourceType = ValueSourceType.DataTable; _rowEnumerator = table.Rows.GetEnumerator(); _isAsyncBulkCopy = true; - + // It returns Task since _isAsyncBulkCopy = true; return WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); } @@ -2114,7 +2136,7 @@ private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctok bool finishedSynchronously = true; _isBulkCopyingInProgress = true; - + CreateOrValidateConnection(nameof(WriteToServer)); SqlConnectionInternal internalConnection = _connection.GetOpenTdsConnection(); @@ -3065,11 +3087,11 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio // No need to cancel timer since SqlBulkCopy creates specific task source for reconnection. AsyncHelper.SetTimeoutExceptionWithState( - completion: cancellableReconnectTS, + completion: cancellableReconnectTS, timeout: BulkCopyTimeout, state: _destinationTableName, - onFailure: static state => - SQL.BulkLoadInvalidDestinationTable((string)state, SQL.CR_ReconnectTimeout()), + onFailure: static state => + SQL.BulkLoadInvalidDestinationTable((string)state, SQL.CR_ReconnectTimeout()), cancellationToken: CancellationToken.None ); @@ -3242,7 +3264,7 @@ private Task WriteToServerInternalAsync(CancellationToken ctoken) } return resultTask; } - + private void ResetWriteToServerGlobalVariables() { _dataTableSource = null; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 5bceb81b58..2e652841c9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -86,7 +86,7 @@ public static class DataTestUtility internal static readonly string KerberosDomainPassword = null; // SQL server Version - private static string s_sQLServerVersion = string.Empty; + private static string s_sqlServerVersion; //SQL Server EngineEdition private static string s_sqlServerEngineEdition; @@ -125,9 +125,9 @@ public static string SQLServerVersion { if (!string.IsNullOrEmpty(TCPConnectionString)) { - s_sQLServerVersion ??= GetSqlServerProperty(TCPConnectionString, ServerProperty.ProductMajorVersion); + s_sqlServerVersion ??= GetSqlServerProperty(TCPConnectionString, ServerProperty.ProductMajorVersion); } - return s_sQLServerVersion; + return s_sqlServerVersion; } } @@ -491,7 +491,14 @@ public static bool AreConnStringsSetup() public static bool IsSQL2019() => string.Equals("15", SQLServerVersion.Trim()); - public static bool IsSQL2016() => string.Equals("14", s_sQLServerVersion.Trim()); + public static bool IsSQL2017() => string.Equals("14", SQLServerVersion.Trim()); + + public static bool IsSQL2016() => string.Equals("13", SQLServerVersion.Trim()); + + // "At least" version checks for use as ConditionalFact/ConditionalTheory conditions. + public static bool IsAtLeastSQL2017() => int.TryParse(SQLServerVersion?.Trim(), out int major) && major >= 14; + + public static bool IsAtLeastSQL2019() => int.TryParse(SQLServerVersion?.Trim(), out int major) && major >= 15; public static bool IsSQLAliasSetup() { 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 fbc7cd222a..32780dcfe6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs @@ -32,7 +32,7 @@ public class CertificateTest : IDisposable // InstanceName will get replaced with an instance name in the connection string private static string InstanceName = "MSSQLSERVER"; - + // s_instanceNamePrefix will get replaced with MSSQL$ is there is an instance name in connection string private static string InstanceNamePrefix = ""; @@ -51,10 +51,14 @@ private static string ForceEncryptionRegistryPath { return $@"SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.{InstanceName}\MSSQLSERVER\SuperSocketNetLib"; } - if (DataTestUtility.IsSQL2016()) + if (DataTestUtility.IsSQL2017()) { return $@"SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.{InstanceName}\MSSQLSERVER\SuperSocketNetLib"; } + if (DataTestUtility.IsSQL2016()) + { + return $@"SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL13.{InstanceName}\MSSQLSERVER\SuperSocketNetLib"; + } return string.Empty; } } @@ -196,7 +200,9 @@ private static void CreateValidCertificate(string script) RedirectStandardError = true, RedirectStandardOutput = true, UseShellExecute = false, - Arguments = $"{script} -Prefix {InstanceNamePrefix} -Instance {InstanceName}", + Arguments = string.IsNullOrEmpty(InstanceNamePrefix) + ? $"{script} -Instance \"{InstanceName}\"" + : $"{script} -Prefix \"{InstanceNamePrefix}\" -Instance \"{InstanceName}\"", CreateNoWindow = false, Verb = "runas" } @@ -224,7 +230,12 @@ private static void CreateValidCertificate(string script) proc.Kill(); // allow async output to process proc.WaitForExit(2000); - throw new Exception($"Could not generate certificate.Error out put: {output}"); + throw new Exception($"Could not generate certificate. Error output: {output}"); + } + + if (proc.ExitCode != 0) + { + throw new Exception($"Certificate generation script failed with exit code {proc.ExitCode}. Output: {output}"); } } else @@ -252,6 +263,11 @@ private static string GetLocalIpAddress() private void RemoveCertificate() { + if (string.IsNullOrEmpty(_thumbprint)) + { + return; + } + using X509Store certStore = new(StoreName.Root, StoreLocation.LocalMachine); certStore.Open(OpenFlags.ReadWrite); X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, _thumbprint, false); 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 98ce8efafa..bb1bb8b5f2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs @@ -89,7 +89,7 @@ private static string ForceEncryptionRegistryPath { return $@"SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL15.{s_instanceName}\MSSQLSERVER\SuperSocketNetLib"; } - if (DataTestUtility.IsSQL2016()) + if (DataTestUtility.IsSQL2017()) { return $@"SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL14.{s_instanceName}\MSSQLSERVER\SuperSocketNetLib"; } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs index 5ba727be5d..f55e512a35 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs @@ -61,9 +61,9 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) DataTestUtility.AssertEqualsWithDescription((long)3, stats["BuffersReceived"], "Unexpected BuffersReceived value."); DataTestUtility.AssertEqualsWithDescription((long)3, stats["BuffersSent"], "Unexpected BuffersSent value."); DataTestUtility.AssertEqualsWithDescription((long)0, stats["IduCount"], "Unexpected IduCount value."); - DataTestUtility.AssertEqualsWithDescription((long)6, stats["SelectCount"], "Unexpected SelectCount value."); + DataTestUtility.AssertEqualsWithDescription((long)8, stats["SelectCount"], "Unexpected SelectCount value."); DataTestUtility.AssertEqualsWithDescription((long)3, stats["ServerRoundtrips"], "Unexpected ServerRoundtrips value."); - DataTestUtility.AssertEqualsWithDescription((long)9, stats["SelectRows"], "Unexpected SelectRows value."); + DataTestUtility.AssertEqualsWithDescription((long)11, stats["SelectRows"], "Unexpected SelectRows value."); DataTestUtility.AssertEqualsWithDescription((long)2, stats["SumResultSets"], "Unexpected SumResultSets value."); DataTestUtility.AssertEqualsWithDescription((long)0, stats["Transactions"], "Unexpected Transactions value."); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs index d83693080f..837ce96e76 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs @@ -11,7 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SqlBulkCopyTests { public class SqlGraphTables { - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsAtLeastSQL2017))] public void WriteToServer_CopyToSqlGraphNodeTable_Succeeds() { string connectionString = DataTestUtility.TCPConnectionString; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs index 5b7112476d..da5287e831 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs @@ -26,6 +26,13 @@ public sealed class TestBulkCopyWithUtf8 : IDisposable /// public TestBulkCopyWithUtf8() { + // xUnit instantiates the class even when ConditionalTheory conditions cause the + // test to be skipped, so we must guard setup that requires UTF-8 collations. + if (!DataTestUtility.IsAtLeastSQL2019()) + { + return; + } + using SqlConnection sourceConnection = new SqlConnection(GetConnectionString(true)); sourceConnection.Open(); SetupTables(sourceConnection, s_sourceTable, s_destinationTable, s_insertQuery); @@ -36,6 +43,12 @@ public TestBulkCopyWithUtf8() /// public void Dispose() { + // Guard matches the constructor: no tables were created on older SQL versions. + if (!DataTestUtility.IsAtLeastSQL2019()) + { + return; + } + using SqlConnection connection = new SqlConnection(GetConnectionString(true)); connection.Open(); DataTestUtility.DropTable(connection, s_sourceTable); @@ -56,7 +69,7 @@ private string GetConnectionString(bool enableMars) /// /// 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. + /// 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) { @@ -75,7 +88,8 @@ private void SetupTables(SqlConnection connection, string sourceTable, string de [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), - nameof(DataTestUtility.IsNotAzureSynapse))] + nameof(DataTestUtility.IsNotAzureSynapse), + nameof(DataTestUtility.IsAtLeastSQL2019))] [InlineData(true, true)] [InlineData(false, true)] [InlineData(true, false)] @@ -139,7 +153,8 @@ public void BulkCopy_Utf8Data_ShouldMatchSource(bool isMarsEnabled, bool enableS [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), - nameof(DataTestUtility.IsNotAzureSynapse))] + nameof(DataTestUtility.IsNotAzureSynapse), + nameof(DataTestUtility.IsAtLeastSQL2019))] [InlineData(true, true)] [InlineData(false, true)] [InlineData(true, false)] From 27999d96939dcbba95145d8ac850ccda9aa9d88c Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:57:52 -0300 Subject: [PATCH 024/298] Fix milestone lookup in hotfix workflow (#4148) --- .github/scripts/cherry-pick-to-release.sh | 2 +- .github/scripts/tests/cherry-pick-to-release.bats | 4 ++++ .github/workflows/cherry-pick-hotfix.yml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/scripts/cherry-pick-to-release.sh b/.github/scripts/cherry-pick-to-release.sh index 8b3172cc50..a8612e7453 100755 --- a/.github/scripts/cherry-pick-to-release.sh +++ b/.github/scripts/cherry-pick-to-release.sh @@ -143,7 +143,7 @@ lookup_milestone() { MILESTONE_ARG="" MILESTONE_NOTE="" - if gh api "repos/${GITHUB_REPOSITORY}/milestones" --paginate \ + if gh api "repos/${GITHUB_REPOSITORY}/milestones" --method GET --paginate \ --field state=open --jq '.[].title' | grep -qx "${version}"; then MILESTONE_ARG="--milestone ${version}" echo "Milestone '${version}' found." diff --git a/.github/scripts/tests/cherry-pick-to-release.bats b/.github/scripts/tests/cherry-pick-to-release.bats index b8d40a3b3e..68ef3d7f8c 100644 --- a/.github/scripts/tests/cherry-pick-to-release.bats +++ b/.github/scripts/tests/cherry-pick-to-release.bats @@ -138,6 +138,8 @@ STUB [ "$status" -eq 0 ] # Verify the git fetch targeted release/7.0. grep -q "GIT: fetch origin release/7.0" "${STUB_DIR}/git.log" + # Milestone lookup must use GET to avoid accidentally POSTing to the create endpoint. + grep "GH: api repos/dotnet/SqlClient/milestones" "${STUB_DIR}/gh.log" | grep -q "\-\-method GET" } @test "derives release/8.0 from version 8.0.0" { @@ -259,6 +261,8 @@ STUB run bash "${SCRIPT}" [ "$status" -eq 0 ] [[ "$output" == *"does not exist"* ]] + # Milestone lookup must use GET to avoid accidentally POSTing to the create endpoint. + grep "GH: api repos/dotnet/SqlClient/milestones" "${STUB_DIR}/gh.log" | grep -q "\-\-method GET" } # ── Conflict handling ─────────────────────────────────────────────────────── diff --git a/.github/workflows/cherry-pick-hotfix.yml b/.github/workflows/cherry-pick-hotfix.yml index 9110a58fa2..794d8e41d3 100644 --- a/.github/workflows/cherry-pick-hotfix.yml +++ b/.github/workflows/cherry-pick-hotfix.yml @@ -57,7 +57,7 @@ jobs: versions: ${{ steps.extract.outputs.versions }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Only the scripts directory is needed; skip full history. sparse-checkout: .github/scripts @@ -91,7 +91,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # Full history is required so the merge commit and target branch are available # for the cherry-pick operation. From 26f5c66124eab36e33dd21c93c500acbc21b805d Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Tue, 7 Apr 2026 21:50:38 +0530 Subject: [PATCH 025/298] Return correct type when invoking GetFieldType and GetProviderSpecificFieldType for vector float32 column (#4105) This commit returns correct type for GetFieldType and GetProviderSpecificFieldType --- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 21 ++++++++++ .../VectorTest/NativeVectorFloat32Tests.cs | 41 +++++++++++++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 8806df9432..bf1f38da00 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -1307,6 +1307,10 @@ private Type GetFieldTypeInternal(_SqlMetaData metaData) Connection.CheckGetExtendedUDTInfo(metaData, false); fieldType = metaData.udt?.Type; } + else if (metaData.type == SqlDbTypeExtensions.Vector) + { + fieldType = GetVectorFieldType(metaData.scale); + } else { // For all other types, including Xml - use data in MetaType. if (metaData.cipherMD != null) @@ -1329,6 +1333,19 @@ private Type GetFieldTypeInternal(_SqlMetaData metaData) return fieldType; } +#if !NETFRAMEWORK + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] +#endif + private static Type GetVectorFieldType(byte vectorElementType) + { + MetaType.SqlVectorElementType elementType = (MetaType.SqlVectorElementType)vectorElementType; + return elementType switch + { + MetaType.SqlVectorElementType.Float32 => typeof(SqlVector), + _ => throw SQL.VectorTypeNotSupported(elementType.ToString()), + }; + } + virtual internal int GetLocaleId(int i) { _SqlMetaData sqlMetaData = MetaData[i]; @@ -1422,6 +1439,10 @@ private Type GetProviderSpecificFieldTypeInternal(_SqlMetaData metaData) Connection.CheckGetExtendedUDTInfo(metaData, false); providerSpecificFieldType = metaData.udt?.Type; } + else if (metaData.type == SqlDbTypeExtensions.Vector) + { + providerSpecificFieldType = GetVectorFieldType(metaData.scale); + } else { // For all other types, including Xml - use data in MetaType. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs index 2ff72bba06..0a86534112 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -29,19 +29,19 @@ public static IEnumerable GetVectorFloat32TestData() 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) + // 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 + // 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 + // Pattern 1-4 with SqlVector.Null yield return new object[] { 1, SqlVector.Null, Array.Empty(), vectorColumnLength }; // Following scenario is not supported in SqlClient. @@ -561,6 +561,41 @@ public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] + public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() + { + using var connection = new SqlConnection(s_connectionString); + connection.Open(); + + // Insert a row so we can query it + using (var insertCmd = new SqlCommand(s_insertCmdString, connection)) + { + var param = insertCmd.Parameters.Add(s_vectorParamName, SqlDbTypeExtensions.Vector); + param.Value = new SqlVector(VectorFloat32TestData.testData); + insertCmd.ExecuteNonQuery(); + } + + using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var reader = selectCmd.ExecuteReader(); + + // Verify GetFieldType returns SqlVector for the vector column + Assert.Equal(typeof(SqlVector), reader.GetFieldType(0)); + + // Verify GetProviderSpecificFieldType also returns SqlVector + Assert.Equal(typeof(SqlVector), reader.GetProviderSpecificFieldType(0)); + + // Verify that GetValue returns an instance consistent with GetFieldType + Assert.True(reader.Read(), "No data found in the table."); + object value = reader.GetValue(0); + Assert.IsType>(value); + Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)value).Memory.ToArray()); + + // Verify GetFieldValue> returns the correct typed value + SqlVector typedValue = reader.GetFieldValue>(0); + Assert.IsType>(typedValue); + Assert.Equal(VectorFloat32TestData.testData, typedValue.Memory.ToArray()); + } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] public void TestInsertVectorsFloat32WithPrepare() { From e01cd138f970bf56931b5f84e5f728411219d614 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Wed, 8 Apr 2026 05:04:58 +1000 Subject: [PATCH 026/298] Replace 'as' casts with explicit casts (#4019) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/Microsoft/Data/SqlClient/SqlBulkCopy.cs | 4 ++-- .../src/Microsoft/Data/SqlClient/SqlDataReader.cs | 2 +- .../src/Microsoft/Data/SqlClient/SqlDependency.cs | 2 +- .../src/Microsoft/Data/SqlClient/SqlParameter.cs | 4 ++-- .../tests/FunctionalTests/AADAuthenticationTests.cs | 4 ++-- .../ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 75cde1c56f..cc90802f08 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -205,10 +205,10 @@ private int RowNumber switch (_rowSourceType) { case ValueSourceType.RowArray: - rowNo = ((DataTable)_dataTableSource).Rows.IndexOf(_rowEnumerator.Current as DataRow); + rowNo = ((DataTable)_dataTableSource).Rows.IndexOf((DataRow)_rowEnumerator.Current); break; case ValueSourceType.DataTable: - rowNo = ((DataTable)_rowSource).Rows.IndexOf(_rowEnumerator.Current as DataRow); + rowNo = ((DataTable)_rowSource).Rows.IndexOf((DataRow)_rowEnumerator.Current); break; case ValueSourceType.DbDataReader: case ValueSourceType.IDataReader: diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index bf1f38da00..0809f322e2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -3125,7 +3125,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met { throw SQL.JsonDocumentNotSupportedOnColumnType(metaData.column); } - JsonDocument document = JsonDocument.Parse(data.Value as string); + JsonDocument document = JsonDocument.Parse(data.String); return (T)(object)document; } else 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 10efdd9e05..94d42a3393 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -539,7 +539,7 @@ private static void GetSerializedObject(SqlClientObjRef objRef, DataContractSeri private static SqlDependencyProcessDispatcher GetDeserializedObject(DataContractSerializer serializer, MemoryStream stream) { object refResult = serializer.ReadObject(stream); - var result = RemotingServices.Unmarshal((refResult as SqlClientObjRef).GetObjRef()); + var result = RemotingServices.Unmarshal(((SqlClientObjRef)refResult).GetObjRef()); return result as SqlDependencyProcessDispatcher; } #endif // NETFRAMEWORK 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 2defc2e950..01d1131789 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -2390,11 +2390,11 @@ internal static object CoerceValue(object value, MetaType destinationType, out b { try { - value = (new SqlVector(JsonSerializer.Deserialize(value as string)) as ISqlVector).VectorPayload; + value = ((ISqlVector)new SqlVector(JsonSerializer.Deserialize((string)value))).VectorPayload; } catch (Exception ex) when (ex is ArgumentNullException || ex is JsonException) { - throw ADP.InvalidJsonStringForVector(value as string, ex); + throw ADP.InvalidJsonStringForVector((string)value, ex); } } else if ( diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs index d86b6684d1..dc6d5b3d7a 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -32,11 +32,11 @@ public void InvalidCombinationOfAccessToken(string description, object[] Params) { SecureString password = new SecureString(); password.MakeReadOnly(); - _credential = new SqlCredential(Params[0] as string, password); + _credential = new SqlCredential((string)Params[0], password); } else { - _builder[Params[0] as string] = Params[1]; + _builder[(string)Params[0]] = Params[1]; } InvalidCombinationCheck(_credential); } 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 5559dbff8e..6d14f6fbd8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs @@ -38,7 +38,7 @@ public class PoolBlockPeriodTest public void TestAzureBlockingPeriod(string description, object[] Params) { string _ = description; // Using C# Discards as workaround to the XUnit warning. - string serverName = Params[0] as string; + string serverName = (string)Params[0]; PoolBlockingPeriod? policy = null; if (Params.Length > 1) { @@ -60,7 +60,7 @@ public void TestAzureBlockingPeriod(string description, object[] Params) public void TestNonAzureBlockingPeriod(string description, object[] Params) { string _ = description; // Using C# Discards as workaround to the XUnit warning. - string serverName = Params[0] as string; + string serverName = (string)Params[0]; PoolBlockingPeriod? policy = null; if (Params.Length > 1) From ea6091ba85a35c2c3b024c8f3d495ee8fc371352 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:45:53 -0700 Subject: [PATCH 027/298] Enable User Agent feature extension (#4124) --- .../Data/SqlClient/LocalAppContextSwitches.cs | 24 --------- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- .../Common/LocalAppContextSwitchesHelper.cs | 15 ------ .../SqlClient/LocalAppContextSwitchesTest.cs | 1 - .../SimulatedServerTests/ConnectionTests.cs | 54 ------------------- 5 files changed, 1 insertion(+), 95 deletions(-) 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 7694092907..16e0abb7ec 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -36,13 +36,6 @@ internal static class LocalAppContextSwitches private const string EnableMultiSubnetFailoverByDefaultString = "Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault"; - /// - /// The name of the app context switch that controls whether - /// the user agent feature is enabled. - /// - private const string EnableUserAgentString = - "Switch.Microsoft.Data.SqlClient.EnableUserAgent"; - #if NET /// /// The name of the app context switch that controls whether @@ -177,11 +170,6 @@ private enum SwitchValue : byte /// private static SwitchValue s_enableMultiSubnetFailoverByDefault = SwitchValue.None; - /// - /// The cached value of the EnableUserAgent switch. - /// - private static SwitchValue s_enableUserAgent = SwitchValue.None; - #if NET /// /// The cached value of the GlobalizationInvariantMode switch. @@ -319,18 +307,6 @@ static LocalAppContextSwitches() defaultValue: false, ref s_enableMultiSubnetFailoverByDefault); - /// - /// When set to true, the user agent feature is enabled and the driver will - /// send the user agent string to the server. - /// - /// The default value of this switch is false. - /// - public static bool EnableUserAgent => - AcquireAndReturn( - EnableUserAgentString, - defaultValue: false, - ref s_enableUserAgent); - #if NET /// /// .NET Core 2.0 and up supports Globalization Invariant mode, which 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 94281b4998..088d41d94d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -9663,7 +9663,7 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, checked { // NOTE: As part of TDS spec UserAgent feature extension should be the first feature extension in the list. - if (LocalAppContextSwitches.EnableUserAgent && ((requestedFeatures & TdsEnums.FeatureExtension.UserAgent) != 0)) + if ((requestedFeatures & TdsEnums.FeatureExtension.UserAgent) != 0) { length += WriteUserAgentFeatureRequest(userAgent, write); } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 4a0cdf67de..8102f47d51 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -43,7 +43,6 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly bool? _disableTnirByDefaultOriginal; #endif private readonly bool? _enableMultiSubnetFailoverByDefaultOriginal; - private readonly bool? _enableUserAgentOriginal; #if NET private readonly bool? _globalizationInvariantModeOriginal; #endif @@ -93,8 +92,6 @@ public LocalAppContextSwitchesHelper() #endif _enableMultiSubnetFailoverByDefaultOriginal = GetSwitchValue("s_enableMultiSubnetFailoverByDefault"); - _enableUserAgentOriginal = - GetSwitchValue("s_enableUserAgent"); #if NET _globalizationInvariantModeOriginal = GetSwitchValue("s_globalizationInvariantMode"); @@ -149,9 +146,6 @@ public void Dispose() SetSwitchValue( "s_enableMultiSubnetFailoverByDefault", _enableMultiSubnetFailoverByDefaultOriginal); - SetSwitchValue( - "s_enableUserAgent", - _enableUserAgentOriginal); #if NET SetSwitchValue( "s_globalizationInvariantMode", @@ -228,15 +222,6 @@ public bool? EnableMultiSubnetFailoverByDefault set => SetSwitchValue("s_enableMultiSubnetFailoverByDefault", value); } - /// - /// Get or set the EnableUserAgent switch value. - /// - public bool? EnableUserAgent - { - get => GetSwitchValue("s_enableUserAgent"); - set => SetSwitchValue("s_enableUserAgent", value); - } - #if NET /// /// Get or set the GlobalizationInvariantMode switch value. diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index 59445a26b6..a5db02faa0 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -28,7 +28,6 @@ public void TestDefaultAppContextSwitchValues() Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); Assert.False(LocalAppContextSwitches.TruncateScaledDecimal); Assert.False(LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner); - Assert.False(LocalAppContextSwitches.EnableUserAgent); Assert.False(LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault); #if NET Assert.False(LocalAppContextSwitches.GlobalizationInvariantMode); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index 21a6cb65de..e3a4e26957 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -841,10 +841,6 @@ public void TestConnWithVectorFeatExtVersionNegotiation(bool expectedConnectionR [InlineData(false)] public void TestConnWithUserAgentFeatureExtension(bool sendAck) { - // Enable sending the UserAgent if desired. - using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.EnableUserAgent = true; - // Start the test server. using TdsServer server = new(); server.Start(); @@ -905,55 +901,5 @@ public void TestConnWithUserAgentFeatureExtension(bool sendAck) // TODO: Confirm the server sent an Ack by reading log message from SqlInternalConnectionTds } - - /// - /// Test to verify no UserAgent relevant information is sent when EnableUserAgentField switch is disabled. - /// - [Fact] - public void TestConnWithoutUserAgentFeatureExtension() - { - // Disable the client-side UserAgent field entirely - using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.EnableUserAgent = false; - - using var server = new TdsServer(); - server.Start(); - - // Do not advertise or force the UserAgent feature on the server - server.ServerSupportedUserAgentFeatureExtVersion = 0x00; // no support - server.EnableUserAgentFeatureExt = false; // no forced ACK - - bool loginValidated = false; - bool userAgentFeatureSeen = false; - - // Inspect the LOGIN7 packet captured by the test server - server.OnLogin7Validated = loginToken => - { - var featureExtTokens = loginToken.FeatureExt - .OfType() - .ToArray(); - - // Ensure there is no UserAgentSupport token at all - var uaToken = featureExtTokens.FirstOrDefault(t => t.FeatureID == TDSFeatureID.UserAgentSupport); - userAgentFeatureSeen = uaToken is not null; - - loginValidated = true; - }; - - // Connect to the test TDS server with a basic connection string - var connStr = new SqlConnectionStringBuilder - { - DataSource = $"localhost,{server.EndPoint.Port}", - Encrypt = SqlConnectionEncryptOption.Optional, - }.ConnectionString; - - using var connection = new SqlConnection(connStr); - connection.Open(); - - // Verify that the connection succeeded and no UserAgent data was sent - Assert.Equal(ConnectionState.Open, connection.State); - Assert.True(loginValidated, "Expected LOGIN7 to be validated by the test server"); - Assert.False(userAgentFeatureSeen, "Did not expect a UserAgentSupport feature token in LOGIN7"); - } } } From a3172b19b935eb9a25a34c116ab816cd1c60d29a Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Tue, 7 Apr 2026 17:41:38 -0700 Subject: [PATCH 028/298] Update prompt tools. (#4132) --- .github/prompts/code-review.prompt.md | 10 ++++++---- .github/prompts/generate-prompt.prompt.md | 1 + .github/prompts/generate-skill.prompt.md | 2 ++ .github/prompts/update-build-pipelines.prompt.md | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/prompts/code-review.prompt.md b/.github/prompts/code-review.prompt.md index 6196c32293..3a64abec0b 100644 --- a/.github/prompts/code-review.prompt.md +++ b/.github/prompts/code-review.prompt.md @@ -1,18 +1,20 @@ --- name: code-review -description: AI-assisted code review for a pull request in Microsoft.Data.SqlClient. +description: AI-assisted code review for a pull request or branch in Microsoft.Data.SqlClient. argument-hint: agent: agent -tools: ['github/search_issues', 'read/readFile', 'codebase/search'] +tools: ['github/search_issues', 'github/pull_request_read', 'github/get_file_contents', 'github/run_secret_scanning', 'read/readFile', 'search'] --- -Review the pull request "${input:pr}" in `dotnet/SqlClient`. +Review the changes in "${input:target}" for `dotnet/SqlClient`. + +The target may be either a **PR number** (e.g., `4106`) or a **branch name** (e.g., `dev/user/my-feature`). Determine which by checking whether the value is purely numeric. Follow this structured review process: ## 1. Understand the Change -- Fetch the PR details: title, description, linked issue(s), and diff. - Read the PR description to understand the intent and scope of the change. +- Check for linked issues referenced in the description (e.g., `Fixes #...`). - Check which files are modified and categorize them: - **Source code** (`src/Microsoft.Data.SqlClient/src/`) — the main review focus - **Tests** (`tests/`) — verify coverage diff --git a/.github/prompts/generate-prompt.prompt.md b/.github/prompts/generate-prompt.prompt.md index 73d083947b..4db6c577c9 100644 --- a/.github/prompts/generate-prompt.prompt.md +++ b/.github/prompts/generate-prompt.prompt.md @@ -2,6 +2,7 @@ name: generate-prompt description: Generates high-quality VS Code Copilot prompt files (.prompt.md) based on user descriptions, leveraging available skills. argument-hint: Describe the prompt you want to create (e.g., "A prompt to generate unit tests for C#") +tools: [read, edit, search, todo] --- You are an expert AI prompt developer specialized in creating **Visual Studio Code Copilot Prompt Files (`.prompt.md`)**. diff --git a/.github/prompts/generate-skill.prompt.md b/.github/prompts/generate-skill.prompt.md index 484fe2debc..e258adedc6 100644 --- a/.github/prompts/generate-skill.prompt.md +++ b/.github/prompts/generate-skill.prompt.md @@ -2,6 +2,8 @@ name: generate-skill description: Generate a GitHub Copilot Agent Skill (SKILL.md) following best practices and official documentation argument-hint: Describe the skill you want to create (e.g., "debugging SQL connection issues") +agent: agent +tools: ['read/readFile', 'edit/createFile', 'search'] --- You are an expert developer specialized in creating **GitHub Copilot Agent Skills**. diff --git a/.github/prompts/update-build-pipelines.prompt.md b/.github/prompts/update-build-pipelines.prompt.md index 617cf39901..a4715fa000 100644 --- a/.github/prompts/update-build-pipelines.prompt.md +++ b/.github/prompts/update-build-pipelines.prompt.md @@ -3,7 +3,7 @@ name: update-build-pipelines description: Guided workflow for updating Azure DevOps CI/CD pipelines for Microsoft.Data.SqlClient. argument-hint: agent: agent -tools: ['edit/createFile', 'edit/editFiles', 'read/readFile', 'codebase/search'] +tools: ['edit/createFile', 'edit/editFiles', 'read/readFile', 'search'] --- Update the Azure DevOps build pipelines for: "${input:change}". From f217d82755ddfc74af8be4c90952c4f41af2dbd9 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Tue, 7 Apr 2026 19:42:03 -0500 Subject: [PATCH 029/298] Official Build Cleanup (#4123) * Cleanup step parameters * Parameter work for build-signed-csproj-package-job.yml: * Drop buildConfiguration parameter * Introduce symbols parameters * Signing parameters with prefix * Forward parameter defaults from build-signed-csproj-package-job to build-stages.yml * Forward default values for symbols/signing parameters in build-stages.yml to parameters. * Delete debug param from build-stages.yml Forward default values to parameters on sqlclient-non-official.yml and sqlclient-official.yml * Don't nest variables files. * Do not import variables templates outside of the pipeline root * OneBranch variable libraries moved to onebranch-variables.yml Replace old libraries with new libraries Replace old library references with new library references * Project for manintaining the pipelines folder * Parameterize package versions from the pipeline to the stages * Store "effective" package versions in new, parameterized variables file * Parameterize the artifact names * Parameterize the assembly versions * Parameterize the mds validation job * Sweep remaining $() references that are not to well-known variables. * Remove unused variables and cleanup remaining variables * Cleanup SDL parameters * AKVProvider -> AkvProvider * MDS -> SqlClient --- eng/pipelines/Pipelines.csproj | 28 ++ .../jobs/build-signed-csproj-package-job.yml | 143 ++++--- ...=> build-signed-sqlclient-package-job.yml} | 26 +- .../jobs/publish-nuget-package-job.yml | 2 +- .../jobs/validate-signed-package-job.yml | 215 ++++++++--- .../onebranch/sqlclient-non-official.yml | 166 ++++---- .../onebranch/sqlclient-official.yml | 171 +++++---- .../onebranch/stages/build-stages.yml | 361 ++++++++++++------ .../onebranch/stages/release-stages.yml | 81 ++-- .../onebranch/steps/build-csproj-step.yml | 23 +- ...-mds-step.yml => build-sqlclient-step.yml} | 6 +- ... => copy-apiscan-files-sqlclient-step.yml} | 0 .../onebranch/steps/pack-csproj-step.yml | 21 +- ...k-mds-step.yml => pack-sqlclient-step.yml} | 6 +- .../steps/roslyn-analyzers-csproj-step.yml | 24 +- ...ml => roslyn-analyzers-sqlclient-step.yml} | 8 +- .../onebranch/variables/common-variables.yml | 164 +------- .../variables/onebranch-variables.yml | 69 ++-- .../onebranch/variables/package-variables.yml | 137 +++++++ .../sqlclient-validation-variables.yml | 38 -- src/Microsoft.Data.SqlClient.slnx | 92 +---- 21 files changed, 985 insertions(+), 796 deletions(-) create mode 100644 eng/pipelines/Pipelines.csproj rename eng/pipelines/onebranch/jobs/{build-signed-mds-package-job.yml => build-signed-sqlclient-package-job.yml} (89%) rename eng/pipelines/onebranch/steps/{build-mds-step.yml => build-sqlclient-step.yml} (89%) rename eng/pipelines/onebranch/steps/{copy-apiscan-files-mds-step.yml => copy-apiscan-files-sqlclient-step.yml} (100%) rename eng/pipelines/onebranch/steps/{pack-mds-step.yml => pack-sqlclient-step.yml} (89%) rename eng/pipelines/onebranch/steps/{roslyn-analyzers-mds-step.yml => roslyn-analyzers-sqlclient-step.yml} (84%) create mode 100644 eng/pipelines/onebranch/variables/package-variables.yml delete mode 100644 eng/pipelines/onebranch/variables/sqlclient-validation-variables.yml diff --git a/eng/pipelines/Pipelines.csproj b/eng/pipelines/Pipelines.csproj new file mode 100644 index 0000000000..f599c80053 --- /dev/null +++ b/eng/pipelines/Pipelines.csproj @@ -0,0 +1,28 @@ + + + + + net10.0 + true + + + + + + diff --git a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml index 653d9419bc..6dc72983cc 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml @@ -15,6 +15,49 @@ # signed DLLs, and finally ESRP signs the NuGet packages. parameters: + + # Signing Parameters ----------------------------------------------------- + + - name: signingAppRegistrationClientId + type: string + + - name: signingAppRegistrationTenantId + type: string + + - name: signingAuthAkvName + type: string + + - name: signingAuthSignCertName + type: string + + - name: signingEsrpClientId + type: string + + - name: signingEsrpConnectedServiceName + type: string + + # Symbols Publishing Parameters ------------------------------------------ + + - name: symbolsAzureSubscription + type: string + default: 'Symbols publishing Workload Identity federation service-ADO.Net' + + - name: symbolsPublishProjectName + type: string + default: 'Microsoft.Data.SqlClient.SNI' + + - name: symbolsPublishServer + type: string + + - name: symbolsPublishTokenUri + type: string + + - name: symbolsUploadAccount + type: string + default: 'SqlClientDrivers' + + # OTHERS +===================================== + # Short package name used in the job name, display strings, filesystem paths, and as a suffix for # the default Build and Pack targets if those aren't specified. - name: packageName @@ -42,20 +85,17 @@ parameters: type: string default: "" + # True to enable ESRP malware scanning and code signing steps, which should not be + # run on non-official pipelines as they access production resources. + - name: isOfficial + type: boolean + # The MSBuild pack target in build.proj (e.g. PackLogging). If not specified, defaults to # Pack. - name: packTarget type: string default: "" - # The C# build configuration to build (e.g. Debug or Release). - - name: buildConfiguration - type: string - values: - - Debug - - Release - default: Release - # Additional MSBuild -p: arguments for version properties. These may include versions of # packages this package depends on, or versions for this package itself. - name: versionProperties @@ -70,30 +110,6 @@ parameters: - name: publishSymbols type: boolean - # True to enable ESRP malware scanning and code signing steps, which should not be - # run on non-official pipelines as they access production resources. - - name: isOfficial - type: boolean - - # Values required by ESRP tasks. - - name: esrpConnectedServiceName - type: string - - - name: esrpClientId - type: string - - - name: appRegistrationClientId - type: string - - - name: appRegistrationTenantId - type: string - - - name: authAkvName - type: string - - - name: authSignCertName - type: string - # Optional list of pipeline artifacts to download before building. Each entry is an object # with 'artifactName' (the pipeline artifact name) and 'displayName' (used in the task label). # This replaces hard-coded packageName conditionals so callers declare their own dependencies. @@ -108,15 +124,20 @@ jobs: type: windows variables: + # Inform OneBranch that files put in this directory should be uploaded as artifacts. ob_outputDirectory: $(PACK_OUTPUT) + # APIScan configuration for this Extension package ob_sdl_apiscan_enabled: true - ob_sdl_apiscan_softwareFolder: $(Build.SourcesDirectory)/apiScan/${{ parameters.packageName }}/dlls - ob_sdl_apiscan_symbolsFolder: $(Build.SourcesDirectory)/apiScan/${{ parameters.packageName }}/pdbs + ob_sdl_apiscan_softwareFolder: $(REPO_ROOT)/apiScan/${{ parameters.packageName }}/dlls + ob_sdl_apiscan_symbolsFolder: $(REPO_ROOT)/apiScan/${{ parameters.packageName }}/pdbs ob_sdl_apiscan_softwarename: ${{ parameters.packageFullName }} ob_sdl_apiscan_versionNumber: ${{ parameters.assemblyFileVersion }} + # If parameters.buildTarget is not provided, default to "Build{parameters.packageName}" buildTarget: ${{ coalesce(parameters.buildTarget, format('Build{0}', parameters.packageName)) }} + + # If parameters.packTarget is not provided, default to "Pack{parameters.packageName}" packTarget: ${{ coalesce(parameters.packTarget, format('Pack{0}', parameters.packageName)) }} steps: @@ -128,7 +149,7 @@ jobs: displayName: Download ${{ artifact.displayName }} inputs: artifactName: ${{ artifact.artifactName }} - targetPath: $(Build.SourcesDirectory)/packages + targetPath: $(REPO_ROOT)/packages # Install the .NET SDK. - template: /eng/pipelines/steps/install-dotnet.yml@self @@ -136,30 +157,29 @@ jobs: # Perform Roslyn analysis before building, since this step will clobber build output. - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml@self parameters: + buildTarget: '${{ variables.buildTarget }}' msBuildArguments: >- - -t:$(buildTarget) - -p:Configuration=${{ parameters.buildConfiguration }} + -p:Configuration=Release -p:ReferenceType=Package ${{ parameters.versionProperties }} # Build the package, producing DLLs only (no NuGet package yet). - template: /eng/pipelines/onebranch/steps/build-csproj-step.yml@self parameters: - buildTarget: $(buildTarget) - buildConfiguration: ${{ parameters.buildConfiguration }} + buildTarget: '${{ variables.buildTarget }}' versionProperties: ${{ parameters.versionProperties }} - ${{ if eq(parameters.isOfficial, true) }}: # ESRP sign the DLLs. - template: /eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml@self parameters: - appRegistrationClientId: ${{ parameters.appRegistrationClientId }} - appRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} - authAkvName: ${{ parameters.authAkvName }} - authSignCertName: ${{ parameters.authSignCertName }} - esrpClientId: ${{ parameters.esrpClientId }} - esrpConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} - pattern: ${{ parameters.packageFullName }}.dll + appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + authAkvName: '${{ parameters.signingAuthAkvName }}' + authSignCertName: '${{ parameters.signingAuthSignCertName }}' + esrpClientId: '${{ parameters.signingEsrpClientId }}' + esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + pattern: '${{ parameters.packageFullName }}.dll' # Copy signed/unsigned DLLs and PDBs to APIScan folders. - task: CopyFiles@2 @@ -167,7 +187,7 @@ jobs: inputs: SourceFolder: $(BUILD_OUTPUT)/Package/bin Contents: "**/${{ parameters.packageFullName }}.dll" - TargetFolder: $(ob_sdl_apiscan_softwareFolder) + TargetFolder: ${{ variables.ob_sdl_apiscan_softwareFolder }} # We must preserve the folder structure since our C# projects may produce multiple # identically named DLLs for different target frameworks (e.g. netstandard2.0, net5.0, # etc.), and we need to keep those separate for APIScan to work correctly. @@ -178,28 +198,27 @@ jobs: inputs: SourceFolder: $(BUILD_OUTPUT)/Package/bin Contents: "**/${{ parameters.packageFullName }}.pdb" - TargetFolder: $(ob_sdl_apiscan_symbolsFolder) + TargetFolder: ${{ variables.ob_sdl_apiscan_symbolsFolder }} flattenFolders: false # Pack the signed DLLs into NuGet package (NoBuild=true). - template: /eng/pipelines/onebranch/steps/pack-csproj-step.yml@self parameters: - packTarget: $(packTarget) - buildConfiguration: ${{ parameters.buildConfiguration }} + packTarget: ${{ variables.packTarget }} versionProperties: ${{ parameters.versionProperties }} - ${{ if eq(parameters.isOfficial, true) }}: # ESRP sign the NuGet package. - template: /eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml@self parameters: - appRegistrationClientId: ${{ parameters.appRegistrationClientId }} - appRegistrationTenantId: ${{ parameters.appRegistrationTenantId }} - authAkvName: ${{ parameters.authAkvName }} - authSignCertName: ${{ parameters.authSignCertName }} - esrpClientId: ${{ parameters.esrpClientId }} - esrpConnectedServiceName: ${{ parameters.esrpConnectedServiceName }} + appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + authAkvName: '${{ parameters.signingAuthAkvName }}' + authSignCertName: '${{ parameters.signingAuthSignCertName }}' + esrpClientId: '${{ parameters.signingEsrpClientId }}' + esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' searchPath: $(PACK_OUTPUT) - searchPattern: '${{ parameters.packageFullName }}.*nupkg' + searchPattern: '${{ parameters.packageFullName}}.*nupkg' # Publish symbols to servers # @TODO: Get these parameters from variables/libraries @@ -207,13 +226,13 @@ jobs: - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self parameters: artifactName: '${{ parameters.packageFullName }}_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.packageVersion }}_$(System.TimelineId)' - azureSubscription: 'Symbols publishing Workload Identity federation service-ADO.Net' + azureSubscription: '${{ parameters.symbolsAzureSubscription }}' packageName: '${{ parameters.packageFullName }}' - publishProjectName: 'Microsoft.Data.SqlClient.SNI' # This is used for all SqlClient packages. Don't know why, but it is. - publishServer: '$(SymbolServer)' + publishProjectName: '${{ parameters.symbolsPublishProjectName }}' + publishServer: '${{ parameters.symbolsPublishServer }}' publishToInternal: 'true' publishToPublic: 'true' - publishTokenUri: '$(SymbolTokenUri)' + publishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' searchPattern: '**/${{ parameters.packageFullName }}*.pdb' - uploadAccount: 'SqlClientDrivers' + uploadAccount: '${{ parameters.symbolsUploadAccount }}' version: '${{ parameters.packageVersion }}' diff --git a/eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml similarity index 89% rename from eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml rename to eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml index 4464a3a9f1..752e459b84 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml @@ -67,14 +67,14 @@ parameters: - name: loggingPackageVersion type: string - - name: mdsAssemblyFileVersion + - name: sqlClientAssemblyFileVersion type: string - - name: mdsPackageVersion + - name: sqlClientPackageVersion type: string jobs: - - job: build_package_Mds + - job: build_package_SqlClient displayName: 'Build Microsoft.Data.SqlClient' pool: type: windows @@ -84,7 +84,7 @@ jobs: ob_sdl_apiscan_softwareFolder: ${{ parameters.apiScanDllPath }} ob_sdl_apiscan_symbolsFolder: ${{ parameters.apiScanPdbPath }} ob_sdl_apiscan_softwarename: 'Microsoft.Data.SqlClient' - ob_sdl_apiscan_versionNumber: ${{ parameters.mdsAssemblyFileVersion }} + ob_sdl_apiscan_versionNumber: ${{ parameters.sqlClientAssemblyFileVersion }} steps: # Dump environment and parameters @@ -114,21 +114,21 @@ jobs: - template: /eng/pipelines/steps/install-dotnet.yml@self # Perform analysis before building, since this step will clobber build output - - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml@self + - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml@self parameters: abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' - mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' # Perform the actual build - - template: /eng/pipelines/onebranch/steps/build-mds-step.yml@self + - template: /eng/pipelines/onebranch/steps/build-sqlclient-step.yml@self parameters: abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' - mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' # Copy the built DLLs and PDBs to the APIScan output folder for APIScanning post-build - - template: /eng/pipelines/onebranch/steps/copy-apiscan-files-mds-step.yml@self + - template: /eng/pipelines/onebranch/steps/copy-apiscan-files-sqlclient-step.yml@self parameters: dllPath: '${{ parameters.apiScanDllPath }}' pdbPath: '${{ parameters.apiScanPdbPath }}' @@ -147,11 +147,11 @@ jobs: pattern: 'Microsoft.Data.SqlClient*.dll' # Package the build output into a NuGet package - - template: /eng/pipelines/onebranch/steps/pack-mds-step.yml + - template: /eng/pipelines/onebranch/steps/pack-sqlclient-step.yml parameters: abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' - mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' # Sign the NuGet packages - ${{ if parameters.isOfficial }}: @@ -169,7 +169,7 @@ jobs: - ${{ if parameters.publishSymbols }}: - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self parameters: - artifactName: 'Microsoft.Data.SqlClient_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.mdsPackageVersion }}_$(System.TimelineId)' + artifactName: 'Microsoft.Data.SqlClient_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.sqlClientPackageVersion }}_$(System.TimelineId)' azureSubscription: '${{ parameters.symbolsAzureSubscription }}' packageName: 'Microsoft.Data.SqlClient' publishProjectName: '${{ parameters.symbolsPublishProjectName }}' @@ -179,4 +179,4 @@ jobs: publishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' searchPattern: '**/Microsoft.Data.SqlClient*.pdb' # @TODO: This seems very heavy uploadAccount: '${{ parameters.symbolsUploadAccount }}' - version: '${{ parameters.mdsPackageVersion }}' + version: '${{ parameters.sqlClientPackageVersion }}' diff --git a/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml b/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml index c04a2eceef..b7a1d8f62e 100644 --- a/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml +++ b/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml @@ -65,7 +65,7 @@ jobs: variables: - name: ob_outputDirectory - value: $(Build.SourcesDirectory)/output + value: $(PACK_OUTPUT) - name: artifactPath value: $(Pipeline.Workspace)/${{ parameters.artifactName }} diff --git a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml index 2a4bf08e97..bc566398cc 100644 --- a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml @@ -4,19 +4,42 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - # The name of the pipeline artifacts to download prior to building the tests. + # The name of the pipeline artifact to download that contains the SqlClient NuGet package. - name: artifactName type: string - # True if this build is a preview. - - name: isPreview - type: boolean + # List of versions of dotnet that are *allowed to exist* in the NuGet package. Separators do not + # matter as the folders in lib, runtime, etc are simply checked to see if they exist in this + # string. + - name: expectedDotnetVersions + type: string + default: 'net462;net8.0;net9.0;netstandard2.0' + + # Expected file version of the assemblies within the package. This should be of the form: + # (major).(minor).(patch).(buildNumber) + - name: expectedFileVersion + type: string + + # List of folders that are *allowed to exist* in the NuGet package. Separators do not matter as + # the folders are simply checked to see if they exist in this string. + - name: expectedFolderNames + type: string + default: 'lib;ref;runtimes' + + # Expected NuGet package version. Used to build the installation path. This should be of the + # form: (major).(minor).(patch)[-preview(preview_number)] + - name: expectedPackageVersion + type: string # True if this build is an official build. This will be used to gate some checks # that only apply to official builds, such as signature verification. - name: isOfficial type: boolean + # True if this build is a preview. + - name: isPreview + type: boolean + jobs: - job: validate_nuget_package displayName: "Validate NuGet package" @@ -28,66 +51,96 @@ jobs: vmImage: "ADO-MMS22-SQL19" variables: # More settings at https://aka.ms/obpipelines/yaml/jobs - - template: /eng/pipelines/onebranch/variables/sqlclient-validation-variables.yml@self - - name: pathToDownloadedNuget # path to the downloaded nuget files - value: $(Pipeline.Workspace)\${{parameters.artifactName }} + - name: artifactPath + value: '$(Pipeline.Workspace)\${{ parameters.artifactName }}' - - ${{ if parameters.isPreview }}: - - name: extractedNugetPath - value: $(extractedNugetRootPath).$(mdsPackagePreviewVersion) - - name: mdsPackageVersion - value: $(mdsPackagePreviewVersion) + # Path to the SqlClient NuGet package after installation. This path will only exist once the package + # been installed. + - name: nugetPackageInstallPath + value: '$(Pipeline.Workspace)\nugetPackageInstalls\Microsoft.Data.SqlClient\${{ parameters.expectedPackageVersion }}' + + # Root folder where NuGet package will be installed locally + - name: nugetPackageInstallRoot + value: '$(Pipeline.Workspace)\nugetPackageInstalls' steps: - - script: SET - displayName: "Print Environment Variables" + - template: '/eng/pipelines/onebranch/steps/script-output-environment-variables-step.yml@self' - task: NuGetToolInstaller@1 - displayName: "Use NuGet" + displayName: "Install NuGet" - powershell: | - # Displays the paths of all the local cache directories + echo "> 1. List all local cache directory paths" nuget locals all -List - #Clears all files from all local cache directories + echo "> 2. Clear all files from local cache directories" nuget locals all -Clear displayName: "Clear local cache" + # Download NuGet packages from the specified build artifact. - download: current - artifact: ${{parameters.artifactName}} + artifact: ${{ parameters.artifactName }} patterns: "**/*.*nupkg" displayName: "Download NuGet Package" - - powershell: | - # Install nuget package - Install-Package -Name "Microsoft.Data.SqlClient" -Destination "$(TempFolderName)" -Force -Source $(pathToDownloadedNuget) -SkipDependencies - - Write-Host "--------------------------------------------------" - Write-Host '$(TempFolderName)' - ls $(TempFolderName) - Write-Host "--------------------------------------------------" - displayName: "Extract Nuget in temp folder" - + # Verify secure signatures on the NuGet packages. + # NOTE: Packages will only be signed if the build is official. - ${{ if eq(parameters.isOfficial, true) }}: - powershell: | - Write-Host "--------------------------------------------------" - Write-Host "This will verify the artifact signature" -ForegroundColor Green - Write-Host "--------------------------------------------------" + # Propagate parameters to PS variables ####################### + $artifactPath = "${{ variables.artifactPath }}" + echo "artifactPath= $artifactPath" - nuget verify -All $(pathToDownloadedNuget)\*.nupkg - nuget verify -All $(pathToDownloadedNuget)\*.snupkg + # Verify package signatures ################################## + echo "> 1. Verify signature of source package(s)" + nuget verify -All $artifactPath\*.nupkg + + echo "> 2. Verify signature of symbols package(s)" + nuget verify -All $artifactPath\*.snupkg displayName: "Verify nuget signature" + # Install NuGet package to the temporary directory + - powershell: | + # Propagate pipeline to PS variables ############################# + $artifactPath = "${{ variables.artifactPath }}" + echo "artifactPath= $artifactPath" + + $nugetPackageInstallRoot = "${{ variables.nugetPackageInstallRoot }}" + echo "nugetPackageInstallRoot= $nugetPackageInstallRoot" + + # Install NuGet Package ########################################## + echo "> 1. Installing Microsoft.Data.SqlClient NuGet package..." + Install-Package ` + -Name "Microsoft.Data.SqlClient" ` + -Source $artifactPath ` + -Destination $nugetPackageInstallRoot ` + -Force ` + -SkipDependencies + + echo "> 2. Listing contents of installed Microsoft.Data.SqlClient NuGet package:" + Write-Host $artifactPath + Get-ChildItem $artifactPath + displayName: "Install NuGet Package" + + # Find all DLL files in the installed NuGet package, verify each is signed with a strong name - powershell: | - # Recursively find all .dll files in TempFolder (installed nuget folder) - # Microsoft.Data.SqlClient.dll and Microsoft.Data.SqlClient.resources.dll (in localized folders) should have strong name - $dllFiles = Get-ChildItem -Path $(TempFolderName) -Recurse -Filter *.dll + # Propagate pipeline to PS variables ############################# + $nugetPackageInstallPath = "${{ variables.nugetPackageInstallPath }}" + echo "nugetPackageInstallPath= $nugetPackageInstallPath" + + # Verify strong name signing ##################################### + echo "> 1. Verifying strong name signing of DLLs ..." + + # @TODO: This path seems brittle to VS upgrades, can we make it more flexible? + $snPath = "C:\Program Files (x86)\Microsoft SDKs\Windows\*\bin\NETFX 4.8.1 Tools\sn.exe" + + $dllFiles = Get-ChildItem -Path $nugetPackageInstallPath -Recurse -Filter *.dll $badDlls = @() foreach ($file in $dllFiles) { # Run sn.exe to verify the strong name on each dll - $result = & "C:\Program Files (x86)\Microsoft SDKs\Windows\*\bin\NETFX 4.8.1 Tools\sn.exe" -vf $file.FullName + $result = & $snPath -vf $file.FullName Write-OutPut $result # if the dll is not valid, it would be delay signed or test-signed which is not meant for production @@ -108,28 +161,49 @@ jobs: Write-Host "Strong name has been verified for all dlls" displayName: "Verify assembly strong names" + # Validate that the folders in the nuget are expected + # @TODO: This does not verify we are not missing any folders, only that the folders that + # exist are expected to exist. - powershell: | - # Checks the expected folder names such as lib, ref, runtimes - Get-ChildItem -Path $(extractedNugetPath) -Directory | select Name | foreach { - if('$(expectedFolderNames)'.contains($_.Name)){ - Write-Host expected folder name verfied: $_.Name - } + # Propagate pipeline to PS variables ############################# + $expectedFolderNames = "${{ parameters.expectedFolderNames }}" + echo "expectedFolderNames= $expectedFolderNames" + + $nugetPackageInstallPath = "${{ variables.nugetPackageInstallPath }}" + echo "nugetPackageInstallPath= $nugetPackageInstallPath" + + # Verify folders are expected #################################### + Get-ChildItem -Path $nugetPackageInstallPath -Directory | select Name | foreach { + if($expectedFolderNames.contains($_.Name)){ + Write-Host expected folder name verfied: $_.Name + } } - displayName: "Check expected folder names" + displayName: "Verify NuGet Root Folder Structure" + # Validate that the folders within the root folders of the nuget are expected + # @TODO: This does not verify we are not missing any folders, only that the folders that + # exist are expected to exist. - powershell: | + # Propagate pipeline to PS variables ############################# + $expectedDotnetVersions = "${{ parameters.expectedDotnetVersions }}" + echo "expectedDotnetVersions= $expectedDotnetVersions" + + $nugetPackageInstallPath = "${{ variables.nugetPackageInstallPath }}" + echo "nugetPackageInstallPath= $nugetPackageInstallPath" + + # Verify folders are expected #################################### # Checks the version of DotNetFramework and DotNet $countErr = 0 $countPass = 0 $excludNamesFromRuntimeFolder = 'lib','win','unix' - Get-ChildItem -Path $(extractedNugetPath) -Directory | foreach { + Get-ChildItem -Path $nugetPackageInstallPath -Directory | foreach { $parentname=$_.Name Write-Host $_.FullName -ForegroundColor yellow if($_.Name -ne 'runtimes') { Get-ChildItem -Path $_.FullName -Directory | select Name | foreach { - if('$(expectedDotnetVersions)'.Contains($_.Name)){ + if($expectedDotnetVersions.Contains($_.Name)){ Write-Host "`tExpected version verified in $parentname": $_.Name -ForegroundColor green $countPass += 1 } @@ -142,7 +216,7 @@ jobs: elseif ($_.Name -eq 'runtimes'){ Get-ChildItem -Depth 3 -Path $_.FullName -Exclude $excludNamesFromRuntimeFolder -Directory | foreach{ - if('$(expectedDotnetVersions)'.Contains($_.Name)){ + if('${{ parameters.expectedDotnetVersions }}'.Contains($_.Name)){ Write-Host "`tExpected version verfied in $parentname": $_.Name $countPass += 1 } @@ -167,18 +241,19 @@ jobs: Write-Host "Unexpected versions are detected!" -ForegroundColor Red Exit -1 } - displayName: "Check Expected framework" + displayName: "Verify NuGet DotNet Versions " - powershell: | - # list all the child items of created temp folder + # Propagate pipeline to PS variables ############################# + $nugetPackageInstallPath = "${{ variables.nugetPackageInstallPath }}" + echo "nugetPackageInstallPath= $nugetPackageInstallPath" - #Verify all DLLs unzipped match "expected" hierarchy - - foreach( $folderName in (Get-ChildItem -Path $(extractedNugetPath) -Directory).Name) + # Verify DLL Hierarchy ########################################### + foreach( $folderName in (Get-ChildItem -Path $nugetPackageInstallPath -Directory).Name) { # List all Childerns of the Path - Get-ChildItem -Path $(extractedNugetPath)\$folderName -Recurse -File - $subFiles = Get-ChildItem -Path $(extractedNugetPath)\$folderName -Recurse -File + Get-ChildItem -Path $nugetPackageInstallPath\$folderName -Recurse -File + $subFiles = Get-ChildItem -Path $nugetPackageInstallPath\$folderName -Recurse -File foreach($file in $subFiles) { @@ -211,11 +286,16 @@ jobs: } displayName: 'Verify all DLLs unzipped match "expected" hierarchy' + # Verify that all DLLs are authenticode signed + # NOTE: This signing is only performed on official builds. - ${{ if eq(parameters.isOfficial, true) }}: - powershell: | - # Verify all dlls status are Valid + # Propagate pipeline to PS variables ############################# + $nugetPackageInstallPath = "${{ variables.nugetPackageInstallPath }}" + echo "nugetPackageInstallPath= $nugetPackageInstallPath" - $dlls = Get-ChildItem -Path $(extractedNugetPath) -Recurse -Include *.dll + # Verify authenticode signature of DLLs ########################## + $dlls = Get-ChildItem -Path $nugetPackageInstallPath -Recurse -Include *.dll foreach ($status in $dlls | Get-AuthenticodeSignature) { if ($status.Status -eq "Valid") @@ -232,20 +312,27 @@ jobs: displayName: "Verify all dlls status are Valid" - powershell: | - # This will check each DLL's ProductVersion and FileVersion against - # expected values. - $failed = 0 - $expectedFileVersion = "$(mdsPackageVersion)".Split('-')[0] + ".$(assemblyBuildNumber)" + # Propagate pipeline to PS variables ############################# + $expectedFileVersion = "${{ parameters.expectedFileVersion }}" + echo "expectedFileVersion= $expectedFileVersion" - foreach ( $pVersion in Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo ) + $expectedPackageVersion = "${{ parameters.expectedPackageVersion }}" + echo "expectedPackageVersion= $expectedPackageVersion" + + $nugetPackageInstallPath = "${{ variables.nugetPackageInstallPath }}" + echo "nugetPackageInstallPath= $nugetPackageInstallPath" + + # Validate ProductVersion and FileVersion fields ################# + $failed = 0 + foreach ( $pVersion in Get-ChildItem *.dll -Path $nugetPackageInstallPath -Recurse | ForEach-Object versioninfo ) { - if ($pVersion.ProductVersion -Like '$(mdsPackageVersion)*') + if ($pVersion.ProductVersion -Like $expectedPackageVersion + '*') { Write-Host -ForegroundColor Green "Correct ProductVersion detected for $($pVersion.FileName): $($pVersion.ProductVersion)" } else { - Write-Host -ForegroundColor Red "Wrong ProductVersion detected for $($pVersion.FileName); expected: $(mdsPackageVersion); found: $($pVersion.ProductVersion)" + Write-Host -ForegroundColor Red "Wrong ProductVersion detected for $($pVersion.FileName); expected: $expectedPackageVersion; found: $($pVersion.ProductVersion)" $failed = 1 } @@ -258,6 +345,8 @@ jobs: Write-Host -ForegroundColor Red "Wrong FileVersion detected for $($pVersion.FileName); expected $expectedFileVersion; found: $($pVersion.FileVersion)" $failed = 1 } + + # @TODO: We should do a check for assembly version here. } if ($failed -ne 0) @@ -265,5 +354,5 @@ jobs: Exit -1 } - Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object VersionInfo | Format-List + Get-ChildItem *.dll -Path $nugetPackageInstallPath -Recurse | ForEach-Object VersionInfo | Format-List displayName: 'Verify "File Version" matches expected values for DLLs' diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index d4b62ac5e8..1ab3c653c3 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -45,7 +45,7 @@ parameters: default: true # Build the Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider package. - - name: buildAKVProvider + - name: buildAkvProvider displayName: Build Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider type: boolean default: true @@ -84,41 +84,17 @@ parameters: default: false # Release the Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider package. - - name: releaseAKVProvider + - name: releaseAkvProvider displayName: Release Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider type: boolean default: false variables: + - template: /eng/pipelines/onebranch/variables/common-variables.yml@self - template: /eng/pipelines/onebranch/variables/onebranch-variables.yml@self - - # Define the effective versions for all of the packages we build and release. - - ${{ if parameters.isPreview }}: - - name: effectiveSqlServerVersion - value: $(sqlServerPackagePreviewVersion) - - name: effectiveLoggingVersion - value: $(loggingPackagePreviewVersion) - - name: effectiveAbstractionsVersion - value: $(abstractionsPackagePreviewVersion) - - name: effectiveSqlClientVersion - value: $(mdsPackagePreviewVersion) - - name: effectiveAzureVersion - value: $(azurePackagePreviewVersion) - - name: effectiveAkvProviderVersion - value: $(akvPackagePreviewVersion) - - ${{ else }}: - - name: effectiveSqlServerVersion - value: $(sqlServerPackageVersion) - - name: effectiveLoggingVersion - value: $(loggingPackageVersion) - - name: effectiveAbstractionsVersion - value: $(abstractionsPackageVersion) - - name: effectiveSqlClientVersion - value: $(mdsPackageVersion) - - name: effectiveAzureVersion - value: $(azurePackageVersion) - - name: effectiveAkvProviderVersion - value: $(akvPackageVersion) + - template: /eng/pipelines/onebranch/variables/package-variables.yml@self + parameters: + isPreview: ${{ parameters.isPreview }} resources: repositories: @@ -130,64 +106,110 @@ resources: extends: # See: https://aka.ms/obpipelines/templates template: /v2/OneBranch.NonOfficial.CrossPlat.yml@templates + parameters: + featureFlags: + EnableCDPxPAT: false + WindowsHostVersion: 1ESWindows2022 + release: # This indicates the pipeline category to deploy Box products. See: # https://eng.ms/docs/products/onebranch/release/yamlreleasepipelines/deployboxproducts category: NonAzure - featureFlags: - EnableCDPxPAT: false - WindowsHostVersion: 1ESWindows2022 + # See: https://aka.ms/obpipelines/sdl globalSdl: - tsa: - # We disable TSA for non-official pipelines. This inhibits spurious TSA alerts and ADO - # task creation, since non-official builds are often done against development branches. - enabled: false apiscan: + # APIScan "software name" and version parameters are set on a per-job basis with via + # ob_sdl_apiscan_* variables. enabled: true break: false - # Other ApiScan options are set by the jobs via ob_sdl_apiscan_* variables. + + armory: + enabled: true + + asyncSdl: + # Disabling this as it complicates the build process with minimal gain + enabled: false + + binskim: + enabled: true + codeql: - compiled: enabled: true - sbom: + compiled: + + credscan: enabled: true - packageName: Microsoft.Data.SqlClient - packageVersion: $(Build.BuildNumber) + suppressionsFile: '$(REPO_ROOT)/.config/CredScanSuppressions.json' + + eslint: + # Only useful for repos with ECMAscript - which we do not have. + enabled: false + policheck: enabled: true - break: true - exclusionsFile: $(REPO_ROOT)\.config\PolicheckExclusions.xml - asyncSdl: + exclusionsFile: '$(REPO_ROOT)\.config\PolicheckExclusions.xml' + + roslyn: + # Note, requires RoslynAnalyzers task to be added as a separate step + enabled: true + + publishLogs: + enabled: true + + sbom: + enabled: true + packageName: 'Microsoft.Data.SqlClient' + packageVersion: '$(Build.BuildNumber)' + + tsa: + # We disable TSA for non-official pipelines. This inhibits spurious TSA alerts and ADO task + # creation, since non-official builds are often done against development branches. This also + # forces all SDL tasks into "break" build mode. enabled: false - credscan: - enabled: true - suppressionsFile: $(REPO_ROOT)/.config/CredScanSuppressions.json - binskim: - enabled: true - armory: - enabled: true - break: true - eslint: - enabled: false - roslyn: - enabled: true - break: true - publishLogs: - enabled: true - tsaOptionsPath: $(REPO_ROOT)\.config\tsaoptions.json - disableLegacyManifest: true + configFile: '$(REPO_ROOT)/.config/tsaoptions.json' + stages: - template: /eng/pipelines/onebranch/stages/build-stages.yml@self parameters: - debug: ${{ parameters.debug }} isPreview: ${{ parameters.isPreview }} isOfficial: false # This is a non-official pipeline. publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} buildSqlClient: ${{ parameters.buildSqlClient }} - buildAKVProvider: ${{ parameters.buildAKVProvider }} + buildAkvProvider: ${{ parameters.buildAkvProvider }} + + abstractionsArtifactsName: '${{ variables.abstractionsArtifactsName }}' + abstractionsFileVersion: '${{ variables.abstractionsFileVersion }}' + abstractionsPackageVersion: '${{ variables.abstractionsPackageVersion }}' + akvProviderArtifactsName: '${{ variables.akvProviderArtifactsName }}' + akvProviderFileVersion: '${{ variables.akvProviderFileVersion }}' + akvProviderPackageVersion: '${{ variables.akvProviderPackageVersion }}' + azureArtifactsName: '${{ variables.azureArtifactsName }}' + azureFileVersion: '${{ variables.azureFileVersion }}' + azurePackageVersion: '${{ variables.azurePackageVersion }}' + loggingArtifactsName: '${{ variables.loggingArtifactsName }}' + loggingFileVersion: '${{ variables.loggingFileVersion }}' + loggingPackageVersion: '${{ variables.loggingPackageVersion }}' + sqlClientArtifactsName: '${{ variables.sqlClientArtifactsName }}' + sqlClientFileVersion: '${{ variables.sqlClientFileVersion }}' + sqlClientPackageVersion: '${{ variables.sqlClientPackageVersion }}' + sqlServerArtifactsName: '${{ variables.sqlServerArtifactsName }}' + sqlServerFileVersion: '${{ variables.sqlServerFileVersion }}' + sqlServerPackageVersion: '${{ variables.sqlServerPackageVersion }}' + + signingAppRegistrationClientId: '$(SigningAppRegistrationClientId)' + signingAppRegistrationTenantId: '$(SigningAppRegistrationTenantId)' + signingAuthAkvName: '$(SigningAuthAkvName)' + signingAuthSignCertName: '$(SigningAuthSignCertName)' + signingEsrpClientId: '$(SigningEsrpClientId)' + signingEsrpConnectedServiceName: '$(SigningEsrpConnectedServiceName)' + symbolsAzureSubscription: '$(SymbolsAzureSubscription)' + symbolsPublishProjectName: '$(SymbolsPublishProjectName)' + symbolsPublishServer: '$(SymbolsPublishServer)' + symbolsPublishTokenUri: '$(SymbolsPublishTokenUri)' + symbolsUploadAccount: '$(SymbolsUploadAccount)' - template: /eng/pipelines/onebranch/stages/release-stages.yml@self parameters: @@ -197,9 +219,23 @@ extends: # This is _not_ an official pipeline. isOfficial: false stageNameSuffix: test + + abstractionsArtifactsName: '${{ variables.abstractionsArtifactsName }}' + abstractionsPackageVersion: '${{ variables.abstractionsPackageVersion }}' + akvProviderArtifactsName: '${{ variables.akvProviderArtifactsName }}' + akvProviderPackageVersion: '${{ variables.akvProviderPackageVersion }}' + azureArtifactsName: '${{ variables.azureArtifactsName }}' + azurePackageVersion: '${{ variables.azurePackageVersion }}' + loggingArtifactsName: '${{ variables.loggingArtifactsName }}' + loggingPackageVersion: '${{ variables.loggingPackageVersion }}' + sqlClientArtifactsName: '${{ variables.sqlClientArtifactsName }}' + sqlClientPackageVersion: '${{ variables.sqlClientPackageVersion }}' + sqlServerArtifactsName: '${{ variables.sqlServerArtifactsName }}' + sqlServerPackageVersion: '${{ variables.sqlServerPackageVersion }}' + releaseSqlServerServer: ${{ parameters.releaseSqlServerServer }} releaseLogging: ${{ parameters.releaseLogging }} releaseAbstractions: ${{ parameters.releaseAbstractions }} releaseSqlClient: ${{ parameters.releaseSqlClient }} releaseAzure: ${{ parameters.releaseAzure }} - releaseAKVProvider: ${{ parameters.releaseAKVProvider }} + releaseAkvProvider: ${{ parameters.releaseAkvProvider }} diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index 93287c3c51..c6c18d679a 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -66,7 +66,7 @@ parameters: default: true # Build the Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider package. - - name: buildAKVProvider + - name: buildAkvProvider displayName: Build Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider type: boolean default: true @@ -105,41 +105,17 @@ parameters: default: false # Release the Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider package. - - name: releaseAKVProvider + - name: releaseAkvProvider displayName: Release Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider type: boolean default: false variables: + - template: /eng/pipelines/onebranch/variables/common-variables.yml@self - template: /eng/pipelines/onebranch/variables/onebranch-variables.yml@self - - # Define the effective versions for all of the packages we build and release. - - ${{ if parameters.isPreview }}: - - name: effectiveSqlServerVersion - value: $(sqlServerPackagePreviewVersion) - - name: effectiveLoggingVersion - value: $(loggingPackagePreviewVersion) - - name: effectiveAbstractionsVersion - value: $(abstractionsPackagePreviewVersion) - - name: effectiveSqlClientVersion - value: $(mdsPackagePreviewVersion) - - name: effectiveAzureVersion - value: $(azurePackagePreviewVersion) - - name: effectiveAkvProviderVersion - value: $(akvPackagePreviewVersion) - - ${{ else }}: - - name: effectiveSqlServerVersion - value: $(sqlServerPackageVersion) - - name: effectiveLoggingVersion - value: $(loggingPackageVersion) - - name: effectiveAbstractionsVersion - value: $(abstractionsPackageVersion) - - name: effectiveSqlClientVersion - value: $(mdsPackageVersion) - - name: effectiveAzureVersion - value: $(azurePackageVersion) - - name: effectiveAkvProviderVersion - value: $(akvPackageVersion) + - template: /eng/pipelines/onebranch/variables/package-variables.yml@self + parameters: + isPreview: ${{ parameters.isPreview }} resources: repositories: @@ -150,69 +126,118 @@ resources: extends: # See: https://aka.ms/obpipelines/templates - template: /v2/OneBranch.Official.CrossPlat.yml@templates + template: '/v2/OneBranch.Official.CrossPlat.yml@templates' + parameters: + featureFlags: + EnableCDPxPAT: false + WindowsHostVersion: 1ESWindows2022 + release: # This indicates the pipeline category to deploy Box products. See: # https://eng.ms/docs/products/onebranch/release/yamlreleasepipelines/deployboxproducts category: NonAzure - featureFlags: - EnableCDPxPAT: false - WindowsHostVersion: 1ESWindows2022 + # See: https://aka.ms/obpipelines/sdl globalSdl: - 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: + # APIScan "software name" and version parameters are set on a per-job basis with via + # ob_sdl_apiscan_* variables. enabled: true # TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/42858): # We have temporarily disabled breaking the build on ApiScan results until we can: # - Register our new packages with API Scan, and/or # - Publish MSDN/Learn/etc documentation for the new packages. break: false - # Other ApiScan options are set by the jobs via ob_sdl_apiscan_* variables. + + armory: + enabled: true + break: true + + asyncSdl: + # Disabling this as it complicates the build process with minimal gain + enabled: false + + binskim: + enabled: true + break: true + codeql: - compiled: enabled: true - sbom: + compiled: + + credscan: enabled: true - packageName: Microsoft.Data.SqlClient - packageVersion: $(Build.BuildNumber) + suppressionsFile: '$(REPO_ROOT)/.config/CredScanSuppressions.json' + + eslint: + # Only useful for repos with ECMAscript - which we do not have. + enabled: false + policheck: enabled: true break: true - exclusionsFile: $(REPO_ROOT)\.config\PolicheckExclusions.xml - asyncSdl: - enabled: false - credscan: - enabled: true - suppressionsFile: $(REPO_ROOT)/.config/CredScanSuppressions.json - binskim: - enabled: true - armory: - enabled: true - break: true - eslint: - enabled: false - roslyn: - enabled: true - break: true - publishLogs: - enabled: true - tsaOptionsPath: $(REPO_ROOT)\.config\tsaoptions.json - disableLegacyManifest: true + exclusionsFile: '$(REPO_ROOT)\.config\PolicheckExclusions.xml' + + roslyn: + # Note, requires RoslynAnalyzers task to be added as a separate step + enabled: true + break: true + + publishLogs: + enabled: true + + sbom: + enabled: true + packageName: 'Microsoft.Data.SqlClient' + packageVersion: '$(Build.BuildNumber)' + + tsa: + # OneBranch publishes all sdl results to TSA. If TSA is disabled, all SDL tools will be + # forced into 'break' build mode. + enabled: true + configFile: '$(REPO_ROOT)/.config/tsaoptions.json' + stages: - template: /eng/pipelines/onebranch/stages/build-stages.yml@self parameters: - debug: ${{ parameters.debug }} isPreview: ${{ parameters.isPreview }} isOfficial: true # This is an official pipeline. publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} buildSqlClient: ${{ parameters.buildSqlClient }} - buildAKVProvider: ${{ parameters.buildAKVProvider }} + buildAkvProvider: ${{ parameters.buildAkvProvider }} + + abstractionsArtifactsName: '${{ variables.abstractionsArtifactsName }}' + abstractionsFileVersion: '${{ variables.abstractionsFileVersion }}' + abstractionsPackageVersion: '${{ variables.abstractionsPackageVersion }}' + akvProviderArtifactsName: '${{ variables.akvProviderArtifactsName }}' + akvProviderFileVersion: '${{ variables.akvProviderFileVersion }}' + akvProviderPackageVersion: '${{ variables.akvProviderPackageVersion }}' + azureArtifactsName: '${{ variables.azureArtifactsName }}' + azureFileVersion: '${{ variables.azureFileVersion }}' + azurePackageVersion: '${{ variables.azurePackageVersion }}' + loggingArtifactsName: '${{ variables.loggingArtifactsName }}' + loggingFileVersion: '${{ variables.loggingFileVersion }}' + loggingPackageVersion: '${{ variables.loggingPackageVersion }}' + sqlClientArtifactsName: '${{ variables.sqlClientArtifactsName }}' + sqlClientFileVersion: '${{ variables.sqlClientFileVersion }}' + sqlClientPackageVersion: '${{ variables.sqlClientPackageVersion }}' + sqlServerArtifactsName: '${{ variables.sqlServerArtifactsName }}' + sqlServerFileVersion: '${{ variables.sqlServerFileVersion }}' + sqlServerPackageVersion: '${{ variables.sqlServerPackageVersion }}' + + signingAppRegistrationClientId: '$(SigningAppRegistrationClientId)' + signingAppRegistrationTenantId: '$(SigningAppRegistrationTenantId)' + signingAuthAkvName: '$(SigningAuthAkvName)' + signingAuthSignCertName: '$(SigningAuthSignCertName)' + signingEsrpClientId: '$(SigningEsrpClientId)' + signingEsrpConnectedServiceName: '$(SigningEsrpConnectedServiceName)' + symbolsAzureSubscription: '$(SymbolsAzureSubscription)' + symbolsPublishProjectName: '$(SymbolsPublishProjectName)' + symbolsPublishServer: '$(SymbolsPublishServer)' + symbolsPublishTokenUri: '$(SymbolsPublishTokenUri)' + symbolsUploadAccount: '$(SymbolsUploadAccount)' - template: /eng/pipelines/onebranch/stages/release-stages.yml@self parameters: @@ -221,9 +246,23 @@ extends: # This is an official pipeline. isOfficial: true stageNameSuffix: production + + abstractionsArtifactsName: '${{ variables.abstractionsArtifactsName }}' + abstractionsPackageVersion: '${{ variables.abstractionsPackageVersion }}' + akvProviderArtifactsName: '${{ variables.akvProviderArtifactsName }}' + akvProviderPackageVersion: '${{ variables.akvProviderPackageVersion }}' + azureArtifactsName: '${{ variables.azureArtifactsName }}' + azurePackageVersion: '${{ variables.azurePackageVersion }}' + loggingArtifactsName: '${{ variables.loggingArtifactsName }}' + loggingPackageVersion: '${{ variables.loggingPackageVersion }}' + sqlClientArtifactsName: '${{ variables.sqlClientArtifactsName }}' + sqlClientPackageVersion: '${{ variables.sqlClientPackageVersion }}' + sqlServerArtifactsName: '${{ variables.sqlServerArtifactsName }}' + sqlServerPackageVersion: '${{ variables.sqlServerPackageVersion }}' + releaseSqlServerServer: ${{ parameters.releaseSqlServerServer }} releaseLogging: ${{ parameters.releaseLogging }} releaseAbstractions: ${{ parameters.releaseAbstractions }} releaseSqlClient: ${{ parameters.releaseSqlClient }} releaseAzure: ${{ parameters.releaseAzure }} - releaseAKVProvider: ${{ parameters.releaseAKVProvider }} + releaseAkvProvider: ${{ parameters.releaseAkvProvider }} diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index 738e91d26f..14e7977c3f 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -12,22 +12,14 @@ # - build_independent: builds packages with no cross-package dependencies (Logging, SqlServer) # - build_abstractions: builds the Abstractions package, which depends on Logging # - build_dependent: builds packages with dependencies on Abstractions (SqlClient, Azure) -# - build_addons: builds add-on packages with dependencies on the core packages (AKV Provider) -# -# This template depends on the following runtime (i.e. macro expansion) variables being defined: -# -# - effectiveSqlServerVersion -# - effectiveLoggingVersion -# - effectiveAbstractionsVersion -# - effectiveSqlClientVersion -# - effectiveAzureVersion -# - effectiveAkvProviderVersion +# - build_addons: builds add-on packages with dependencies on the core packages (Akv Provider) parameters: # ── General parameters ───────────────────────────────────────────────── - # True to enable debug information and steps. - - name: debug + # True if this is an official build, which runs additional ESRP malware scanning + # and codesigning steps. + - name: isOfficial type: boolean # True if this is a preview build, which uses the preview version numbers from @@ -35,11 +27,6 @@ parameters: - name: isPreview type: boolean - # True if this is an official build, which runs additional ESRP malware scanning - # and codesigning steps. - - name: isOfficial - type: boolean - # True to publish symbols to public and private servers. - name: publishSymbols type: boolean @@ -52,9 +39,102 @@ parameters: - name: buildSqlClient type: boolean - - name: buildAKVProvider + - name: buildAkvProvider type: boolean + # Package Parameters ----------------------------------------------------- + + - name: abstractionsArtifactsName + type: string + + - name: abstractionsFileVersion + type: string + + - name: abstractionsPackageVersion + type: string + + - name: akvProviderArtifactsName + type: string + + - name: akvProviderFileVersion + type: string + + - name: akvProviderPackageVersion + type: string + + - name: azureArtifactsName + type: string + + - name: azureFileVersion + type: string + + - name: azurePackageVersion + type: string + + - name: loggingArtifactsName + type: string + + - name: loggingFileVersion + type: string + + - name: loggingPackageVersion + type: string + + - name: sqlClientArtifactsName + type: string + + - name: sqlClientFileVersion + type: string + + - name: sqlClientPackageVersion + type: string + + - name: sqlServerArtifactsName + type: string + + - name: sqlServerFileVersion + type: string + + - name: sqlServerPackageVersion + type: string + + # Signing Parameters ----------------------------------------------------- + + - name: signingAppRegistrationClientId + type: string + + - name: signingAppRegistrationTenantId + type: string + + - name: signingAuthAkvName + type: string + + - name: signingAuthSignCertName + type: string + + - name: signingEsrpClientId + type: string + + - name: signingEsrpConnectedServiceName + type: string + + # Symbols Publishing Parameters ------------------------------------------ + + - name: symbolsAzureSubscription + type: string + + - name: symbolsPublishProjectName + type: string + + - name: symbolsPublishServer + type: string + + - name: symbolsPublishTokenUri + type: string + + - name: symbolsUploadAccount + type: string + stages: # ==================================================================== # Stage 1: Independent packages (no cross-package dependencies) @@ -64,43 +144,56 @@ stages: displayName: "Build Independent Packages" jobs: - - ${{ if or(eq(parameters.buildAKVProvider, true), eq(parameters.buildSqlClient, true)) }}: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self - parameters: - packageName: Logging - packageFullName: Microsoft.Data.SqlClient.Internal.Logging - packageVersion: $(effectiveLoggingVersion) - versionProperties: >- - -p:LoggingPackageVersion=$(effectiveLoggingVersion) - -p:LoggingAssemblyFileVersion=$(loggingAssemblyFileVersion) - assemblyFileVersion: $(loggingAssemblyFileVersion) - publishSymbols: ${{ parameters.publishSymbols }} - isOfficial: ${{ parameters.isOfficial }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) + - ${{ if or(eq(parameters.buildAkvProvider, true), eq(parameters.buildSqlClient, true)) }}: + - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + parameters: + signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' + signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' + signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' + signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + isOfficial: ${{ parameters.isOfficial }} + packageName: Logging + packageFullName: Microsoft.Data.SqlClient.Internal.Logging + packageVersion: '${{ parameters.loggingPackageVersion }}' + versionProperties: >- + -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} + -p:LoggingAssemblyFileVersion=${{ parameters.loggingFileVersion }} + assemblyFileVersion: '${{ parameters.loggingFileVersion }}' + publishSymbols: ${{ parameters.publishSymbols }} + - ${{ if eq(parameters.buildSqlServerServer, true) }}: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self - parameters: - packageName: SqlServer - packageFullName: Microsoft.SqlServer.Server - packageVersion: $(effectiveSqlServerVersion) - versionProperties: >- - -p:SqlServerAssemblyFileVersion=$(sqlServerAssemblyFileVersion) - -p:SqlServerPackageVersion=$(effectiveSqlServerVersion) - assemblyFileVersion: $(sqlServerAssemblyFileVersion) - publishSymbols: ${{ parameters.publishSymbols }} - isOfficial: ${{ parameters.isOfficial }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) + - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + parameters: + signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' + signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' + signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' + signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + isOfficial: ${{ parameters.isOfficial }} + packageName: SqlServer + packageFullName: Microsoft.SqlServer.Server + packageVersion: '${{ parameters.sqlServerPackageVersion }}' + versionProperties: >- + -p:SqlServerAssemblyFileVersion=${{ parameters.sqlServerFileVersion }} + -p:SqlServerPackageVersion=${{ parameters.sqlServerPackageVersion }} + assemblyFileVersion: '${{ parameters.sqlServerFileVersion }}' + publishSymbols: ${{ parameters.publishSymbols }} # ==================================================================== # Stage 2: Abstractions package (depends on Logging from Stage 1) @@ -115,29 +208,35 @@ stages: jobs: - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self parameters: + signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' + signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' + signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' + signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + isOfficial: ${{ parameters.isOfficial }} packageName: Abstractions packageFullName: Microsoft.Data.SqlClient.Extensions.Abstractions - packageVersion: $(effectiveAbstractionsVersion) + packageVersion: '${{ parameters.abstractionsPackageVersion }}' versionProperties: >- - -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) - -p:AbstractionsAssemblyFileVersion=$(abstractionsAssemblyFileVersion) - -p:LoggingPackageVersion=$(effectiveLoggingVersion) - assemblyFileVersion: $(abstractionsAssemblyFileVersion) + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + -p:AbstractionsAssemblyFileVersion=${{ parameters.abstractionsFileVersion }} + -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} + assemblyFileVersion: '${{ parameters.abstractionsFileVersion }}' publishSymbols: ${{ parameters.publishSymbols }} - isOfficial: ${{ parameters.isOfficial }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) downloadArtifacts: - - artifactName: $(loggingArtifactsName) - displayName: Logging Package + - artifactName: '${{ parameters.loggingArtifactsName }}' + displayName: 'Logging Package' # ==================================================================== # Stage 3: Core packages (depend on Abstractions) - # MDS and Extensions.Azure build in parallel after Abstractions. + # SqlClient and Extensions.Azure build in parallel after Abstractions. # Stage name kept as 'build_dependent' for validate job compatibility. # ==================================================================== - ${{ if eq(parameters.buildSqlClient, true) }}: @@ -146,61 +245,67 @@ stages: dependsOn: build_abstractions jobs: - - template: /eng/pipelines/onebranch/jobs/build-signed-mds-package-job.yml@self + - template: /eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml@self parameters: apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient/dlls' apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient/pdbs' isOfficial: ${{ parameters.isOfficial }} publishSymbols: ${{ parameters.publishSymbols }} - signingAppRegistrationClientId: '$(AppRegistrationClientId)' - signingAppRegistrationTenantId: '$(AppRegistrationTenantId)' - signingAuthAkvName: '$(AuthAKVName)' - signingAuthSignCertName: '$(AuthSignCertName)' - signingEsrpClientId: '$(ESRPClientId)' - signingEsrpConnectedServiceName: '$(ESRPConnectedServiceName)' - symbolsAzureSubscription: 'Symbols publishing Workload Identity federation service-ADO.Net' - symbolsPublishProjectName: 'Microsoft.Data.SqlClient.SNI' - symbolsPublishServer: '$(SymbolServer)' - symbolsPublishTokenUri: '$(SymbolTokenUri)' - symbolsUploadAccount: 'SqlClientDrivers' - - abstractionsArtifactName: '$(abstractionsArtifactsName)' - abstractionsPackageVersion: '$(effectiveAbstractionsVersion)' - loggingArtifactName: '$(loggingArtifactsName)' - loggingPackageVersion: '$(effectiveLoggingVersion)' - mdsAssemblyFileVersion: '$(mdsAssemblyFileVersion)' - mdsPackageVersion: '$(effectiveSqlClientVersion)' + signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' + signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' + signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' + signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + abstractionsArtifactName: '${{ parameters.abstractionsArtifactsName }}' + abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' + loggingArtifactName: '${{ parameters.loggingArtifactsName }}' + loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' + sqlClientAssemblyFileVersion: '${{ parameters.sqlClientFileVersion }}' + sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self parameters: + signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' + signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' + signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' + signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + isOfficial: ${{ parameters.isOfficial }} packageName: Azure packageFullName: Microsoft.Data.SqlClient.Extensions.Azure - packageVersion: $(effectiveAzureVersion) + packageVersion: '${{ parameters.azurePackageVersion }}' versionProperties: >- - -p:AzurePackageVersion=$(effectiveAzureVersion) - -p:AzureAssemblyFileVersion=$(azureAssemblyFileVersion) - -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) - -p:LoggingPackageVersion=$(effectiveLoggingVersion) - assemblyFileVersion: $(azureAssemblyFileVersion) + -p:AzurePackageVersion=${{ parameters.azurePackageVersion }} + -p:AzureAssemblyFileVersion=${{ parameters.azureFileVersion }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} + assemblyFileVersion: '${{ parameters.azureFileVersion }}' publishSymbols: ${{ parameters.publishSymbols }} - isOfficial: ${{ parameters.isOfficial }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) downloadArtifacts: - - artifactName: $(abstractionsArtifactsName) + - artifactName: '${{ parameters.abstractionsArtifactsName }}' displayName: Abstractions Package - - artifactName: $(loggingArtifactsName) + - artifactName: '${{ parameters.loggingArtifactsName }}' displayName: Logging Package # ==================================================================== # Stage 4: Add-on packages (depend on core packages) - # AKV Provider builds after MDS completes. + # Akv Provider builds after SqlClient completes. # ==================================================================== - - ${{ if and(eq(parameters.buildAKVProvider, true), eq(parameters.buildSqlClient, true)) }}: + - ${{ if and(eq(parameters.buildAkvProvider, true), eq(parameters.buildSqlClient, true)) }}: - stage: build_addons displayName: "Build Add-on Packages" dependsOn: build_dependent @@ -208,42 +313,50 @@ stages: jobs: - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self parameters: + signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' + signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' + signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' + signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + isOfficial: ${{ parameters.isOfficial }} packageName: AkvProvider packageFullName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider - packageVersion: $(effectiveAkvProviderVersion) + packageVersion: '${{ parameters.akvProviderPackageVersion }}' versionProperties: >- - -p:AkvPackageVersion=$(effectiveAkvProviderVersion) - -p:AkvAssemblyFileVersion=$(akvAssemblyFileVersion) - -p:MdsPackageVersion=$(effectiveSqlClientVersion) - -p:LoggingPackageVersion=$(effectiveLoggingVersion) - -p:AbstractionsPackageVersion=$(effectiveAbstractionsVersion) - assemblyFileVersion: $(akvAssemblyFileVersion) + -p:AkvPackageVersion=${{ parameters.akvProviderPackageVersion }} + -p:AkvAssemblyFileVersion=${{ parameters.akvProviderFileVersion }} + -p:MdsPackageVersion=${{ parameters.sqlClientPackageVersion }} + -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + assemblyFileVersion: '${{ parameters.akvProviderFileVersion }}' publishSymbols: ${{ parameters.publishSymbols }} - isOfficial: ${{ parameters.isOfficial }} - esrpConnectedServiceName: $(ESRPConnectedServiceName) - esrpClientId: $(ESRPClientId) - appRegistrationClientId: $(AppRegistrationClientId) - appRegistrationTenantId: $(AppRegistrationTenantId) - authAkvName: $(AuthAKVName) - authSignCertName: $(AuthSignCertName) downloadArtifacts: - - artifactName: $(sqlClientArtifactsName) + - artifactName: '${{ parameters.sqlClientArtifactsName }}' displayName: SqlClient Package - - artifactName: $(abstractionsArtifactsName) + - artifactName: '${{ parameters.abstractionsArtifactsName }}' displayName: Abstractions Package - - artifactName: $(loggingArtifactsName) + - artifactName: '${{ parameters.loggingArtifactsName }}' displayName: Logging Package # ==================================================================== # Validation # ==================================================================== - ${{ if eq(parameters.buildSqlClient, true) }}: - - stage: mds_package_validation - displayName: "MDS Package Validation" + - stage: sqlclient_package_validation + displayName: "SqlClient Package Validation" dependsOn: build_dependent jobs: - template: /eng/pipelines/onebranch/jobs/validate-signed-package-job.yml@self parameters: - artifactName: $(sqlClientArtifactsName) + artifactName: '${{ parameters.sqlClientArtifactsName }}' + expectedFileVersion: '${{ parameters.sqlClientFileVersion }}' + expectedPackageVersion: '${{ parameters.sqlClientPackageVersion }}' isPreview: ${{ parameters.isPreview }} isOfficial: ${{ parameters.isOfficial }} diff --git a/eng/pipelines/onebranch/stages/release-stages.yml b/eng/pipelines/onebranch/stages/release-stages.yml index a9810f1251..917447a077 100644 --- a/eng/pipelines/onebranch/stages/release-stages.yml +++ b/eng/pipelines/onebranch/stages/release-stages.yml @@ -23,15 +23,6 @@ # - false → NuGet Test feed (via NuGetServiceConnectionTest). # # This template depends on stages defined by the build-stages.yml template. -# -# This template depends on the following runtime (i.e. macro expansion) variables being defined: -# -# - effectiveSqlServerVersion -# - effectiveLoggingVersion -# - effectiveAbstractionsVersion -# - effectiveSqlClientVersion -# - effectiveAzureVersion -# - effectiveAkvProviderVersion parameters: # ── General parameters ───────────────────────────────────────────────── @@ -60,6 +51,44 @@ parameters: - production - test + # Package Parameters ----------------------------------------------------- + + - name: abstractionsArtifactsName + type: string + + - name: abstractionsPackageVersion + type: string + + - name: akvProviderArtifactsName + type: string + + - name: akvProviderPackageVersion + type: string + + - name: azureArtifactsName + type: string + + - name: azurePackageVersion + type: string + + - name: loggingArtifactsName + type: string + + - name: loggingPackageVersion + type: string + + - name: sqlClientArtifactsName + type: string + + - name: sqlClientPackageVersion + type: string + + - name: sqlServerArtifactsName + type: string + + - name: sqlServerPackageVersion + type: string + # ── Release parameters ───────────────────────────────────────────────── - name: releaseSqlServerServer @@ -77,7 +106,7 @@ parameters: - name: releaseAzure type: boolean - - name: releaseAKVProvider + - name: releaseAkvProvider type: boolean stages: @@ -94,7 +123,7 @@ stages: # true → NuGet Production feed. # false → NuGet Test feed. # ==================================================================== - - ${{ if or(parameters.releaseSqlServerServer, parameters.releaseLogging, parameters.releaseAbstractions, parameters.releaseSqlClient, parameters.releaseAzure, parameters.releaseAKVProvider) }}: + - ${{ if or(parameters.releaseSqlServerServer, parameters.releaseLogging, parameters.releaseAbstractions, parameters.releaseSqlClient, parameters.releaseAzure, parameters.releaseAkvProvider) }}: - stage: release_${{ parameters.stageNameSuffix }} ${{ if eq(parameters.releaseToProduction, true) }}: displayName: Release to NuGet Production @@ -107,8 +136,8 @@ stages: - build_abstractions - ${{ if or(parameters.releaseSqlClient, parameters.releaseAzure) }}: - build_dependent - - mds_package_validation - - ${{ if parameters.releaseAKVProvider }}: + - sqlclient_package_validation + - ${{ if parameters.releaseAkvProvider }}: - build_addons variables: @@ -153,8 +182,8 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.SqlServer.Server - artifactName: $(sqlServerArtifactsName) - packagePath: Microsoft.SqlServer.Server.$(effectiveSqlServerVersion).nupkg + artifactName: '${{ parameters.sqlServerArtifactsName }}' + packagePath: 'Microsoft.SqlServer.Server.${{ parameters.sqlServerPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -163,8 +192,8 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient.Internal.Logging - artifactName: $(loggingArtifactsName) - packagePath: Microsoft.Data.SqlClient.Internal.Logging.$(effectiveLoggingVersion).nupkg + artifactName: '${{ parameters.loggingArtifactsName }}' + packagePath: 'Microsoft.Data.SqlClient.Internal.Logging.${{ parameters.loggingPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -173,8 +202,8 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient.Extensions.Abstractions - artifactName: $(abstractionsArtifactsName) - packagePath: Microsoft.Data.SqlClient.Extensions.Abstractions.$(effectiveAbstractionsVersion).nupkg + artifactName: '${{ parameters.abstractionsArtifactsName }}' + packagePath: 'Microsoft.Data.SqlClient.Extensions.Abstractions.${{ parameters.abstractionsPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -183,8 +212,8 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient - artifactName: $(sqlClientArtifactsName) - packagePath: Microsoft.Data.SqlClient.$(effectiveSqlClientVersion).nupkg + artifactName: '${{ parameters.sqlClientArtifactsName }}' + packagePath: 'Microsoft.Data.SqlClient.${{ parameters.sqlClientPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -193,18 +222,18 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient.Extensions.Azure - artifactName: $(azureArtifactsName) - packagePath: Microsoft.Data.SqlClient.Extensions.Azure.$(effectiveAzureVersion).nupkg + artifactName: '${{ parameters.azureArtifactsName }}' + packagePath: 'Microsoft.Data.SqlClient.Extensions.Azure.${{ parameters.azurePackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} - - ${{ if eq(parameters.releaseAKVProvider, true) }}: + - ${{ if eq(parameters.releaseAkvProvider, true) }}: - template: /eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml@self parameters: packageName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider - artifactName: $(akvArtifactsName) - packagePath: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.$(effectiveAkvProviderVersion).nupkg + artifactName: '${{ parameters.akvProviderArtifactsName }}' + packagePath: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.${{ parameters.akvProviderPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} diff --git a/eng/pipelines/onebranch/steps/build-csproj-step.yml b/eng/pipelines/onebranch/steps/build-csproj-step.yml index d1cea7c190..a4a38b9fc3 100644 --- a/eng/pipelines/onebranch/steps/build-csproj-step.yml +++ b/eng/pipelines/onebranch/steps/build-csproj-step.yml @@ -15,13 +15,6 @@ parameters: - name: buildTarget type: string - # Build Configuration. - - name: buildConfiguration - type: string - values: - - Debug - - Release - # Additional MSBuild arguments for version properties, e.g. # -p:LoggingPackageVersion=1.0.0 -p:LoggingAssemblyFileVersion=1.0.123 - name: versionProperties @@ -36,15 +29,15 @@ steps: name: keyFile - task: MSBuild@1 - displayName: Build.proj - ${{ parameters.buildTarget }} + displayName: 'Build.proj - ${{ parameters.buildTarget }}' inputs: - solution: $(REPO_ROOT)/build.proj - configuration: ${{ parameters.buildConfiguration }} + solution: '$(REPO_ROOT)/build.proj' + configuration: 'Release' msbuildArguments: >- - -t:${{ parameters.buildTarget }} - -p:ReferenceType=Package - -p:SigningKeyPath=$(keyFile.secureFilePath) - ${{ parameters.versionProperties }} + -t:${{ parameters.buildTarget }} + -p:ReferenceType=Package + -p:SigningKeyPath=$(keyFile.secureFilePath) + ${{ parameters.versionProperties }} - script: tree /a /f $(BUILD_OUTPUT) - displayName: List Build Output Tree + displayName: Output Build Output Tree diff --git a/eng/pipelines/onebranch/steps/build-mds-step.yml b/eng/pipelines/onebranch/steps/build-sqlclient-step.yml similarity index 89% rename from eng/pipelines/onebranch/steps/build-mds-step.yml rename to eng/pipelines/onebranch/steps/build-sqlclient-step.yml index 4d5058f261..390d17fc34 100644 --- a/eng/pipelines/onebranch/steps/build-mds-step.yml +++ b/eng/pipelines/onebranch/steps/build-sqlclient-step.yml @@ -13,7 +13,7 @@ parameters: - name: loggingPackageVersion type: string - - name: mdsPackageVersion + - name: sqlClientPackageVersion type: string steps: @@ -24,7 +24,7 @@ steps: name: keyFile - task: MSBuild@1 - displayName: 'Build2.proj - BuildMds' + displayName: 'Build2.proj - Build SqlClient' inputs: solution: '$(REPO_ROOT)/build2.proj' configuration: 'Release' @@ -33,7 +33,7 @@ steps: -p:BuildNumber="$(Build.BuildNumber)" -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" - -p:PackageVersionMds="${{ parameters.mdsPackageVersion }}" + -p:PackageVersionMds="${{ parameters.sqlClientPackageVersion }}" -p:ReferenceType=Package -p:SigningKeyPath="$(keyFile.secureFilePath)" diff --git a/eng/pipelines/onebranch/steps/copy-apiscan-files-mds-step.yml b/eng/pipelines/onebranch/steps/copy-apiscan-files-sqlclient-step.yml similarity index 100% rename from eng/pipelines/onebranch/steps/copy-apiscan-files-mds-step.yml rename to eng/pipelines/onebranch/steps/copy-apiscan-files-sqlclient-step.yml diff --git a/eng/pipelines/onebranch/steps/pack-csproj-step.yml b/eng/pipelines/onebranch/steps/pack-csproj-step.yml index db9b9a8237..edbec72c00 100644 --- a/eng/pipelines/onebranch/steps/pack-csproj-step.yml +++ b/eng/pipelines/onebranch/steps/pack-csproj-step.yml @@ -14,13 +14,6 @@ parameters: - name: packTarget type: string - # Build Configuration. - - name: buildConfiguration - type: string - values: - - Debug - - Release - # Additional MSBuild arguments for version properties. - name: versionProperties type: string @@ -28,15 +21,15 @@ parameters: steps: - task: MSBuild@1 - displayName: Build.proj - ${{ parameters.packTarget }} + displayName: 'Build.proj - ${{ parameters.packTarget }}' inputs: - solution: $(REPO_ROOT)/build.proj - configuration: ${{ parameters.buildConfiguration }} + solution: '$(REPO_ROOT)/build.proj' + configuration: 'Release' msbuildArguments: >- - -t:${{ parameters.packTarget }} - -p:ReferenceType=Package - -p:PackagesDir=$(PACK_OUTPUT)/ - ${{ parameters.versionProperties }} + -t:${{ parameters.packTarget }} + -p:ReferenceType=Package + -p:PackagesDir=$(PACK_OUTPUT)/ + ${{ parameters.versionProperties }} - script: tree /a /f $(PACK_OUTPUT) displayName: List Pack Output Tree After Pack diff --git a/eng/pipelines/onebranch/steps/pack-mds-step.yml b/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml similarity index 89% rename from eng/pipelines/onebranch/steps/pack-mds-step.yml rename to eng/pipelines/onebranch/steps/pack-sqlclient-step.yml index f1277280ae..933c37f6a6 100644 --- a/eng/pipelines/onebranch/steps/pack-mds-step.yml +++ b/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml @@ -12,12 +12,12 @@ parameters: - name: loggingPackageVersion type: string - - name: mdsPackageVersion + - name: sqlClientPackageVersion type: string steps: - task: MSBuild@1 - displayName: 'Build2.proj - PackMds' + displayName: 'Build2.proj - Pack SqlClient' inputs: solution: '$(REPO_ROOT)/build2.proj' configuration: 'Release' @@ -27,7 +27,7 @@ steps: -p:PackBuild=false -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" - -p:PackageVersionMds="${{ parameters.mdsPackageVersion }}" + -p:PackageVersionMds="${{ parameters.sqlClientPackageVersion }}" -p:ReferenceType=Package - script: tree /a /f $(BUILD_OUTPUT) diff --git a/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml index c112493603..8810bed838 100644 --- a/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml +++ b/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml @@ -5,7 +5,7 @@ ################################################################################# # This template defines a step to run Roslyn Analyzers against builds driven by -# build.proj. It is used for both the full Microsoft.Data.SqlClient (MDS) build +# build.proj. It is used for both the full Microsoft.Data.SqlClient (SqlClient) build # and for individual extension packages; callers control the analyzed targets # via the msBuildArguments parameter. # It uses the RoslynAnalyzers@3 task from the Secure Development Team's SDL @@ -17,30 +17,24 @@ # _before_ any build steps that perform versioning or signing. parameters: - # Source Root. - - name: sourceRoot + # Target to build from build.proj. This is separated from the rest of the msBuildArguments to + # provide a rich display name. + - name: buildTarget type: string - default: $(REPO_ROOT) - # MSBuild arguments appended after the project file path. The default targets - # BuildAllConfigurations for the full MDS build; override this when analyzing - # individual extension packages. + # Additional build arguments to append to the end of the MSBuild command. - name: msBuildArguments type: string - default: >- - -t:BuildAllConfigurations - -p:configuration=Release - -p:GenerateNuget=false - -p:BuildTools=false steps: - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 - displayName: Roslyn Analyzers + displayName: 'Roslyn Analyzers: build.proj ${{ parameters.buildTarget }}' inputs: msBuildVersion: 17.0 msBuildArchitecture: x64 setupCommandLinePicker: vs2022 msBuildCommandLine: >- msbuild - ${{parameters.sourceRoot}}\build.proj - ${{parameters.msBuildArguments}} + $(REPO_ROOT)\build.proj + -t:${{ parameters.buildTarget }} + ${{ parameters.msBuildArguments }} diff --git a/eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml similarity index 84% rename from eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml rename to eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml index 5fe8009078..813df97326 100644 --- a/eng/pipelines/onebranch/steps/roslyn-analyzers-mds-step.yml +++ b/eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml @@ -4,7 +4,7 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# This template defines a step to run Roslyn Analyzers on the MDS build. It uses the +# This template defines a step to run Roslyn Analyzers on the SqlClient build. It uses the # RoslynAnalyzers@3 task from the Secure Development Team's SDL extension: # # https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/roslyn-analyzers-build-task @@ -19,12 +19,12 @@ parameters: - name: loggingPackageVersion type: string - - name: mdsPackageVersion + - name: sqlClientPackageVersion type: string steps: - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 - displayName: 'Roslyn Analyzers: Build2.proj BuildMds' + displayName: 'Roslyn Analyzers: build2.proj Build SqlClient' inputs: msBuildArchitecture: x64 msBuildCommandLine: >- @@ -34,7 +34,7 @@ steps: -p:Configuration=Release -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" - -p:PackageVersionMds="${{ parameters.mdsPackageVersion }}" + -p:PackageVersionMds="${{ parameters.sqlClientPackageVersion }}" -p:ReferenceType=Package msBuildVersion: 17.0 setupCommandLinePicker: vs2022 diff --git a/eng/pipelines/onebranch/variables/common-variables.yml b/eng/pipelines/onebranch/variables/common-variables.yml index dbbddefc51..a2a48ba184 100644 --- a/eng/pipelines/onebranch/variables/common-variables.yml +++ b/eng/pipelines/onebranch/variables/common-variables.yml @@ -7,27 +7,10 @@ # Common variables shared across all OneBranch Official pipelines. variables: - - group: Release Variables - - # Symbols publishing credentials - - group: Symbols publishing - # SymbolsAzureSubscription - # SymbolsPublishProjectName - # SymbolsPublishServer - # SymbolsPublishTokenUri - # SymbolsUploadAccount - - - group: ESRP Federated Creds (AME) - # ESRPConnectedServiceName - # ESRPClientId - # AppRegistrationClientId - # AppRegistrationTenantId - # AuthAKVName - # AuthSignCertName - - name: CommitHead value: '' # the value will be extracted from the repo's head + ########################################################################## # "Well-Known" Variables that are ok to use directly, anywhere in the pipeline. # The root of our repo. @@ -35,147 +18,18 @@ variables: value: $(Build.SourcesDirectory) # This is where our C# projects place their build outputs (see Directory.Build.props - # ). We exclude the reference type here since Official pipelines always use + # ). We exclude the reference type here since Official pipelines always use # Package mode. - name: BUILD_OUTPUT value: $(REPO_ROOT)/artifacts - # This is where our C# projects place their NuGet package outputs. This is intentionally - # separate from packages/ (where downloaded pipeline artifacts go) so that ESRP signing and + # This is where downloaded artifacts should be placed. This will enable other build jobs to + # do local NuGet installations with packages built by earlier stages. + - name: PACK_INPUT + value: $(REPO_ROOT)/packages + + # This is where our C# projects place their NuGet package outputs. This is intentionally + # separated from packages/ (where downloaded pipeline artifacts go) so that ESRP signing and # OneBranch artifact publishing only operate on packages built by the current job. - name: PACK_OUTPUT value: $(REPO_ROOT)/output - - # C# assembly versions must be in the format: Major.Minor.Build.Revision, but - # $(Build.BuildNumber) has the format XXX.YY. Additionally, each version part - # must be a positive 16-bit integer less than 65535. Simply concatenating - # both parts of $(Build.BuildNumber) could produce values larger than 65534, - # so we must omit the second part entirely. Unfortunately, this may result - # in multiple subsequent pipline builds using the same C# assembly versions. - # The package versions will not be affected and will show the complete - # $(Build.BuildNumber) values. - - name: assemblyBuildNumber - value: $[ split(variables['Build.BuildNumber'], '.')[0] ] - - # ---------------------------------------------------------------------------- - # Abstractions Package Versions - # - # These are version values that will be used by the official build. They - # should be updated after each release to reflect the next release's versions. - - # The NuGet package version for GA releases (non-preview). - - name: abstractionsPackageVersion - value: '1.0.0' - - # The NuGet package version for preview releases. - - name: abstractionsPackagePreviewVersion - value: 1.0.0-preview1.$(Build.BuildNumber) - - # The AssemblyFileVersion for all assemblies in the Abstractions package. - - name: abstractionsAssemblyFileVersion - value: 1.0.0.$(assemblyBuildNumber) - - # ---------------------------------------------------------------------------- - # MDS Package Versions - # - # These are version values that will be used by the official build. They - # should be updated after each release to reflect the next release's versions. - - # The NuGet package version for GA releases (non-preview). - - name: mdsPackageVersion - value: '7.0.0' - - # The NuGet package version for preview releases. - - name: mdsPackagePreviewVersion - value: 7.0.0-preview4.$(Build.BuildNumber) - - # The AssemblyFileVersion for all assemblies in the MDS package. - - name: mdsAssemblyFileVersion - value: 7.0.0.$(assemblyBuildNumber) - - # The path to the NuGet packaging specification file used to generate the MDS NuGet package. - - name: nuspecPath - value: '$(REPO_ROOT)/tools/specs/Microsoft.Data.SqlClient.nuspec' - - # ---------------------------------------------------------------------------- - # Logging Package Versions - # - # These are version values that will be used by the official build. They - # should be updated after each release to reflect the next release's versions. - - # The NuGet package version for GA releases (non-preview). - - name: loggingPackageVersion - value: '1.0.0' - - # The NuGet package version for preview releases. - - name: loggingPackagePreviewVersion - value: 1.0.0-preview1.$(Build.BuildNumber) - - # The AssemblyFileVersion for all assemblies in the Logging package. - - name: loggingAssemblyFileVersion - value: 1.0.0.$(assemblyBuildNumber) - - # ---------------------------------------------------------------------------- - # Azure Package Versions - # - # These are version values that will be used by the official build. They - # should be updated after each release to reflect the next release's versions. - - # The NuGet package version for GA releases (non-preview). - - name: azurePackageVersion - value: '1.0.0' - - # The NuGet package version for preview releases. - - name: azurePackagePreviewVersion - value: 1.0.0-preview1.$(Build.BuildNumber) - - # The AssemblyFileVersion for all assemblies in the Azure package. - - name: azureAssemblyFileVersion - value: 1.0.0.$(assemblyBuildNumber) - - # ---------------------------------------------------------------------------- - # SqlServer.Server Package Versions - # - # These are version values that will be used by the official build. They - # should be updated after each release to reflect the next release's versions. - - # The NuGet package version for GA releases (non-preview). - - name: sqlServerPackageVersion - value: '1.0.0' - - # The NuGet package version for preview releases. - - name: sqlServerPackagePreviewVersion - value: 1.0.0-preview1.$(Build.BuildNumber) - - # The AssemblyFileVersion for all assemblies in the SqlServer.Server package. - - name: sqlServerAssemblyFileVersion - value: 1.0.0.$(assemblyBuildNumber) - - # ---------------------------------------------------------------------------- - # AKV Provider Package Versions - # - # These are version values that will be used by the official build. They - # should be updated after each release to reflect the next release's versions. - - # The NuGet package version for GA releases (non-preview). - - name: akvPackageVersion - value: '7.0.0' - - # The NuGet package version for preview releases. - - name: akvPackagePreviewVersion - value: 7.0.0-preview1.$(Build.BuildNumber) - - # The AssemblyFileVersion for all assemblies in the AKV Provider package. - - name: akvAssemblyFileVersion - value: 7.0.0.$(assemblyBuildNumber) - - # ---------------------------------------------------------------------------- - # MDS Symbols Publishing - # - # Alias variables for MDS publish-symbols-step.yml, which defaults to - # $(SymbolServer) and $(SymbolTokenUri). These map to the values provided - # by the "Symbols publishing" library group. - - name: SymbolServer - value: $(SymbolsPublishServer) - - name: SymbolTokenUri - value: $(SymbolsPublishTokenUri) diff --git a/eng/pipelines/onebranch/variables/onebranch-variables.yml b/eng/pipelines/onebranch/variables/onebranch-variables.yml index 004786e25f..4009c60b3d 100644 --- a/eng/pipelines/onebranch/variables/onebranch-variables.yml +++ b/eng/pipelines/onebranch/variables/onebranch-variables.yml @@ -4,52 +4,41 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# This file is only included in MDS OneBranch Official pipelines. +# This file is only included in SqlClient OneBranch Official pipelines. variables: - - template: /eng/pipelines/onebranch/variables/common-variables.yml@self + # Libraries ============================================================== - # onebranch template variables - - name: ob_sdl_binskim_break - value: true # https://aka.ms/obpipelines/sdl - - name: Packaging.EnableSBOMSigning - value: true - - name: WindowsContainerImage - value: "onebranch.azurecr.io/windows/ltsc2022/vse2022:latest" # Docker image which is used to build the project https://aka.ms/obpipelines/containers - - # OneBranch automatically publishes pipeline artifacts from all jobs. Use of the - # PublishPipelineArtifacts task is prohibited. All content in the ob_outputDirectory is - # published. The artifacts are named according to this pattern: - # - # drop__ + # 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). # - # Downstream stages and jobs may download these artifacts via the DownloadPipelineArtifact task, - # but they must know the artifact name. + # SigningAppRegistrationClientId + # SigningAppRegistrationTenantId + # SigningAuthAkvName + # SigningAuthSignCertName + # SigningEsrpClientId + # SigningEsrpConnectedServiceName + - group: 'esrp-variables-v2' + + # These variables are used for running symbol publishing tasks. All names start with "Symbols" to + # make it clear that these variables are used for symbols (as opposed to other msc tasks). # - # Here we define the artifact names that our OneBranch pipelines produce to avoid hardcoding them - # throughout the pipeline files. If any of the stage or job names change, we only need to update - # the artifact names here. - - # The Abstractions package artifacts. - - name: abstractionsArtifactsName - value: drop_build_abstractions_build_package_Abstractions + # SymbolsAzureSubscription + # SymbolsPublishProjectName + # SymbolsPublishServer + # SymbolsPublishTokenUri + # SymbolsUploadAccount + - group: 'symbols-variables-v2' - # The AKV Provider package artifacts. - - name: akvArtifactsName - value: drop_build_addons_build_package_AkvProvider + # OneBranch Template Variables ########################################### - # The Azure package artifacts. - - name: azureArtifactsName - value: drop_build_dependent_build_package_Azure - - # The Logging package artifacts. - - name: loggingArtifactsName - value: drop_build_independent_build_package_Logging + # https://aka.ms/obpipelines/sdl + - name: ob_sdl_binskim_break + value: true - # The SqlServer package artifacts. - - name: sqlServerArtifactsName - value: drop_build_independent_build_package_SqlServer + - name: Packaging.EnableSBOMSigning + value: true - # The SqlClient package artifacts. - - name: sqlClientArtifactsName - value: drop_build_dependent_build_package_Mds + # 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/eng/pipelines/onebranch/variables/package-variables.yml b/eng/pipelines/onebranch/variables/package-variables.yml new file mode 100644 index 0000000000..92f9863021 --- /dev/null +++ b/eng/pipelines/onebranch/variables/package-variables.yml @@ -0,0 +1,137 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This file contains variables that relate to the various packages that are produced via the +# OneBranch official/non-official pipelines. They are grouped by the packages they represent. +# +# The variables are defined as the following main types: +# - *ArtifactsName +# What the artifacts drop for the various stages of the pipeline will be named. +# +# OneBranch automatically publishes pipeline artifacts from all jobs. Use of the +# PublishPipelineArtifacts task is prohibited. All content in the ob_outputDirectory is +# automatically published. The artifacts are named according to this pattern: +# +# drop__ +# +# Downstream stages and jobs may download these artifacts via the DownloadPipelineArtifact task, +# but they must know the artifact name. +# +# - *FileVersion +# Version to use for the FileVersion field in the assembly. +# +# - *PackageVersion +# NuGet package versions releases. This is switched based on the isPreview parameter. + +# @TODO: Although defining the versions in source is a major improvement over depending on versions +# coming from variable libraries, this is not the final form. Ideally, we want a single point for +# versions to be defined and are moving towards doing so via the [PackageName]Versions.props +# files. In the meantime, when doing a version bump (after a release), versions will need to be +# updated here and in their corresponding *Versions.props file. + +parameters: + - name: isPreview + type: boolean + +variables: + # C# assembly versions must be in the format: Major.Minor.Build.Revision, but + # $(Build.BuildNumber) has the format XXX.YY. Additionally, each version part must be a positive + # 16-bit integer less than 65535. Simply concatenating both parts of $(Build.BuildNumber) could + # produce values larger than 65534, so we must omit the second part entirely. Unfortunately, this + # may result in multiple subsequent pipline builds using the same C# assembly versions. The + # package versions will not be affected and will show the complete $(Build.BuildNumber) values. + - name: _assemblyBuildNumber + value: $[ split(variables['Build.BuildNumber'], '.')[0] ] + + # ------------------------------------------------------------------------ + # Abstractions Package + # Microsoft.Data.SqlClient.Extensions.Abstractions + - name: abstractionsArtifactsName + value: 'drop_build_abstractions_build_package_Abstractions' + + - name: abstractionsFileVersion + value: '1.0.0.$(_assemblyBuildNumber)' + + - name: abstractionsPackageVersion + ${{ if parameters.isPreview }}: + value: '1.0.0-preview1.$(Build.BuildNumber)' + ${{ else }}: + value: '1.0.0' + + # ------------------------------------------------------------------------ + # AKV Provider Package + # Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVault + - name: akvProviderArtifactsName + value: 'drop_build_addons_build_package_AkvProvider' + + - name: akvProviderFileVersion + value: '7.0.0.$(_assemblyBuildNumber)' + + - name: akvProviderPackageVersion + ${{ if parameters.isPreview }}: + value: '7.0.0-preview1.$(Build.BuildNumber)' + ${{ else }}: + value: '7.0.0' + + # ------------------------------------------------------------------------ + # Azure Package + # Microsoft.Data.SqlClient.Extensions.Azure + - name: azureArtifactsName + value: 'drop_build_dependent_build_package_Azure' + + - name: azureFileVersion + value: '1.0.0.$(_assemblyBuildNumber)' + + - name: azurePackageVersion + ${{ if parameters.isPreview }}: + value: '1.0.0-preview1.$(Build.BuildNumber)' + ${{ else }}: + value: '1.0.0' + + # ------------------------------------------------------------------------ + # Logging Package + # Microsoft.Data.SqlClient.Internal.Logging + - name: loggingArtifactsName + value: 'drop_build_independent_build_package_Logging' + + - name: loggingFileVersion + value: '1.0.0.$(_assemblyBuildNumber)' + + - name: loggingPackageVersion + ${{ if parameters.isPreview }}: + value: '1.0.0-preview1.$(Build.BuildNumber)' + ${{ else }}: + value: '1.0.0' + + # ------------------------------------------------------------------------ + # SqlClient Package + # Microsoft.Data.SqlClient + - name: sqlClientArtifactsName + value: 'drop_build_dependent_build_package_SqlClient' + + - name: sqlClientFileVersion + value: '7.0.0.$(_assemblyBuildNumber)' + + - name: sqlClientPackageVersion + ${{ if parameters.isPreview }}: + value: '7.0.0-preview4.$(Build.BuildNumber)' + ${{ else }}: + value: '7.0.0' + + # ------------------------------------------------------------------------ + # SqlServer Package + # Microsoft.SqlServer.Server + - name: sqlServerArtifactsName + value: 'drop_build_independent_build_package_SqlServer' + + - name: sqlServerFileVersion + value: '1.0.0.$(_assemblyBuildNumber)' + + - name: sqlServerPackageVersion + ${{ if parameters.isPreview }}: + value: '1.0.0-preview1.$(Build.BuildNumber)' + ${{ else }}: + value: '1.0.0' diff --git a/eng/pipelines/onebranch/variables/sqlclient-validation-variables.yml b/eng/pipelines/onebranch/variables/sqlclient-validation-variables.yml deleted file mode 100644 index 75ad369dbc..0000000000 --- a/eng/pipelines/onebranch/variables/sqlclient-validation-variables.yml +++ /dev/null @@ -1,38 +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. # -################################################################################# - -# This file is only included in MDS OneBranch Official pipelines. - -variables: - - group: Release Variables - - template: /eng/pipelines/onebranch/variables/common-variables.yml@self - - - name: TempFolderName # extract the nuget package here - value: temp - - name: extractedNugetRootPath - value: $(Build.SourcesDirectory)\$(TempFolderName)\Microsoft.Data.SqlClient - - name: extractedNugetPath - value: $(extractedNugetRootPath).$(mdsPackageVersion) - - name: expectedFolderNames - value: lib,ref,runtimes - - name: expectedDotnetVersions - value: netstandard2.0,net462,net8.0,net9.0 - - name: Database - value: Northwind - - name: platform - value: AnyCPU - - name: TargetNetFxVersion - value: net481 - - name: TargetNetCoreVersion - value: net9.0 - - name: SQLTarget - value: localhost - - name: encrypt - value: false - - name: SQL_NP_CONN_STRING - value: Data Source=np:$(SQLTarget);Initial Catalog=$(Database);Integrated Security=true;Encrypt=$(ENCRYPT);TrustServerCertificate=true; - - name: SQL_TCP_CONN_STRING - value: Data Source=tcp:$(SQLTarget);Initial Catalog=$(Database);Integrated Security=true;Encrypt=$(ENCRYPT);TrustServerCertificate=true; diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index 0452cba60c..46eb7fc748 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -126,95 +126,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + From d7fb5cb45ad46a9a7131fdbf93c88ccea76b9a3a Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 8 Apr 2026 02:13:06 +0100 Subject: [PATCH 030/298] Tests | Add collation transcoding tests (#4051) * Replace CollatedDataReaderTest This previously only tested Kazakh_90_CI_AI and Georgian_Modern_Sort_CI_AS. The replacement tests every collation. It also performs a more comprehensive check that the string/byte[] roundtrips with the varbinary/varchar from the database instance. Finally, it no longer requires permission to drop and create databases: we can just use the COLLATE statement. * Add collated output parameter test --- .../SQL/DataReaderTest/DataReaderTest.cs | 111 ++++++++++++------ .../SQL/ParameterTest/OutputParameterTests.cs | 47 ++++++++ 2 files changed, 122 insertions(+), 36 deletions(-) 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 040119616e..5cf8b1e4b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -125,49 +125,88 @@ public static void CheckSparseColumnBit() } } - // Synapse: Statement 'Drop Database' is not supported in this version of SQL Server. - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] - [InlineData("KAZAKH_90_CI_AI")] - [InlineData("Georgian_Modern_Sort_CI_AS")] - public static void CollatedDataReaderTest(string collation) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static void CollatedDataReaderTest() { - string dbName = DataTestUtility.GetShortName("CollationTest", false); - - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) + const string SampleText = "Text with an accented é varies by encoding"; + const string CollatedStringCommandText = $@"declare c cursor for + select name + from fn_helpcollations() +declare @collation nvarchar(max) + +open c +fetch next from c into @collation + +while @@FETCH_STATUS = 0 +begin + declare @sql nvarchar(max) = N'select @collation as [Collation], + convert(int, COLLATIONPROPERTY(@collation, ''LCID'')) & 0xFFFF as [LanguageId], + convert(int, COLLATIONPROPERTY(@collation, ''LCID'')) as [LCID], + convert(int, COLLATIONPROPERTY(@collation, ''CodePage'')) as [CodePage], + @Text collate ' + @collation + ' as [Text], + convert(varbinary(max), @Text collate ' + @collation + ')' + + begin try + exec sp_executesql @sql, N'@collation nvarchar(max), @Text varchar(max)', @Text='{SampleText}', @collation=@collation + end try + begin catch + -- Error 459: Collation '%.*ls' is supported on Unicode data types only and cannot be applied to char, varchar or text data types. + if error_number() != 459 + begin + throw + end + end catch + + fetch next from c into @collation +end + +close c +deallocate c"; + + using SqlConnection conn = new(DataTestUtility.TCPConnectionString); + using SqlCommand collatedStringCommand = new(CollatedStringCommandText, conn); + + conn.Open(); + using SqlDataReader reader = collatedStringCommand.ExecuteReader(); + + // We receive one result set per collation, with identical columns: + // 1. Collation name + // 2. Language ID (the LCID without any flags) + // 3. LCID (with flags) + // 4. ID of the code page used to decode the byte array + // 5. The collated string itself + // 6. The collated string, converted to a byte array within SQL Server + do { - InitialCatalog = dbName, - Pooling = false - }; + reader.Read(); - using SqlConnection con = new(DataTestUtility.TCPConnectionString); - using SqlCommand cmd = con.CreateCommand(); - try - { - con.Open(); + string collationName = reader.GetString(0); + int languageId = reader.GetInt32(1); + int lcid = reader.GetInt32(2); + int codePageId = reader.GetInt32(3); + string collatedString = reader.GetString(4); + byte[] collatedStringBytes = reader.GetSqlBinary(5).Value; - // Create collated database - cmd.CommandText = $"CREATE DATABASE [{dbName}] COLLATE {collation}"; - cmd.ExecuteNonQuery(); + // The code page's encoding must exist. We must then be able to round-trip the string + // to and from a byte array. + Encoding codePageEncoding = Encoding.GetEncoding(codePageId); - //Create connection without pooling in order to delete database later. - using (SqlConnection dbCon = new(builder.ConnectionString)) - using (SqlCommand dbCmd = dbCon.CreateCommand()) - { - string data = Guid.NewGuid().ToString(); + Assert.True(codePageEncoding is not null, + $@"Collation ""{collationName}"", LCID {lcid}, code page {codePageId} is not identifiable as a client-side encoding."); - dbCon.Open(); - dbCmd.CommandText = $"SELECT '{data}'"; - using SqlDataReader reader = dbCmd.ExecuteReader(); - reader.Read(); - Assert.Equal(data, reader.GetString(0)); - } - } - finally - { - // Let connection close safely before dropping database for slow servers. - Thread.Sleep(500); - DataTestUtility.DropDatabase(con, dbName); + string clientSideDecodedString = codePageEncoding.GetString(collatedStringBytes); + byte[] clientSideStringBytes = codePageEncoding.GetBytes(collatedString); + + Assert.True(collatedString == clientSideDecodedString, + $@"Collation ""{collationName}"", LCID {lcid}, code page {codePageId}: server-supplied string does not match client-side decoded bytes."); + + Assert.True(collatedStringBytes.AsSpan().SequenceEqual(clientSideStringBytes), + $@"Collation ""{collationName}"", LCID {lcid}, code page {codePageId}: server-supplied byte array does not match client-side encoded bytes."); + + // The character é does not exist in the Cyrillic character set, so do not compare the + // collated string with the original input text. } + while (reader.NextResult()); } private static bool IsColumnBitSet(SqlConnection con, string selectQuery, int indexOfColumnSet) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs index 98e51b812d..537313792e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Data; +using System.Text; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -12,6 +13,16 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// public class OutputParameterTests { + /// + /// Test data indicating which collations do not encode the character é to 0xE9. + /// + public static TheoryData OutputParameterCodePages => + // Code page 936 and 65001/UTF8 do not encode "é" to 0xE9. CP936 encodes it to [0xA8, 0xA6], UTF8 encodes it to [0xC3, 0xA9] + // Chinese_PRC_CI_AI and Albanian_100_CI_AI_SC_UTF8 are the alphabetically first collations which use these two code pages. + DataTestUtility.IsUTF8Supported() + ? new() { { "Chinese_PRC_CI_AI", 936 }, { "Albanian_100_CI_AI_SC_UTF8", 65001 } } + : new() { { "Chinese_PRC_CI_AI", 936 } }; + /// /// Tests that setting an output SqlParameter to an invalid value (e.g. a string in a decimal param) /// doesn't throw, since the value is cleared before execution starts. @@ -48,5 +59,41 @@ public void InvalidValueInOutputParameter_ShouldSucceed() // Validate - the output value should be set correctly by SQL Server Assert.Equal(new decimal(1.23), (decimal)decimalParam.Value); } + + /// + /// Tests that text with sample collations roundtrips. + /// + /// Name of a SQL Server collation which encodes text in the given code page. + /// ID of the codepage which should be used by SQL Server and the driver to encode and decode text. + [Theory] + [MemberData(nameof(OutputParameterCodePages))] + public void CollatedStringInOutputParameter_DecodesSuccessfully(string collation, int codePage) + { + const string SampleText = "Text with an accented é varies by encoding"; + + using SqlConnection sqlConnection = new(DataTestUtility.TCPConnectionString); + using SqlCommand roundtripCollationCommand = new($"SELECT @Output_Varchar = convert(varchar(max), '{SampleText}') COLLATE {collation}, " + + $"@Output_Varbinary = convert(varbinary(max), convert(varchar(max), '{SampleText}') COLLATE {collation})", sqlConnection); + SqlParameter outputVarcharParameter = new("@Output_Varchar", SqlDbType.VarChar, 8000) + { Direction = ParameterDirection.Output }; + SqlParameter outputVarbinaryParameter = new("@Output_Varbinary", SqlDbType.VarBinary, 8000) + { Direction = ParameterDirection.Output }; + Encoding codePageEncoding = Encoding.GetEncoding(codePage); + + roundtripCollationCommand.Parameters.Add(outputVarcharParameter); + roundtripCollationCommand.Parameters.Add(outputVarbinaryParameter); + + sqlConnection.Open(); + roundtripCollationCommand.ExecuteNonQuery(); + + string clientSideDecodedString = codePageEncoding.GetString((byte[])outputVarbinaryParameter.Value); + byte[] clientSideStringBytes = codePageEncoding.GetBytes(outputVarcharParameter.Value.ToString()); + + // Verify that the varchar value has been decoded correctly and matches the sample text, + // then verify that the varbinary value roundtrips properly. + Assert.Equal(SampleText, outputVarcharParameter.Value.ToString()); + Assert.Equal(outputVarcharParameter.Value.ToString(), clientSideDecodedString); + Assert.Equal((byte[])outputVarbinaryParameter.Value, clientSideStringBytes); + } } } From 5d695b664b873b8cddef8ff8c6873c136520f438 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Wed, 8 Apr 2026 11:56:49 +1000 Subject: [PATCH 031/298] Fix typo in "Received instance info from UDP Client." (#4103) --- .../Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs index 1582a80bd4..43b411fb5d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs @@ -421,7 +421,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(SsrpClient), EventType.INFO, "Received instnace info from UDP Client."); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "Received instance 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) From 52c7149d4aa8f0c6d7d021a19261c1f3d5c0a88f Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Wed, 8 Apr 2026 13:15:03 +1000 Subject: [PATCH 032/298] Avoid tostring in SqlConnectionEncryptOptionConverter if we know value is a string (#4012) --- .../Data/SqlClient/SqlConnectionEncryptOptionConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOptionConverter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOptionConverter.cs index 0e22e6ce2e..3a7289e136 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOptionConverter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOptionConverter.cs @@ -36,9 +36,9 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type sourceTyp // Overrides the ConvertFrom method of TypeConverter. public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - if (value is string) + if (value is string s) { - return SqlConnectionEncryptOption.Parse(value.ToString()); + return SqlConnectionEncryptOption.Parse(s); } throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionEncryptOption), null); } From 4c294c67e4699e0f292b2a086c7879612cf2ede2 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:04:10 -0700 Subject: [PATCH 033/298] Fix duplicated docs, and missing snippet ref (#4086) --- .../Microsoft.Data.SqlClient/SqlCommand.xml | 101 +----------------- 1 file changed, 2 insertions(+), 99 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml index 7509f756b3..34c738b41e 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml @@ -2682,7 +2682,7 @@ If you use or , 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)] +[!code-csharp[SqlCommand_ExecuteReader](~/../sqlclient/doc/samples/SqlCommand_ExecuteReader.cs#1)] ]]> @@ -2750,45 +2750,9 @@ If you use or , 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)] +[!code-csharp[SqlCommand_ExecuteReader2#1](~/../sqlclient/doc/samples/SqlCommand_ExecuteReader2.cs#1)] ]]> - - - 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 . - - - - 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)) - { - SqlCommand command = new SqlCommand(queryString, connection); - connection.Open(); - SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection); - while (reader.Read()) - { - Console.WriteLine(String.Format("{0}", reader[0])); - } - } - } - } - - @@ -3526,67 +3490,6 @@ The following example demonstrates how to create a - - - The following example demonstrates how to create a and add parameters to the . - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - string demo = @"<StoreSurvey xmlns=""http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey""><AnnualSales>1500000</AnnualSales><AnnualRevenue>150000</AnnualRevenue><BankName>Primary International</BankName><BusinessType>OS</BusinessType><YearOpened>1974</YearOpened><Specialty>Road</Specialty><SquareFeet<38000</SquareFeet><Brands>3</Brands><Internet>DSL</Internet><NumberEmployees>40</NumberEmployees></StoreSurvey>"; - Int32 id = 3; - UpdateDemographics(id, demo, connectionString); - Console.ReadLine(); - } - private static void UpdateDemographics(Int32 customerID, - string demoXml, string connectionString) - { - // Update the demographics for a store, which is stored - // in an xml column. - string commandText = "UPDATE Sales.Store SET Demographics = @demographics " - + "WHERE CustomerID = @ID;"; - - using (SqlConnection connection = new SqlConnection(connectionString)) - { - SqlCommand command = new SqlCommand(commandText, connection); - command.Parameters.Add("@ID", SqlDbType.Int); - command.Parameters["@ID"].Value = customerID; - - // Use AddWithValue to assign Demographics. - // SQL Server will implicitly convert strings into XML. - command.Parameters.AddWithValue("@demographics", demoXml); - - try - { - connection.Open(); - Int32 rowsAffected = command.ExecuteNonQuery(); - Console.WriteLine("RowsAffected: {0}", rowsAffected); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - } - } - - static private string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local);Initial Catalog=AdventureWorks;" - + "Integrated Security=SSPI"; - } - } - - From 12005ace228f8d0f31c91a29b627eeb079118ec2 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:56:52 -0300 Subject: [PATCH 034/298] Gate test steps on setup success in CI pipeline (#4141) --- .../templates/jobs/ci-run-tests-job.yml | 41 ++++++++++++------- .../templates/steps/run-all-tests-step.yml | 24 +++++------ 2 files changed, 39 insertions(+), 26 deletions(-) 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 76a3bf3c75..8666cd0dbb 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -326,6 +326,33 @@ jobs: ${{ if parameters.configProperties.FileStreamDirectory }}: fileStreamDirectory: ${{ parameters.configProperties.FileStreamDirectory }} + # 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. + - ${{ if and(eq(parameters.enableX86Test, true), eq(parameters.operatingSystem, 'Windows')) }}: + - ${{ if ne(variables['dotnetx86RootPath'], '') }}: + # Install the .NET SDK and Runtimes for x86. + - template: /eng/pipelines/steps/install-dotnet.yml@self + parameters: + architecture: x86 + debug: ${{ parameters.debug }} + installDir: $(dotnetx86RootPath) + runtimes: [8.x, 9.x] + + # Ensure TestResults directory exists so that publish steps don't fail when + # tests are skipped due to a setup failure. + - pwsh: New-Item -ItemType Directory -Path TestResults -Force | Out-Null + displayName: 'Create TestResults Directory' + + # Gate: record that all setup steps (SDK install, build, SQL config, x86 SDK + # install, etc.) completed successfully. Test steps condition on this variable + # so they are skipped when setup fails, yet remain independent of each + # other's results. + - pwsh: Write-Host '##vso[task.setvariable variable=setupSucceeded]true' + displayName: 'Gate: Mark Setup Succeeded' + - ${{ if eq(parameters.enableX64Test, true) }}: # run native tests - template: /eng/pipelines/common/templates/steps/run-all-tests-step.yml@self # run tests parameters: @@ -340,20 +367,6 @@ jobs: mdsPackageVersion: ${{ parameters.mdsPackageVersion }} - ${{ 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. - - ${{ if ne(variables['dotnetx86RootPath'], '') }}: - # Install the .NET SDK and Runtimes for x86. - - template: /eng/pipelines/steps/install-dotnet.yml@self - parameters: - architecture: x86 - debug: ${{ parameters.debug }} - installDir: $(dotnetx86RootPath) - runtimes: [8.x, 9.x] - - template: /eng/pipelines/common/templates/steps/run-all-tests-step.yml@self parameters: debug: ${{ parameters.debug }} 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 2c407f7d1e..68689adf44 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -79,7 +79,7 @@ steps: - ${{if eq(parameters.referenceType, 'Project')}}: - task: MSBuild@1 displayName: 'Run Unit Tests ${{parameters.msbuildArchitecture }}' - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} @@ -101,7 +101,7 @@ steps: - task: MSBuild@1 displayName: 'Run Flaky Unit Tests ${{parameters.msbuildArchitecture }}' - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} @@ -128,7 +128,7 @@ steps: - task: MSBuild@1 displayName: 'Run Functional Tests ${{parameters.msbuildArchitecture }}' - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: solution: build2.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} @@ -155,7 +155,7 @@ steps: -p:TestResultsFolderPath=TestResults - task: MSBuild@1 - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) displayName: 'Run Flaky Functional Tests ${{parameters.msbuildArchitecture }}' inputs: solution: build2.proj @@ -188,7 +188,7 @@ steps: continueOnError: true - task: MSBuild@1 - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) displayName: 'Run Manual Tests ${{parameters.msbuildArchitecture }}' inputs: solution: build2.proj @@ -219,7 +219,7 @@ steps: retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} - task: MSBuild@1 - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) displayName: 'Run Flaky Manual Tests ${{parameters.msbuildArchitecture }}' inputs: solution: build2.proj @@ -257,7 +257,7 @@ steps: - ${{if eq(parameters.referenceType, 'Project')}}: - task: DotNetCoreCLI@2 displayName: 'Run Unit Tests' - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom projects: build2.proj @@ -276,7 +276,7 @@ steps: - task: DotNetCoreCLI@2 displayName: 'Run Flaky Unit Tests' - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom projects: build2.proj @@ -298,7 +298,7 @@ steps: - task: DotNetCoreCLI@2 displayName: 'Run Functional Tests' - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom projects: build2.proj @@ -316,7 +316,7 @@ steps: verbosityPack: Detailed - task: DotNetCoreCLI@2 - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) displayName: 'Run Flaky Functional Tests' inputs: command: custom @@ -339,7 +339,7 @@ steps: - task: DotNetCoreCLI@2 displayName: 'Run Manual Tests' - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom projects: build2.proj @@ -360,7 +360,7 @@ steps: - task: DotNetCoreCLI@2 displayName: 'Run Flaky Manual Tests' - condition: succeededOrFailed() + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom projects: build2.proj From d197d6a3654ccde467c7ab549d8f9e2a0f022ea8 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:14:31 -0300 Subject: [PATCH 035/298] Pin .NET SDK to 10.0.105 (#4165) --- global.json | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/global.json b/global.json index 303d578bc6..3d74096930 100644 --- a/global.json +++ b/global.json @@ -3,22 +3,14 @@ { // We currently require the .NET 10 SDK to build and test the project. // - // We specify the most recent release, and let rollForward pick the latest - // available suitable SDK per the rollForward setting. + // GOTCHA: Our CI infrastructure for Windows uses VS 2022 which comes with MSBuild 17.x. The + // .NET 10 SDK versions in the 10.0.2xx series require MSBuild 18.x, so we specify the most + // recent 10.0.1xx series release which is compatible with MSBuild 17.x. // - // We must specify a complete version here since this file is also used by - // the Azure Pipelines UseDotNet@2 task, which doesn't support wildcards, - // and won't roll-forward. It uses the version verbatim. - // - // GOTCHA: This file is only used by the dotnet CLI and related tools. - // Other toolchains (IDEs like Visual Studio, the .NET Framework MSBuild - // system, etc.) may use their own installed SDKs unless configured - // otherwise. - // - "version": "10.0.104", + "version": "10.0.105", - // Any 10.x version is acceptable. - "rollForward": "latestMinor", + // We cannot allow any roll forward due to the above MSBuild compatibility issues. + "rollForward": "disable", // Do not allow pre-release versions. // From 8fc7154ff027adade0e71c58b0374ce0884e82ef Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 9 Apr 2026 13:07:12 -0500 Subject: [PATCH 036/298] Official Build Cleanup 2 (#4155) * PR comments and uh what * Rely on AssemblyBuildNumber for non-MDS projects, too. --- .../onebranch/jobs/validate-signed-package-job.yml | 10 +++------- eng/pipelines/onebranch/sqlclient-non-official.yml | 1 - eng/pipelines/onebranch/sqlclient-official.yml | 1 - eng/pipelines/onebranch/stages/build-stages.yml | 6 ------ .../steps/roslyn-analyzers-sqlclient-step.yml | 6 +++--- .../onebranch/variables/onebranch-variables.yml | 2 +- .../onebranch/variables/package-variables.yml | 2 +- .../Abstractions/src/AbstractionsVersions.props | 6 +++--- .../Azure/src/AzureVersions.props | 4 ++-- .../Logging/src/LoggingVersions.props | 6 +++--- src/Microsoft.Data.SqlClient/MdsVersions.props | 1 - .../AzureKeyVaultProvider/AkvProviderVersions.props | 6 +++--- tools/props/Versions.props | 2 +- 13 files changed, 20 insertions(+), 33 deletions(-) diff --git a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml index bc566398cc..27369eb6cc 100644 --- a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml @@ -36,10 +36,6 @@ parameters: - name: isOfficial type: boolean - # True if this build is a preview. - - name: isPreview - type: boolean - jobs: - job: validate_nuget_package displayName: "Validate NuGet package" @@ -58,7 +54,7 @@ jobs: # Path to the SqlClient NuGet package after installation. This path will only exist once the package # been installed. - name: nugetPackageInstallPath - value: '$(Pipeline.Workspace)\nugetPackageInstalls\Microsoft.Data.SqlClient\${{ parameters.expectedPackageVersion }}' + value: '$(Pipeline.Workspace)\nugetPackageInstalls\Microsoft.Data.SqlClient.${{ parameters.expectedPackageVersion }}' # Root folder where NuGet package will be installed locally - name: nugetPackageInstallRoot @@ -119,8 +115,8 @@ jobs: -SkipDependencies echo "> 2. Listing contents of installed Microsoft.Data.SqlClient NuGet package:" - Write-Host $artifactPath - Get-ChildItem $artifactPath + Write-Host $nugetPackageInstallRoot + Get-ChildItem $nugetPackageInstallRoot displayName: "Install NuGet Package" # Find all DLL files in the installed NuGet package, verify each is signed with a strong name diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index 1ab3c653c3..a908ae4e74 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -173,7 +173,6 @@ extends: stages: - template: /eng/pipelines/onebranch/stages/build-stages.yml@self parameters: - isPreview: ${{ parameters.isPreview }} isOfficial: false # This is a non-official pipeline. publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index c6c18d679a..7f02ea0226 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -201,7 +201,6 @@ extends: stages: - template: /eng/pipelines/onebranch/stages/build-stages.yml@self parameters: - isPreview: ${{ parameters.isPreview }} isOfficial: true # This is an official pipeline. publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index 14e7977c3f..ba24af6309 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -22,11 +22,6 @@ parameters: - name: isOfficial type: boolean - # True if this is a preview build, which uses the preview version numbers from - # common-variables.yml. - - name: isPreview - type: boolean - # True to publish symbols to public and private servers. - name: publishSymbols type: boolean @@ -358,5 +353,4 @@ stages: artifactName: '${{ parameters.sqlClientArtifactsName }}' expectedFileVersion: '${{ parameters.sqlClientFileVersion }}' expectedPackageVersion: '${{ parameters.sqlClientPackageVersion }}' - isPreview: ${{ parameters.isPreview }} isOfficial: ${{ parameters.isOfficial }} diff --git a/eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml index 813df97326..ac515aa021 100644 --- a/eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml +++ b/eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml @@ -32,9 +32,9 @@ steps: $(REPO_ROOT)/build2.proj -t:BuildMds -p:Configuration=Release - -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" - -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" - -p:PackageVersionMds="${{ parameters.sqlClientPackageVersion }}" + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionMds=${{ parameters.sqlClientPackageVersion }} -p:ReferenceType=Package msBuildVersion: 17.0 setupCommandLinePicker: vs2022 diff --git a/eng/pipelines/onebranch/variables/onebranch-variables.yml b/eng/pipelines/onebranch/variables/onebranch-variables.yml index 4009c60b3d..7b7c7597cb 100644 --- a/eng/pipelines/onebranch/variables/onebranch-variables.yml +++ b/eng/pipelines/onebranch/variables/onebranch-variables.yml @@ -4,7 +4,7 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# This file is only included in SqlClient OneBranch Official pipelines. +# This file is only included in SqlClient OneBranch official/non-official pipelines. variables: # Libraries ============================================================== diff --git a/eng/pipelines/onebranch/variables/package-variables.yml b/eng/pipelines/onebranch/variables/package-variables.yml index 92f9863021..b1466b06b9 100644 --- a/eng/pipelines/onebranch/variables/package-variables.yml +++ b/eng/pipelines/onebranch/variables/package-variables.yml @@ -41,7 +41,7 @@ variables: # $(Build.BuildNumber) has the format XXX.YY. Additionally, each version part must be a positive # 16-bit integer less than 65535. Simply concatenating both parts of $(Build.BuildNumber) could # produce values larger than 65534, so we must omit the second part entirely. Unfortunately, this - # may result in multiple subsequent pipline builds using the same C# assembly versions. The + # may result in multiple subsequent pipeline builds using the same C# assembly versions. The # package versions will not be affected and will show the complete $(Build.BuildNumber) values. - name: _assemblyBuildNumber value: $[ split(variables['Build.BuildNumber'], '.')[0] ] diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props index 2bd328fb92..75be8d98c3 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props @@ -24,7 +24,7 @@ - Used as the base value of the assembly attribute 'AssemblyInformationalVersion' generated by MSBuild. - If not specified, defaults to: - $(DefaultMajorVersion).0.0.$(BuildNumber)-dev. + $(DefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev. AbstractionsAssemblyFileVersion: - Used as the value of the assembly attribute 'AssemblyFileVersion' @@ -33,7 +33,7 @@ - If $(AbstractionsPackageVersion) is specified, use the numeric parts of its value, for example 1.0.0.345. - Otherwise, defaults to: - $(DefaultMajorVersion).0.0.$(BuildNumber). + $(DefaultMajorVersion).0.0.$(AssemblyBuildNumber). We use private properties to hold the computed values, and then always assign those to the public properties. After importing this file, the @@ -45,7 +45,7 @@ <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' != ''">$(AbstractionsPackageVersion) - <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' == ''">$(AbstractionsDefaultMajorVersion).0.0.$(BuildNumber)-dev + <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' == ''">$(AbstractionsDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props index 3a9debd3bb..2102cd798a 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props @@ -23,7 +23,7 @@ - Used as the base value of the assembly attribute 'AssemblyInformationalVersion' generated by MSBuild. - If not specified, defaults to: - $(DefaultMajorVersion).0.0.$(BuildNumber)-dev. + $(DefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev. AzureAssemblyFileVersion: - Used as the value of the assembly attribute 'AssemblyFileVersion' @@ -44,7 +44,7 @@ <_OurPackageVersion Condition="'$(AzurePackageVersion)' != ''">$(AzurePackageVersion) - <_OurPackageVersion Condition="'$(AzurePackageVersion)' == ''">$(AzureDefaultMajorVersion).0.0.$(BuildNumber)-dev + <_OurPackageVersion Condition="'$(AzurePackageVersion)' == ''">$(AzureDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev diff --git a/src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props b/src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props index 597a692457..9205b88793 100644 --- a/src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props +++ b/src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props @@ -24,7 +24,7 @@ - Used as the base value of the assembly attribute 'AssemblyInformationalVersion' generated by MSBuild. - If not specified, defaults to: - $(DefaultMajorVersion).0.0.$(BuildNumber)-dev. + $(DefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev. LoggingAssemblyFileVersion: - Used as the value of the assembly attribute 'AssemblyFileVersion' @@ -33,7 +33,7 @@ - If $(LoggingPackageVersion) is specified, use the numeric parts of its value, for example 1.0.0.345. - Otherwise, defaults to: - $(DefaultMajorVersion).0.0.$(BuildNumber). + $(DefaultMajorVersion).0.0.$(AssemblyBuildNumber). We use private properties to hold the computed values, and then always assign those to the public properties. After importing this file, the @@ -45,7 +45,7 @@ <_OurPackageVersion Condition="'$(LoggingPackageVersion)' != ''">$(LoggingPackageVersion) - <_OurPackageVersion Condition="'$(LoggingPackageVersion)' == ''">$(LoggingDefaultMajorVersion).0.0.$(BuildNumber)-dev + <_OurPackageVersion Condition="'$(LoggingPackageVersion)' == ''">$(LoggingDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev diff --git a/src/Microsoft.Data.SqlClient/MdsVersions.props b/src/Microsoft.Data.SqlClient/MdsVersions.props index a9f8d1cc83..97063881d9 100644 --- a/src/Microsoft.Data.SqlClient/MdsVersions.props +++ b/src/Microsoft.Data.SqlClient/MdsVersions.props @@ -85,5 +85,4 @@ $(MdsFileVersion.Split('.')[0]).0.0.0 - diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props index d575830c40..47982a6478 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props @@ -23,7 +23,7 @@ - Used as the base value of the assembly attribute 'AssemblyInformationalVersion' generated by MSBuild. - If not specified, defaults to: - $(AkvDefaultMajorVersion).0.0.$(BuildNumber)-dev. + $(AkvDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev. AkvAssemblyFileVersion: - Used as the value of the assembly attribute 'AssemblyFileVersion' @@ -32,7 +32,7 @@ - If $(AkvPackageVersion) is specified, use the numeric parts of its value, for example 7.0.0.345. - Otherwise, defaults to: - $(AkvDefaultMajorVersion).0.0.$(BuildNumber). + $(AkvDefaultMajorVersion).0.0.$(AssemblyBuildNumber). We use private properties to hold the computed values, and then always assign those to the public properties. After importing this file, the @@ -44,7 +44,7 @@ <_AkvPackageVersion Condition="'$(AkvPackageVersion)' != ''">$(AkvPackageVersion) - <_AkvPackageVersion Condition="'$(AkvPackageVersion)' == ''">$(AkvDefaultMajorVersion).0.0.$(BuildNumber)-dev + <_AkvPackageVersion Condition="'$(AkvPackageVersion)' == ''">$(AkvDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 858b41628b..f2132677c9 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -19,7 +19,7 @@ 1.0.0.0 1.0.0.0 - 1.0.0.$(BuildNumber)-dev + 1.0.0.$(AssemblyBuildNumber)-dev $(SqlServerPackageVersion) From 6db52a687d26547ce0c5641ccc6b66bedfdb7e42 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 9 Apr 2026 17:37:49 -0500 Subject: [PATCH 037/298] Merge | Cleanup (#4145) * Delete separate projects ... this was less satisfying than I expected it'd be * Test output should go to the same folder as the project ... no need to give it a special place. * Drop targets that aren't necessary from build.proj * Remove extraneous properties from build.proj * Remove unused targets * Test project references from RepoRoot * Remove unnecessary stuff from Directory.Build.props * Drop some more unnecessary files * Remove references to BuildAllConfigurations * :robot: Remove separate project stuff from CompareMdsRefAssemblies.targets --- build.proj | 296 +---- .../steps/roslyn-analyzers-csproj-step.yml | 3 +- src/Directory.Build.props | 76 +- src/Microsoft.Data.SqlClient.slnx | 77 +- .../ref/Microsoft.Data.SqlClient.csproj | 72 -- .../src/Microsoft.Data.SqlClient.csproj | 1105 ----------------- .../netfx/ref/Microsoft.Data.SqlClient.csproj | 69 - .../netfx/src/Microsoft.Data.SqlClient.csproj | 1089 ---------------- .../targets/GenerateAssemblyInfo.targets | 36 - .../tools/targets/GenerateAssemblyRef.targets | 31 - ...soft.Data.SqlClient.FunctionalTests.csproj | 24 +- ...icrosoft.Data.SqlClient.ManualTests.csproj | 20 +- .../SQL/UdtTest/UDTs/Address/Address.csproj | 5 - .../SQL/UdtTest/UDTs/Circle/Circle.csproj | 5 - .../SQL/UdtTest/UDTs/Shapes/Shapes.csproj | 5 - .../UdtTest/UDTs/Utf8String/Utf8String.csproj | 5 - ...oft.Data.SqlClient.PerformanceTests.csproj | 37 +- .../Microsoft.Data.SqlClient.UnitTests.csproj | 16 +- ...crosoft.Data.SqlClient.ExtUtilities.csproj | 2 +- ...rosoft.Data.SqlClient.TestUtilities.csproj | 4 +- .../TDS/TDS.EndPoint/TDS.EndPoint.csproj | 11 +- .../tools/TDS/TDS.Servers/TDS.Servers.csproj | 13 +- .../tests/tools/TDS/TDS/TDS.csproj | 5 +- tools/GenAPI/Directory.Build.props | 7 - tools/props/Tools.props | 8 - tools/targets/CompareMdsRefAssemblies.targets | 104 +- tools/targets/GenerateMdsPackage.targets | 14 - tools/targets/NotSupported.targets | 65 - tools/targets/ResolveContract.targets | 29 - tools/targets/TrimDocsForIntelliSense.targets | 9 - 30 files changed, 132 insertions(+), 3110 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj delete mode 100644 src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj delete mode 100644 src/Microsoft.Data.SqlClient/netfx/tools/targets/GenerateAssemblyInfo.targets delete mode 100644 src/Microsoft.Data.SqlClient/netfx/tools/targets/GenerateAssemblyRef.targets delete mode 100644 tools/GenAPI/Directory.Build.props delete mode 100644 tools/props/Tools.props delete mode 100644 tools/targets/GenerateMdsPackage.targets delete mode 100644 tools/targets/NotSupported.targets delete mode 100644 tools/targets/ResolveContract.targets delete mode 100644 tools/targets/TrimDocsForIntelliSense.targets diff --git a/build.proj b/build.proj index 33ff1aff31..f6954667c4 100644 --- a/build.proj +++ b/build.proj @@ -1,8 +1,7 @@ - + - @@ -10,26 +9,7 @@ false Debug - AnyCPU - - true - - true - false - Windows - Unix - net9.0 - netfx - netcore - netfx - netcoreapp - $(TF) - $(TF) - true - - Configuration=$(Configuration);ReferenceType=$(ReferenceType); - $(CommonProperties);AssemblyFileVersion=$(AssemblyFileVersion);TargetsWindows=$(TargetsWindows);TargetsUnix=$(TargetsUnix); - $(ProjectProperties);BuildForRelease=false;TargetNetCoreVersion=$(TargetNetCoreVersion);TargetNetFxVersion=$(TargetNetFxVersion) + TestResults - - 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; - - - - - - category!=failing&category!=flaky&category!=interactive - --filter "$(Filter)" - - - true - $(TestsPath)/tools/Microsoft.Data.SqlClient.TestUtilities/CodeCoverage.runsettings - --collect "Code coverage" --settings "$(CodeCoverageRunSettings)" - - - $(Blame) - - --blame-hang - --blame-hang-dump-type full - --blame-hang-timeout 10m - @@ -76,39 +32,19 @@ - - - - - - - - - - - - - + - - - - - - $(CommonProperties) - + $(SqlServerProperties);SqlServerPackageVersion=$(SqlServerPackageVersion) - + $(SqlServerProperties);SqlServerAssemblyFileVersion=$(SqlServerAssemblyFileVersion) @@ -148,14 +84,12 @@ That results in $(AbstractionsPackageVersion) being defined as empty, and cannot be overridden by the project. --> - + $(AbstractionsProperties);AbstractionsPackageVersion=$(AbstractionsPackageVersion) - + $(AbstractionsProperties);AbstractionsAssemblyFileVersion=$(AbstractionsAssemblyFileVersion) @@ -191,14 +125,12 @@ to avoid it expanding as empty here and thus overriding the default behaviour in the Logging project. --> - + $(LoggingProperties);LoggingPackageVersion=$(LoggingPackageVersion) - + $(LoggingProperties);LoggingAssemblyFileVersion=$(LoggingAssemblyFileVersion) @@ -240,14 +172,12 @@ That results in $(AzurePackageVersion) being defined as empty, and cannot be overridden by the project. --> - + $(AzureProperties);AzurePackageVersion=$(AzurePackageVersion) - + $(AzureProperties);AzureAssemblyFileVersion=$(AzureAssemblyFileVersion) @@ -273,210 +203,6 @@ Properties="$(AzureProperties);NoBuild=true" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $(DotnetPath)dotnet test "@(UnitTestsProj)" - -f $(TF) - -p:Configuration=$(Configuration) - $(FilterArgument) - $(BlameArgument) - $(CollectArgument) - --results-directory $(ResultsDirectory) - --logger:"trx;LogFilePrefix=Unit-Windows$(TargetGroup)-$(TestSet)" - - - $([System.Text.RegularExpressions.Regex]::Replace($(TestCommand), "\s+", " ")) - - - - - - - - - - $(DotnetPath)dotnet test "@(UnitTestsProj)" - -f $(TF) - -p:Configuration=$(Configuration) - $(FilterArgument) - $(BlameArgument) - $(CollectArgument) - --results-directory $(ResultsDirectory) - --logger:"trx;LogFilePrefix=Unit-Unixnetcoreapp-$(TestSet)" - - - $([System.Text.RegularExpressions.Regex]::Replace($(TestCommand), "\s+", " ")) - - - - - - - - - - - - - $(DotnetPath)dotnet test "@(FunctionalTestsProj)" - -f $(TF) - -p:Configuration=$(Configuration) - -p:ReferenceType=$(ReferenceType) - -p:AbstractionsPackageVersion=$(AbstractionsPackageVersion) - -p:MdsPackageVersion=$(MdsPackageVersion) - $(FilterArgument) - $(BlameArgument) - $(CollectArgument) - --results-directory $(ResultsDirectory) - --logger:"trx;LogFilePrefix=Functional-Windows$(TargetGroup)-$(TestSet)" - - - $([System.Text.RegularExpressions.Regex]::Replace($(TestCommand), "\s+", " ")) - - - - - - - - - - $(DotnetPath)dotnet test "@(FunctionalTestsProj)" - -f $(TF) - -p:Configuration=$(Configuration) - -p:ReferenceType=$(ReferenceType) - -p:AbstractionsPackageVersion=$(AbstractionsPackageVersion) - -p:MdsPackageVersion=$(MdsPackageVersion) - $(FilterArgument) - $(BlameArgument) - $(CollectArgument) - --results-directory $(ResultsDirectory) - --logger:"trx;LogFilePrefix=Functional-Unixnetcoreapp-$(TestSet)" - - - $([System.Text.RegularExpressions.Regex]::Replace($(TestCommand), "\s+", " ")) - - - - - - - - - - - - - $(DotnetPath)dotnet test "@(ManualTestsProj)" - -f $(TF) - -p:Configuration=$(Configuration) - -p:ReferenceType=$(ReferenceType) - -p:AbstractionsPackageVersion=$(AbstractionsPackageVersion) - -p:MdsPackageVersion=$(MdsPackageVersion) - $(FilterArgument) - $(BlameArgument) - $(CollectArgument) - --results-directory $(ResultsDirectory) - --logger:"trx;LogFilePrefix=Manual-Windows$(TargetGroup)-$(TestSet)" - - - - $(TestCommand) - -p:TestSet=$(TestSet) - - - - $([System.Text.RegularExpressions.Regex]::Replace($(TestCommand), "\s+", " ")) - - - - - - - - - - $(DotnetPath)dotnet test "@(ManualTestsProj)" - -f $(TF) - -p:Configuration=$(Configuration) - -p:ReferenceType=$(ReferenceType) - -p:AbstractionsPackageVersion=$(AbstractionsPackageVersion) - -p:MdsPackageVersion=$(MdsPackageVersion) - $(FilterArgument) - $(BlameArgument) - $(CollectArgument) - --results-directory $(ResultsDirectory) - --logger:"trx;LogFilePrefix=Manual-Unixnetcoreapp-$(TestSet)" - - - - - $(TestCommand) - -p:TestSet=$(TestSet) - - - - $([System.Text.RegularExpressions.Regex]::Replace($(TestCommand), "\s+", " ")) - - - - - diff --git a/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml index 8810bed838..53b17d4bb9 100644 --- a/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml +++ b/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml @@ -10,10 +10,9 @@ # via the msBuildArguments parameter. # It uses the RoslynAnalyzers@3 task from the Secure Development Team's SDL # extension: -# # https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/roslyn-analyzers-build-task # -# GOTCHA: This step will clobber any existing build output. It should be run +# GOTCHA: This step will clobber any existing build output. It should be run # _before_ any build steps that perform versioning or signing. parameters: diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5152ce6065..6dcd4bbbd9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,14 +1,6 @@ - true - false - true - true - false - true - true - true Project + + $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) + + $(DefineConstants);DEBUG @@ -33,40 +29,14 @@ $(MSBuildThisFileDirectory) $(ProjectDir)..\ $(RepoRoot)artifacts\$(ReferenceType)\ - $(Artifacts)tools\ - $(ProjectDir)Microsoft.Data.SqlClient\ - $(ProjectDir)Microsoft.SqlServer.Server\ - $(ManagedSourceCode)netcore\ - $(ManagedSourceCode)src\Resources\ - Strings - SqlClient.Resources.$(ResxFileName) - $(ResxFileName).ResourceNames.cs - $(ManagedSourceCode)netfx\ - $(ManagedSourceCode)src\Resources\ - $(ManagedSourceCode)add-ons\ - $(RepoRoot)src\Microsoft.SqlServer.Server\ - $(RepoRoot)src\Microsoft.Data.SqlClient\src\ - $(Artifacts)obj\ - $(NetCoreSource)src\Common\src - $(NetCoreSource)src\Common\tests - $(ProjectDir)Microsoft.Data.SqlClient/tests/ - $(Artifacts)bin\Windows_NT\ - $(Artifacts)bin\AnyOS\ - $(Artifacts)bin\Unix\ + $(RepoRoot)tools\ - $(ToolsDir)GenAPI\ $(RepoRoot)packages\ - $(RepoRoot).nuget\ - $(NuGetRoot)nuget.exe false - - - $(DefineConstants);$(UserDefinedConstants) - - + $(DefineConstants);ENCLAVE_SIMULATOR @@ -98,18 +68,15 @@ true - + @@ -126,7 +93,7 @@ $(DefineConstants);STRONG_NAME_SIGNING - + portable true @@ -135,14 +102,10 @@ true false - - true - - - + @@ -154,20 +117,15 @@ - 14 diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index 46eb7fc748..e08e693479 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -10,6 +10,9 @@ + + + @@ -130,30 +133,27 @@ - - - - + + + + + - + + - - + - - - - - + - + - + @@ -164,41 +164,22 @@ - + - + - - - - - - - - + + - - - - - - - - - - - - - @@ -206,9 +187,7 @@ - - - + @@ -231,19 +210,9 @@ - - - - - - - - - - - - - + + + + - diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj deleted file mode 100644 index 3215e42068..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - Microsoft.Data.SqlClient - net8.0;net9.0;netstandard2.0 - false - $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ - netcoreapp - $(BinFolder)$(Configuration)\$(AssemblyName)\ref\ - - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName)\netstandard\ - $(OutputPath)\$(TargetFramework)\$(AssemblyName).xml - Core $(BaseProduct) - Debug;Release; - AnyCPU;x64;x86 - - - - - - <_Parameter1>true - - - - - - - - - - - - - - TypeForwards.Abstractions.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj deleted file mode 100644 index c2fdb27f3f..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ /dev/null @@ -1,1105 +0,0 @@ - - - Microsoft.Data.SqlClient - net8.0;net9.0 - $(OS) - Microsoft.Data.SqlClient is not supported on this platform. - - false - netcoreapp - Debug;Release; - AnyCPU;x64;x86 - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName)\netcore\ - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName)\netcore\ - $(OutputPath)\$(TargetFramework)\Microsoft.Data.SqlClient.xml - true - Core $(BaseProduct) - true - $(NoWarn);IL2026;IL2057;IL2072;IL2075 - - false - - - - - - - - - $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFramework)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) - - - - - <_Parameter1>true - - - - - - - - - - - $(DefineConstants),_UNIX - $(DefineConstants),_WINDOWS - - - - - - Interop\Common\Sni\SniErrors.cs - - - Interop\Windows\Handles\SafeLibraryHandle.netcore.cs - - - Interop\Windows\Kernel32\FileTypes.cs - - - Interop\Windows\Kernel32\IoControlCodeAccess.cs - - - Interop\Windows\Kernel32\IoControlTransferType.cs - - - Interop\Windows\Kernel32\Kernel32.cs - - - Interop\Windows\NtDll\CreateDisposition.cs - - - Interop\Windows\NtDll\CreateOptions.cs - - - Interop\Windows\NtDll\DesiredAccess.cs - - - Interop\Windows\NtDll\FileFullEaInformation.cs - - - Interop\Windows\NtDll\ImpersonationLevel.cs - - - Interop\Windows\NtDll\IoStatusBlock.cs - - - Interop\Windows\NtDll\NtDll.cs - - - Interop\Windows\NtDll\ObjectAttributeFlags.cs - - - Interop\Windows\NtDll\ObjectAttributes.cs - - - Interop\Windows\NtDll\SecurityQualityOfService.cs - - - Interop\Windows\Sni\AuthProviderInfo.cs - - - Interop\Windows\Sni\ConsumerInfo.cs - - - Interop\Windows\Sni\ConsumerNumber.cs - - - Interop\Windows\Sni\Delegates.cs - - - Interop\Windows\Sni\IoType.cs - - - Interop\Windows\Sni\ISniNativeMethods.cs - - - Interop\Windows\Sni\Prefix.cs - - - Interop\Windows\Sni\Provider.cs - - - Interop\Windows\Sni\QueryType.cs - - - Interop\Windows\Sni\SniClientConsumerInfo.cs - - - Interop\Windows\Sni\SniConsumerInfo.cs - - - Interop\Windows\Sni\SniDnsCacheInfo.cs - - - Interop\Windows\Sni\SniError.cs - - - Interop\Windows\Sni\SniNativeMethods.netcore.cs - - - Interop\Windows\Sni\SniNativeWrapper.cs - - - Interop\Windows\Sni\SniSslProtocols.cs - - - Interop\Windows\Sni\TransparentNetworkResolutionMode.cs - - - Interop\Windows\SystemErrors.cs - - - Interop\Windows\UnicodeString.cs - - - Microsoft\Data\Common\ActivityCorrelator.cs - - - Microsoft\Data\Common\AdapterUtil.cs - - - Microsoft\Data\Common\AdapterUtil.netcore.cs - - - Microsoft\Data\Common\BitConverterCompatible.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\ConnectionString\IpAddressPreferenceUtilities.cs - - - Microsoft\Data\Common\ConnectionString\PoolBlockingUtilities.cs - - - Microsoft\Data\Common\MultipartIdentifier.cs - - - Microsoft\Data\Common\NameValuePair.cs - - - Microsoft\Data\DataException.cs - - - Microsoft\Data\OperationAbortedException.cs - - - Microsoft\Data\ProviderBase\DbConnectionClosed.cs - - - Microsoft\Data\ProviderBase\DbConnectionInternal.cs - - - Microsoft\Data\ProviderBase\DbReferenceCollection.cs - - - Microsoft\Data\ProviderBase\FieldNameLookup.cs - - - Microsoft\Data\ProviderBase\TimeoutTimer.cs - - - Microsoft\Data\SqlClient\AlwaysEncrypted\ColumnMasterKeyMetadata.cs - - - Microsoft\Data\SqlClient\AlwaysEncrypted\EncryptedColumnEncryptionKeyParameters.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumerator.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumeratorManagedHelper.netcore.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumeratorUtil.cs - - - Microsoft\Data\Sql\SqlNotificationRequest.cs - - - Microsoft\Data\SqlClient\AAsyncCallContext.cs - - - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationTimeoutRetryHelper.cs - - - Microsoft\Data\SqlClient\AlwaysEncryptedEnclaveProviderUtils.cs - - - Microsoft\Data\SqlClient\AlwaysEncryptedHelperClasses.cs - - - Microsoft\Data\SqlClient\AlwaysEncryptedKeyConverter.cs - - - Microsoft\Data\SqlClient\ApplicationIntent.cs - - - Microsoft\Data\SqlClient\AssemblyRef.cs - - - Microsoft\Data\SqlClient\AzureAttestationBasedEnclaveProvider.cs - - - Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs - - - Microsoft\Data\SqlClient\Connection\CachedContexts.cs - - - Microsoft\Data\SqlClient\Connection\ServerInfo.cs - - - Microsoft\Data\SqlClient\Connection\SessionData.cs - - - Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs - - - Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs - - - Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs - - - Microsoft\Data\SqlClient\ConnectionPool\ConnectionPoolSlots.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContextKey.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroup.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroupProviderInfo.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolIdentity.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolKey.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolOptions.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\ConnectionPool\SqlConnectionPoolKey.cs - - - Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolProviderInfo.cs - - - Microsoft\Data\SqlClient\ConnectionPool\TransactedConnectionPool.cs - - - Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs - - - Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs - - - Microsoft\Data\SqlClient\Diagnostics\DiagnosticScope.cs - - - Microsoft\Data\SqlClient\Diagnostics\DiagnosticTransactionScope.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlDiagnosticListener.cs - - - Microsoft\Data\SqlClient\DisposableTemporaryOnStack.cs - - - Microsoft\Data\SqlClient\EnclaveDelegate.cs - - - Microsoft\Data\SqlClient\EnclaveDelegate.Crypto.cs - - - Microsoft\Data\SqlClient\EnclavePackage.cs - - - Microsoft\Data\SqlClient\EnclaveProviderBase.cs - - - Microsoft\Data\SqlClient\EnclaveSessionCache.cs - - - Microsoft\Data\SqlClient\ISqlVector.cs - - - Microsoft\Data\SqlClient\LocalAppContextSwitches.cs - - - Microsoft\Data\SqlClient\LocalDb\LocalDbApi.unix.cs - - - Microsoft\Data\SqlClient\LocalDb\LocalDbApi.windows.cs - - - Microsoft\Data\SqlClient\ManagedSni\ConcurrentQueueSemaphore.netcore.cs - - - Microsoft\Data\SqlClient\ManagedSni\LocalDB.netcore.unix.cs - - - Microsoft\Data\SqlClient\ManagedSni\LocalDB.netcore.windows.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\SsrpClient.netcore.cs - - - Microsoft\Data\SqlClient\NoneAttestationEnclaveProvider.cs - - - Microsoft\Data\SqlClient\OnChangedEventHandler.cs - - - Microsoft\Data\SqlClient\Packet.cs - - - Microsoft\Data\SqlClient\PacketHandle.netcore.unix.cs - - - Microsoft\Data\SqlClient\PacketHandle.windows.cs - - - Microsoft\Data\SqlClient\ParameterPeekAheadValue.cs - - - Microsoft\Data\SqlClient\PoolBlockingPeriod.cs - - - 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\SqlConfigurableRetryFactory.cs - - - Microsoft\Data\SqlClient\Reliability\SqlConfigurableRetryLogicLoader.cs - - - Microsoft\Data\SqlClient\Reliability\SqlConfigurableRetryLogicManager.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryingEventArgs.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryIntervalBaseEnumerator.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryLogic.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryLogicBase.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryLogicBaseProvider.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryLogicProvider.cs - - - Microsoft\Data\SqlClient\Reliability\SqlRetryIntervalEnumerators.cs - - - Microsoft\Data\SqlClient\RowsCopiedEventArgs.cs - - - Microsoft\Data\SqlClient\RowsCopiedEventHandler.cs - - - Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs - - - Microsoft\Data\SqlClient\Server\ITypedGettersV3.cs - - - Microsoft\Data\SqlClient\Server\ITypedSettersV3.cs - - - Microsoft\Data\SqlClient\Server\MemoryRecordBuffer.cs - - - Microsoft\Data\SqlClient\Server\MetadataUtilsSmi.cs - - - Microsoft\Data\SqlClient\Server\SmiGettersStream.cs - - - Microsoft\Data\SqlClient\Server\SmiMetaData.cs - - - Microsoft\Data\SqlClient\Server\SmiMetaDataProperty.cs - - - Microsoft\Data\SqlClient\Server\SmiSettersStream.cs - - - Microsoft\Data\SqlClient\Server\SmiTypedGetterSetter.cs - - - Microsoft\Data\SqlClient\Server\SmiXetterAccessMap.cs - - - Microsoft\Data\SqlClient\Server\SmiXetterTypeCode.cs - - - Microsoft\Data\SqlClient\Server\SqlDataRecord.cs - - - Microsoft\Data\SqlClient\Server\SqlMetaData.cs - - - Microsoft\Data\SqlClient\Server\SqlNormalizer.cs - - - Microsoft\Data\SqlClient\Server\SqlRecordBuffer.cs - - - Microsoft\Data\SqlClient\Server\SqlSer.cs - - - Microsoft\Data\SqlClient\Server\ValueUtilsSmi.cs - - - Microsoft\Data\SqlClient\SessionHandle.netcore.unix.cs - - - Microsoft\Data\SqlClient\SessionHandle.windows.cs - - - Microsoft\Data\SqlClient\SignatureVerificationCache.cs - - - Microsoft\Data\SqlClient\SortOrder.cs - - - Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256Algorithm.cs - - - Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256EncryptionKey.cs - - - Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256Factory.cs - - - Microsoft\Data\SqlClient\SqlAppContextSwitchManager.netcore.cs - - - Microsoft\Data\SqlClient\SqlAuthenticationParametersBuilder.cs - - - Microsoft\Data\SqlClient\SqlAuthenticationProviderManager.cs - - - Microsoft\Data\SqlClient\SqlBatch.cs - - - Microsoft\Data\SqlClient\SqlBatchCommand.cs - - - Microsoft\Data\SqlClient\SqlBatchCommand.netcore.cs - - - Microsoft\Data\SqlClient\SqlBatchCommandCollection.cs - - - Microsoft\Data\SqlClient\SqlBuffer.cs - - - Microsoft\Data\SqlClient\SqlBulkCopy.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyColumnMapping.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyColumnMappingCollection.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyColumnOrderHint.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyColumnOrderHintCollection.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyOptions.cs - - - Microsoft\Data\SqlClient\SqlCachedBuffer.cs - - - Microsoft\Data\SqlClient\SqlClientEncryptionAlgorithm.cs - - - Microsoft\Data\SqlClient\SqlClientEncryptionAlgorithmFactory.cs - - - Microsoft\Data\SqlClient\SqlClientEncryptionAlgorithmFactoryList.cs - - - Microsoft\Data\SqlClient\SqlClientEncryptionType.cs - - - Microsoft\Data\SqlClient\SqlClientEventSource.cs - - - Microsoft\Data\SqlClient\SqlClientFactory.cs - - - Microsoft\Data\SqlClient\SqlClientLogger.cs - - - Microsoft\Data\SqlClient\SqlClientMetaDataCollectionNames.cs - - - Microsoft\Data\SqlClient\SqlClientSymmetricKey.cs - - - Microsoft\Data\SqlClient\SqlCollation.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionCertificateStoreProvider.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionCngProvider.netcore.unix.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionCngProvider.windows.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionCspProvider.netcore.unix.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionCspProvider.windows.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionEnclaveProvider.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs - - - Microsoft\Data\SqlClient\SqlCommand.cs - - - Microsoft\Data\SqlClient\SqlCommand.Batch.cs - - - Microsoft\Data\SqlClient\SqlCommand.Encryption.cs - - - Microsoft\Data\SqlClient\SqlCommand.NonQuery.cs - - - Microsoft\Data\SqlClient\SqlCommand.Reader.cs - - - Microsoft\Data\SqlClient\SqlCommand.Scalar.cs - - - Microsoft\Data\SqlClient\SqlCommand.Xml.cs - - - Microsoft\Data\SqlClient\SqlCommandBuilder.cs - - - Microsoft\Data\SqlClient\SqlCommandSet.cs - - - Microsoft\Data\SqlClient\SqlConnection.cs - - - Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs - - - Microsoft\Data\SqlClient\SqlConnectionEncryptOptionConverter.cs - - - Microsoft\Data\SqlClient\SqlConnectionFactory.cs - - - Microsoft\Data\SqlClient\SqlConnectionString.cs - - - Microsoft\Data\SqlClient\SqlConnectionStringBuilder.cs - - - Microsoft\Data\SqlClient\SqlConnectionTimeoutErrorInternal.cs - - - Microsoft\Data\SqlClient\SqlCredential.cs - - - Microsoft\Data\SqlClient\SqlDataAdapter.cs - - - Microsoft\Data\SqlClient\SqlDataReader.cs - - - Microsoft\Data\SqlClient\SqlDbColumn.cs - - - Microsoft\Data\SqlClient\SqlDelegatedTransaction.cs - - - Microsoft\Data\SqlClient\SqlDependency.cs - - - Microsoft\Data\SqlClient\SqlDependencyListener.cs - - - Microsoft\Data\SqlClient\SqlDependencyUtils.cs - - - Microsoft\Data\SqlClient\SqlDependencyUtils.AppDomain.netcore.cs - - - Microsoft\Data\SqlClient\SqlDependencyUtils.AssemblyLoadContext.netcore.cs - - - Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.Crypto.cs - - - Microsoft\Data\SqlClient\SqlEnclaveSession.cs - - - Microsoft\Data\SqlClient\SqlEnums.cs - - - Microsoft\Data\SqlClient\SqlEnvChange.cs - - - Microsoft\Data\SqlClient\SqlError.cs - - - Microsoft\Data\SqlClient\SqlErrorCollection.cs - - - Microsoft\Data\SqlClient\SqlException.cs - - - Microsoft\Data\SqlClient\SQLFallbackDNSCache.cs - - - Microsoft\Data\SqlClient\SqlInfoMessageEvent.cs - - - Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs - - - Microsoft\Data\SqlClient\SqlInternalTransaction.cs - - - Microsoft\Data\SqlClient\SqlMetaDataFactory.cs - - - Microsoft\Data\SqlClient\SqlNotificationEventArgs.cs - - - Microsoft\Data\SqlClient\SqlNotificationInfo.cs - - - Microsoft\Data\SqlClient\SqlNotificationSource.cs - - - Microsoft\Data\SqlClient\SqlNotificationType.cs - - - Microsoft\Data\SqlClient\SqlParameter.cs - - - Microsoft\Data\SqlClient\SqlParameterCollection.cs - - - Microsoft\Data\SqlClient\SqlQueryMetadataCache.cs - - - Microsoft\Data\SqlClient\SqlReferenceCollection.cs - - - Microsoft\Data\SqlClient\SqlRowUpdatedEvent.cs - - - Microsoft\Data\SqlClient\SqlRowUpdatedEventHandler.cs - - - Microsoft\Data\SqlClient\SqlRowUpdatingEvent.cs - - - Microsoft\Data\SqlClient\SqlRowUpdatingEventHandler.cs - - - Microsoft\Data\SqlClient\SqlSecurityUtility.cs - - - Microsoft\Data\SqlClient\SqlSequentialStream.cs - - - Microsoft\Data\SqlClient\SqlSequentialTextReader.cs - - - Microsoft\Data\SqlClient\SqlStatistics.cs - - - Microsoft\Data\SqlClient\SqlStream.cs - - - Microsoft\Data\SqlClient\SqlSymmetricKeyCache.cs - - - Microsoft\Data\SqlClient\SqlTransaction.cs - - - Microsoft\Data\SqlClient\SqlUdtInfo.cs - - - Microsoft\Data\SqlClient\SqlUtil.cs - - - Microsoft\Data\SqlClient\SSPI\NativeSspiContextProvider.windows.cs - - - Microsoft\Data\SqlClient\SSPI\NegotiateSspiContextProvider.cs - - - Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs - - - Microsoft\Data\SqlClient\SSPI\SspiContextProvider.cs - - - Microsoft\Data\SqlClient\Utilities\ObjectPool.cs - - - Microsoft\Data\SqlClient\Utilities\ObjectPools.cs - - - Microsoft\Data\SqlClient\TdsEnums.cs - - - Microsoft\Data\SqlClient\TdsParameterSetter.cs - - - Microsoft\Data\SqlClient\TdsParser.cs - - - Microsoft\Data\SqlClient\TdsParserHelperClasses.cs - - - Microsoft\Data\SqlClient\TdsParserSafeHandles.windows.cs - - - Microsoft\Data\SqlClient\TdsParserStateObject.cs - - - Microsoft\Data\SqlClient\TdsParserStateObject.Multiplexer.cs - - - Microsoft\Data\SqlClient\TdsParserStateObjectFactory.unix.cs - - - Microsoft\Data\SqlClient\TdsParserStateObjectFactory.windows.cs - - - Microsoft\Data\SqlClient\TdsParserStateObjectManaged.netcore.cs - - - Microsoft\Data\SqlClient\TdsParserStateObjectNative.windows.cs - - - Microsoft\Data\SqlClient\TdsParserStaticMethods.cs - - - Microsoft\Data\SqlClient\TdsRecordBufferSetter.cs - - - Microsoft\Data\SqlClient\TdsParserSessionPool.cs - - - Microsoft\Data\SqlClient\TdsValueSetter.cs - - - Microsoft\Data\SqlClient\TransactionRequest.cs - - - Microsoft\Data\SqlClient\UserAgent.cs - - - Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProvider.cs - - - Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProviderBase.cs - - - Microsoft\Data\SqlDbTypeExtensions.cs - - - Microsoft\Data\SqlTypes\SqlFileStream.netcore.unix.cs - - - Microsoft\Data\SqlTypes\SqlFileStream.windows.cs - - - Microsoft\Data\SqlTypes\SqlJson.cs - - - Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs - - - Microsoft\Data\SqlTypes\SqlVector.cs - - - Resources\ResCategoryAttribute.cs - - - Resources\ResDescriptionAttribute.cs - - - Resources\StringsHelper.cs - - - TypeForwards.netcore.cs - - - TypeForwards.Abstractions.cs - - - - - - - - - Resources\ILLink.Substitutions.xml - - - - - Resources\Microsoft.Data.SqlCLient.SqlMetaData.xml - Microsoft.Data.SqlClient.SqlMetaData.xml - - - - - Resources\Strings.resx - Microsoft.Data.SqlClient.Resources.Strings.resources - ResXFileCodeGenerator - Strings.Designer.cs - System - - - - - Resources\%(RecursiveDir)%(Filename)%(Extension) - Microsoft.Data.SqlCLient.Resources.%(Filename).resources - - - - - Resources\Strings.Designer.cs - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj deleted file mode 100644 index 93fc5a5375..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ /dev/null @@ -1,69 +0,0 @@ - - - Microsoft.Data.SqlClient - net462 - false - $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ - $(BinFolder)$(Configuration)\$(AssemblyName)\ref\ - $(OutputPath)\$(AssemblyName).xml - Framework $(BaseProduct) - Debug;Release - - - - - - <_Parameter1>true - - - - - - - - - - - - - - TypeForwards.Abstractions.cs - - - - - - - - - - - - All - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj deleted file mode 100644 index eb14ccc985..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ /dev/null @@ -1,1089 +0,0 @@ - - - Microsoft.Data.SqlClient - net462 - {407890AC-9876-4FEF-A6F1-F36A876BAADE} - - $(OS) - true - AnyCPU - $(BinFolder)$(Configuration).$(OutputPlatform)\ - $(ObjFolder)$(Configuration).$(OutputPlatform)\ - $(BinPath)$(AssemblyName)\netfx\ - $(OutputPath)\$(AssemblyName).xml - $(ObjPath)$(AssemblyName)\netfx\ - Framework $(BaseProduct) - false - false - - - - - - <_Parameter1>UnitTests - - - - - - full - - - - - <_Parameter1>true - - - - - - - - - True - True - False - 1701,1702 - True - True - 4 - 512 - True - True - True - 6.00 - Library - True - True - None - True - True - $(DefineConstants);USEOFFSET;CODE_ANALYSIS_BASELINE;FEATURE_LEGACYSURFACEAREA;FEATURE_UTF32;FEATURE_UTF7;TRACE; - - true - ECMA - - true - $(RawClDefines);SUPPRESS_SECURITY_RULES=1 - - True - False - - - - - - - $(DefineConstants);DEBUG;DBG;_DEBUG;_LOGGING; - Full - False - - - $(DefineConstants);RETAIL; - Pdbonly - True - - - - - - - - - - - $(DefineConstants),_WINDOWS - - - - - - Interop\Common\Sni\SniErrors.cs - - - Interop\Windows\Kernel32\FileTypes.cs - - - Interop\Windows\Kernel32\IoControlCodeAccess.cs - - - Interop\Windows\Kernel32\IoControlTransferType.cs - - - Interop\Windows\Kernel32\Kernel32.cs - - - Interop\Windows\Kernel32\Kernel32Safe.netfx.cs - - - Interop\Windows\NtDll\CreateDisposition.cs - - - Interop\Windows\NtDll\CreateOptions.cs - - - Interop\Windows\NtDll\DesiredAccess.cs - - - Interop\Windows\NtDll\FileFullEaInformation.cs - - - Interop\Windows\NtDll\ImpersonationLevel.cs - - - Interop\Windows\NtDll\IoStatusBlock.cs - - - Interop\Windows\NtDll\NtDll.cs - - - Interop\Windows\NtDll\ObjectAttributeFlags.cs - - - Interop\Windows\NtDll\ObjectAttributes.cs - - - Interop\Windows\NtDll\SecurityQualityOfService.cs - - - Interop\Windows\Sni\AuthProviderInfo.cs - - - Interop\Windows\Sni\ConsumerInfo.cs - - - Interop\Windows\Sni\ConsumerNumber.cs - - - Interop\Windows\Sni\Delegates.cs - - - Interop\Windows\Sni\IoType.cs - - - Interop\Windows\Sni\ISniNativeMethods.cs - - - Interop\Windows\Sni\Prefix.cs - - - Interop\Windows\Sni\Provider.cs - - - Interop\Windows\Sni\QueryType.cs - - - Interop\Windows\Sni\SniClientConsumerInfo.cs - - - Interop\Windows\Sni\SniConsumerInfo.cs - - - Interop\Windows\Sni\SniDnsCacheInfo.cs - - - 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\SniSslProtocols.cs - - - Interop\Windows\Sni\SqlDependencyProcessDispatcherStorage.netfx.cs - - - Interop\Windows\Sni\TransparentNetworkResolutionMode.cs - - - Interop\Windows\SystemErrors.cs - - - Interop\Windows\UnicodeString.cs - - - Microsoft\Data\Common\ActivityCorrelator.cs - - - Microsoft\Data\Common\AdapterUtil.cs - - - Microsoft\Data\Common\AdapterUtil.netfx.cs - - - Microsoft\Data\Common\BitConverterCompatible.cs - - - Microsoft\Data\Common\ConnectionString\AttestationProtocolUtilities.cs - - - Microsoft\Data\Common\ConnectionString\DbConnectionOptions.cs - - - Microsoft\Data\Common\ConnectionString\DbConnectionOptions.Debug.cs - - - Microsoft\Data\Common\ConnectionString\DbConnectionString.netfx.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\ConnectionString\IpAddressPreferenceUtilities.cs - - - Microsoft\Data\Common\ConnectionString\PoolBlockingUtilities.cs - - - Microsoft\Data\SqlClient\ISqlVector.cs - - - Microsoft\Data\Common\MultipartIdentifier.cs - - - Microsoft\Data\Common\NameValuePair.cs - - - Microsoft\Data\DataException.cs - - - Microsoft\Data\OperationAbortedException.cs - - - Microsoft\Data\ProviderBase\DbConnectionClosed.cs - - - Microsoft\Data\ProviderBase\DbConnectionInternal.cs - - - Microsoft\Data\ProviderBase\DbReferenceCollection.cs - - - Microsoft\Data\ProviderBase\FieldNameLookup.cs - - - Microsoft\Data\ProviderBase\TimeoutTimer.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumerator.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.windows.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumeratorUtil.cs - - - Microsoft\Data\Sql\SqlNotificationRequest.cs - - - Microsoft\Data\SqlClient\AAsyncCallContext.cs - - - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationTimeoutRetryHelper.cs - - - Microsoft\Data\SqlClient\AlwaysEncrypted\ColumnMasterKeyMetadata.cs - - - Microsoft\Data\SqlClient\AlwaysEncrypted\EncryptedColumnEncryptionKeyParameters.cs - - - Microsoft\Data\SqlClient\AlwaysEncryptedEnclaveProviderUtils.cs - - - Microsoft\Data\SqlClient\AlwaysEncryptedHelperClasses.cs - - - Microsoft\Data\SqlClient\AlwaysEncryptedKeyConverter.cs - - - Microsoft\Data\SqlClient\ApplicationIntent.cs - - - Microsoft\Data\SqlClient\AssemblyRef.cs - - - Microsoft\Data\SqlClient\AzureAttestationBasedEnclaveProvider.cs - - - Microsoft\Data\SqlClient\ColumnEncryptionKeyInfo.cs - - - Microsoft\Data\SqlClient\Connection\CachedContexts.cs - - - Microsoft\Data\SqlClient\Connection\ServerInfo.cs - - - Microsoft\Data\SqlClient\Connection\SessionData.cs - - - Microsoft\Data\SqlClient\Connection\SessionStateRecord.cs - - - Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs - - - Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs - - - Microsoft\Data\SqlClient\ConnectionPool\ConnectionPoolSlots.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContextKey.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroup.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroupProviderInfo.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolIdentity.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolKey.cs - - - Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolOptions.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\ConnectionPool\SqlConnectionPoolKey.cs - - - Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolProviderInfo.cs - - - Microsoft\Data\SqlClient\ConnectionPool\TransactedConnectionPool.cs - - - Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs - - - Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs - - - Microsoft\Data\SqlClient\Diagnostics\DiagnosticScope.cs - - - Microsoft\Data\SqlClient\Diagnostics\DiagnosticTransactionScope.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackAfter.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackBefore.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackError.cs - - - Microsoft\Data\SqlClient\Diagnostics\SqlDiagnosticListener.cs - - - Microsoft\Data\SqlClient\DisposableTemporaryOnStack.cs - - - Microsoft\Data\SqlClient\EnclaveDelegate.cs - - - Microsoft\Data\SqlClient\EnclaveDelegate.Crypto.cs - - - Microsoft\Data\SqlClient\EnclavePackage.cs - - - Microsoft\Data\SqlClient\EnclaveProviderBase.cs - - - Microsoft\Data\SqlClient\EnclaveSessionCache.cs - - - Microsoft\Data\SqlClient\LocalAppContextSwitches.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\ManagedSni\ResolvedServerSpn.cs - - - Microsoft\Data\SqlClient\NoneAttestationEnclaveProvider.cs - - - Microsoft\Data\SqlClient\OnChangedEventHandler.cs - - - Microsoft\Data\SqlClient\Packet.cs - - - Microsoft\Data\SqlClient\PacketHandle.netfx.cs - - - Microsoft\Data\SqlClient\ParameterPeekAheadValue.cs - - - Microsoft\Data\SqlClient\PoolBlockingPeriod.cs - - - Microsoft\Data\SqlClient\Reliability\AppConfigManager.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryingEventArgs.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryIntervalBaseEnumerator.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryLogic.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryLogicBase.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryLogicBaseProvider.cs - - - - Microsoft\Data\SqlClient\Reliability\Common\SqlRetryLogicProvider.cs - - - Microsoft\Data\SqlClient\Reliability\IAppContextSwitchOverridesSection.cs - - - Microsoft\Data\SqlClient\Reliability\ISqlConfigurableRetryCommandSection.cs - - - Microsoft\Data\SqlClient\Reliability\ISqlConfigurableRetryConnectionSection.cs - - - Microsoft\Data\SqlClient\Reliability\SqlConfigurableRetryFactory.cs - - - Microsoft\Data\SqlClient\Reliability\SqlConfigurableRetryLogicLoader.cs - - - Microsoft\Data\SqlClient\Reliability\SqlConfigurableRetryLogicManager.cs - - - Microsoft\Data\SqlClient\Reliability\SqlRetryIntervalEnumerators.cs - - - Microsoft\Data\SqlClient\RowsCopiedEventArgs.cs - - - Microsoft\Data\SqlClient\RowsCopiedEventHandler.cs - - - Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs - - - Microsoft\Data\SqlClient\Server\ITypedGettersV3.cs - - - Microsoft\Data\SqlClient\Server\ITypedSettersV3.cs - - - Microsoft\Data\SqlClient\Server\MemoryRecordBuffer.cs - - - Microsoft\Data\SqlClient\Server\MetadataUtilsSmi.cs - - - Microsoft\Data\SqlClient\Server\SmiGettersStream.cs - - - Microsoft\Data\SqlClient\Server\SmiMetaData.cs - - - Microsoft\Data\SqlClient\Server\SmiMetaDataProperty.cs - - - Microsoft\Data\SqlClient\Server\SmiSettersStream.cs - - - Microsoft\Data\SqlClient\Server\SmiTypedGetterSetter.cs - - - Microsoft\Data\SqlClient\Server\SmiXetterAccessMap.cs - - - Microsoft\Data\SqlClient\Server\SmiXetterTypeCode.cs - - - Microsoft\Data\SqlClient\Server\SqlDataRecord.cs - - - Microsoft\Data\SqlClient\Server\SqlMetaData.cs - - - Microsoft\Data\SqlClient\Server\SqlNormalizer.cs - - - Microsoft\Data\SqlClient\Server\SqlRecordBuffer.cs - - - Microsoft\Data\SqlClient\Server\TriggerAction.netfx.cs - - - Microsoft\Data\SqlClient\Server\ValueUtilsSmi.cs - - - Microsoft\Data\SqlClient\Server\SqlSer.cs - - - Microsoft\Data\SqlClient\SessionHandle.netfx.cs - - - Microsoft\Data\SqlClient\SignatureVerificationCache.cs - - - Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256Algorithm.cs - - - Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256EncryptionKey.cs - - - Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256Factory.cs - - - Microsoft\Data\SqlClient\SqlAuthenticationParametersBuilder.cs - - - Microsoft\Data\SqlClient\SqlAuthenticationProviderManager.cs - - - Microsoft\Data\SqlClient\SqlBatchCommand.cs - - - Microsoft\Data\SqlClient\SqlBuffer.cs - - - Microsoft\Data\SqlClient\SqlBulkCopy.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyColumnMapping.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyColumnMappingCollection.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyColumnOrderHint.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyColumnOrderHintCollection.cs - - - Microsoft\Data\SqlClient\SqlBulkCopyOptions.cs - - - Microsoft\Data\SqlClient\SqlCachedBuffer.cs - - - Microsoft\Data\SqlClient\SqlClientEncryptionAlgorithm.cs - - - Microsoft\Data\SqlClient\SqlClientEncryptionAlgorithmFactory.cs - - - Microsoft\Data\SqlClient\SqlClientEncryptionAlgorithmFactoryList.cs - - - Microsoft\Data\SqlClient\SqlClientEncryptionType.cs - - - Microsoft\Data\SqlClient\SqlClientEventSource.cs - - - Microsoft\Data\SqlClient\SqlClientFactory.cs - - - Microsoft\Data\SqlClient\SqlClientLogger.cs - - - Microsoft\Data\SqlClient\SqlClientMetaDataCollectionNames.cs - - - Microsoft\Data\SqlClient\SqlClientPermission.netfx.cs - - - Microsoft\Data\SqlClient\SqlClientPermissionAttribute.netfx.cs - - - Microsoft\Data\SqlClient\SqlClientSymmetricKey.cs - - - Microsoft\Data\SqlClient\SqlCollation.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionCertificateStoreProvider.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionCngProvider.windows.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionCspProvider.windows.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionEnclaveProvider.cs - - - Microsoft\Data\SqlClient\SqlColumnEncryptionKeyStoreProvider.cs - - - Microsoft\Data\SqlClient\SqlCommand.cs - - - Microsoft\Data\SqlClient\SqlCommand.Batch.cs - - - Microsoft\Data\SqlClient\SqlCommand.Encryption.cs - - - Microsoft\Data\SqlClient\SqlCommand.NonQuery.cs - - - Microsoft\Data\SqlClient\SqlCommand.Reader.cs - - - Microsoft\Data\SqlClient\SqlCommand.Scalar.cs - - - Microsoft\Data\SqlClient\SqlCommand.Xml.cs - - - Microsoft\Data\SqlClient\SqlCommandBuilder.cs - - - Microsoft\Data\SqlClient\SqlCommandSet.cs - - - Microsoft\Data\SqlClient\SqlConnection.cs - - - Microsoft\Data\SqlClient\SqlConnectionEncryptOption.cs - - - Microsoft\Data\SqlClient\SqlConnectionEncryptOptionConverter.cs - - - Microsoft\Data\SqlClient\SqlConnectionFactory.cs - - - Microsoft\Data\SqlClient\SqlConnectionString.cs - - - Microsoft\Data\SqlClient\SqlConnectionStringBuilder.cs - - - Microsoft\Data\SqlClient\SqlConnectionTimeoutErrorInternal.cs - - - Microsoft\Data\SqlClient\SqlCredential.cs - - - Microsoft\Data\SqlClient\SqlDataAdapter.cs - - - Microsoft\Data\SqlClient\SqlDataReader.cs - - - Microsoft\Data\SqlClient\SqlDbColumn.cs - - - Microsoft\Data\SqlClient\SqlDelegatedTransaction.cs - - - Microsoft\Data\SqlClient\SqlDependency.cs - - - Microsoft\Data\SqlClient\SqlDependencyListener.cs - - - Microsoft\Data\SqlClient\SqlDependencyUtils.cs - - - Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.Crypto.cs - - - Microsoft\Data\SqlClient\SqlEnclaveSession.cs - - - Microsoft\Data\SqlClient\SqlEnums.cs - - - Microsoft\Data\SqlClient\SqlEnvChange.cs - - - Microsoft\Data\SqlClient\SqlError.cs - - - Microsoft\Data\SqlClient\SqlErrorCollection.cs - - - Microsoft\Data\SqlClient\SqlException.cs - - - Microsoft\Data\SqlClient\SQLFallbackDNSCache.cs - - - Microsoft\Data\SqlClient\SqlInfoMessageEvent.cs - - - Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs - - - Microsoft\Data\SqlClient\SqlInternalTransaction.cs - - - Microsoft\Data\SqlClient\SqlMetaDataFactory.cs - - - Microsoft\Data\SqlClient\SqlNotificationEventArgs.cs - - - Microsoft\Data\SqlClient\SqlNotificationInfo.cs - - - Microsoft\Data\SqlClient\SqlNotificationSource.cs - - - Microsoft\Data\SqlClient\SqlNotificationType.cs - - - Microsoft\Data\SqlClient\SqlParameter.cs - - - Microsoft\Data\SqlClient\SqlParameterCollection.cs - - - Microsoft\Data\SqlClient\SqlQueryMetadataCache.cs - - - Microsoft\Data\SqlClient\SqlReferenceCollection.cs - - - Microsoft\Data\SqlClient\SqlRowUpdatedEvent.cs - - - Microsoft\Data\SqlClient\SqlRowUpdatedEventHandler.cs - - - Microsoft\Data\SqlClient\SqlRowUpdatingEvent.cs - - - Microsoft\Data\SqlClient\SqlRowUpdatingEventHandler.cs - - - Microsoft\Data\SqlClient\SqlSecurityUtility.cs - - - Microsoft\Data\SqlClient\SqlSequentialStream.cs - - - Microsoft\Data\SqlClient\SqlSequentialTextReader.cs - - - Microsoft\Data\SqlClient\SqlStatistics.cs - - - Microsoft\Data\SqlClient\SqlStream.cs - - - Microsoft\Data\SqlClient\SqlSymmetricKeyCache.cs - - - Microsoft\Data\SqlClient\SqlTransaction.cs - - - Microsoft\Data\SqlClient\SqlUdtInfo.cs - - - Microsoft\Data\SqlClient\SqlUtil.cs - - - Microsoft\Data\SqlClient\SSPI\NativeSspiContextProvider.windows..cs - - - Microsoft\Data\SqlClient\SSPI\NegotiateSspiContextProvider.cs - - - Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs - - - Microsoft\Data\SqlClient\SSPI\SspiContextProvider.cs - - - Microsoft\Data\SqlClient\SortOrder.cs - - - Microsoft\Data\SqlClient\TdsEnums.cs - - - Microsoft\Data\SqlClient\TdsParameterSetter.cs - - - Microsoft\Data\SqlClient\TdsParser.cs - - - Microsoft\Data\SqlClient\TdsParserSafeHandles.windows.cs - - - Microsoft\Data\SqlClient\TdsParserStateObject.cs - - - Microsoft\Data\SqlClient\TdsParserStateObject.Multiplexer.cs - - - Microsoft\Data\SqlClient\TdsParserStateObjectFactory.windows.cs - - - Microsoft\Data\SqlClient\TdsParserStateObjectNative.windows.cs - - - Microsoft\Data\SqlClient\TdsParserStaticMethods.cs - - - Microsoft\Data\SqlClient\TdsRecordBufferSetter.cs - - - Microsoft\Data\SqlClient\TdsParserHelperClasses.cs - - - Microsoft\Data\SqlClient\TdsParserSessionPool.cs - - - Microsoft\Data\SqlClient\TdsValueSetter.cs - - - Microsoft\Data\SqlClient\TransactionRequest.cs - - - Microsoft\Data\SqlClient\UserAgent.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.windows.cs - - - Microsoft\Data\SqlTypes\SqlJson.cs - - - Microsoft\Data\SqlTypes\SqlVector.cs - - - Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs - - - Resources\ResCategoryAttribute.cs - - - Resources\StringsHelper.cs - - - Resources\ResDescriptionAttribute.cs - - - System\Buffers\ArrayBufferWriter.netfx.cs - - - System\Diagnostics\CodeAnalysis.netfx.cs - - - System\IO\StreamExtensions.netfx.cs - - - System\Runtime\CompilerServices\IsExternalInit.netfx.cs - - - TypeForwards.Abstractions.cs - - - - - - - Resources\Microsoft.Data.SqlClient.SqlMetaData.xml - Microsoft.Data.SqlClient.SqlMetaData.xml - PreserveNewest - - - - - Resources\Strings.resx - Microsoft.Data.SqlClient.Resources.Strings.resources - System - ResXFileCodeGenerator - Strings.Designer.cs - - - - - Resources\%(RecursiveDir)%(Filename)%(Extension) - Microsoft.Data.SqlClient.Resources.%(Filename).resources - - - - - Resources\Strings.Designer.cs - True - True - Strings.resx - - - - - - - - All - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/netfx/tools/targets/GenerateAssemblyInfo.targets b/src/Microsoft.Data.SqlClient/netfx/tools/targets/GenerateAssemblyInfo.targets deleted file mode 100644 index 9c43ce4989..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/tools/targets/GenerateAssemblyInfo.targets +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/netfx/tools/targets/GenerateAssemblyRef.targets b/src/Microsoft.Data.SqlClient/netfx/tools/targets/GenerateAssemblyRef.targets deleted file mode 100644 index 618d30af8b..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/tools/targets/GenerateAssemblyRef.targets +++ /dev/null @@ -1,31 +0,0 @@ - - - - - $(NetFxSource)src\System\Data\SqlClient\ - %20%20%20%20 - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 8be139a0e6..7045eecc65 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -87,12 +87,12 @@ IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" PrivateAssets="all" /> - - - - - - + + + + + + @@ -117,11 +117,11 @@ IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" PrivateAssets="all" /> - - - - - - + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 25a1d53d0d..c28f1cfeb7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -382,11 +382,11 @@ - - - - - + + + + + @@ -415,11 +415,11 @@ - - - - - + + + + + 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 7ad0323852..d481e4cca6 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 @@ -2,11 +2,6 @@ Address Address - {D1392B54-998A-4F27-BC17-4CE149117BCC} - netfx - netcoreapp - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) 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 105dad030d..6066c1c751 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 @@ -2,11 +2,6 @@ Circle Circle - {6C88F00F-9597-43AD-9E5F-9B344DA3B16F} - netfx - netcoreapp - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) 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 6728189c55..67f26c9ae9 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 @@ -2,11 +2,6 @@ Shapes Shapes - {B73A7063-37C3-415D-AD53-BB3DA20ABD6E} - netfx - netcoreapp - $(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 bcc8ad499d..aa1dd19dd2 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 @@ -2,11 +2,6 @@ Utf8String Utf8String - {E0A6BB21-574B-43D9-890D-6E1144F2EE9E} - netfx - netcoreapp - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) 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 239be4d3a2..2da560b6b7 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 @@ -4,35 +4,38 @@ PerformanceTests net8.0;net9.0;net10.0 Debug;Release; - $(ObjFolder)$(Configuration).$(Platform).$(AssemblyName) - $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) Microsoft.Data.SqlClient.PerformanceTests.Program + + - - - + - - - - - - - - + + + + - + + - + + + + + + + + - - + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index 318b44a28d..3c35a1a205 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -64,10 +64,10 @@ IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" PrivateAssets="all" /> - - - - + + + + @@ -85,9 +85,9 @@ IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" PrivateAssets="all" /> - - - - + + + + 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 6d84a14da8..7d95dfe7c0 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 @@ -2,7 +2,7 @@ Exe Microsoft.Data.SqlClient.ExtUtilities.Runner - net10.0 + net10.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 2f802adc75..083cca8a61 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,8 +1,6 @@  - netstandard2.0 - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) + netstandard2.0 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 a90ef98be2..2e1a1b5c81 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 @@ -2,11 +2,8 @@ Microsoft.SqlServer.TDS.EndPoint Microsoft.SqlServer.TDS.EndPoint - {1FF891B4-D3DE-4CCE-887C-CB48F5351A45} - netstandard2.0 + netstandard2.0 false - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) @@ -53,12 +50,8 @@ - - - TDS - - + 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 e2df4cc760..57d3dfadd2 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 @@ -2,17 +2,10 @@ Microsoft.SqlServer.TDS.Servers Microsoft.SqlServer.TDS.Servers - {978063D3-FBB5-4E10-8C45-67C90BE1B931} - netstandard2.0 - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) + netstandard2.0 - - TDS.EndPoint - - - TDS - + + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj index 6b30cbe05c..a4da7c8b92 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj @@ -2,9 +2,6 @@ Microsoft.SqlServer.TDS Microsoft.SqlServer.TDS - {8DC9D1A0-351B-47BC-A90F-B9DA542550E9} - netstandard2.0 - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) + netstandard2.0 diff --git a/tools/GenAPI/Directory.Build.props b/tools/GenAPI/Directory.Build.props deleted file mode 100644 index 730ca3a87b..0000000000 --- a/tools/GenAPI/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/tools/props/Tools.props b/tools/props/Tools.props deleted file mode 100644 index 5190b46806..0000000000 --- a/tools/props/Tools.props +++ /dev/null @@ -1,8 +0,0 @@ - - - - - $(RestoreSources) - - - diff --git a/tools/targets/CompareMdsRefAssemblies.targets b/tools/targets/CompareMdsRefAssemblies.targets index 91458d72b4..1e89a26bf3 100644 --- a/tools/targets/CompareMdsRefAssemblies.targets +++ b/tools/targets/CompareMdsRefAssemblies.targets @@ -10,8 +10,8 @@ Usage: dotnet build build.proj /t:CompareMdsRefAssemblies /p:BaselinePackageVersion=6.1.4 - BaselinePackageVersion is required. Both the legacy ref projects (netcore/ref, - netfx/ref) and the new consolidated ref project (ref/) are built and compared. + BaselinePackageVersion is required. The consolidated ref project (ref/) is + built and compared against the published package's ref assemblies. Depends on src/Directory.Build.props for path properties and build configuration. --> @@ -32,44 +32,22 @@ https://api.nuget.org/v3-flatcontainer/microsoft.data.sqlclient/$(BaselinePackageVersion)/microsoft.data.sqlclient.$(BaselinePackageVersion).nupkg $([MSBuild]::NormalizePath('$(BaselinePackageDir)', 'extracted', '$(BaselinePackageVersion)'))\ - - $([MSBuild]::NormalizePath('$(ManagedSourceCode)', 'ref', 'Microsoft.Data.SqlClient.csproj')) - $([MSBuild]::NormalizePath('$(ManagedSourceCode)', 'ref', 'bin', '$(Configuration)'))\ - - - $([MSBuild]::NormalizePath('$(Artifacts)', 'bin', 'Windows_NT', '$(Configuration)', 'Microsoft.Data.SqlClient', 'ref'))\ - $([MSBuild]::NormalizePath('$(Artifacts)', 'bin', 'AnyOS', '$(Configuration)', 'Microsoft.Data.SqlClient', 'ref'))\ - - - $([MSBuild]::NormalizePath('$(NetFxSource)', 'ref', 'Microsoft.Data.SqlClient.csproj')) - $([MSBuild]::NormalizePath('$(NetCoreSource)', 'ref', 'Microsoft.Data.SqlClient.csproj')) + + $([MSBuild]::NormalizePath('$(RepoRoot)', 'src', 'Microsoft.Data.SqlClient', 'ref', 'Microsoft.Data.SqlClient.csproj')) + $([MSBuild]::NormalizePath('$(RepoRoot)', 'artifacts', 'Microsoft.Data.SqlClient.ref', '$(ReferenceType)-$(Configuration)'))\ - - <_RefTfm Include="net462"> - $(LegacyNetFxRefDir) - - <_RefTfm Include="net8.0"> - $(LegacyNetCoreRefDir) - - <_RefTfm Include="net9.0"> - $(LegacyNetCoreRefDir) - - <_RefTfm Include="netstandard2.0"> - $(LegacyNetCoreRefDir) - + <_RefTfm Include="net462" /> + <_RefTfm Include="net8.0" /> + <_RefTfm Include="net9.0" /> + <_RefTfm Include="netstandard2.0" /> - - - + + @@ -120,49 +98,26 @@ - - - - - - - - - + - - - + + - - - - - - - - + Projects="$(RefProjectPath)"/> + DependsOnTargets="_DownloadBaselinePackage;_RestoreTools;_BuildRefProject"> - - - - - - - - - + Text="--- Comparing %(_RefTfm.Identity) ref vs baseline $(BaselinePackageVersion) ---" /> diff --git a/tools/targets/GenerateMdsPackage.targets b/tools/targets/GenerateMdsPackage.targets deleted file mode 100644 index b4bf4a0d5c..0000000000 --- a/tools/targets/GenerateMdsPackage.targets +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - diff --git a/tools/targets/NotSupported.targets b/tools/targets/NotSupported.targets deleted file mode 100644 index 10900e76be..0000000000 --- a/tools/targets/NotSupported.targets +++ /dev/null @@ -1,65 +0,0 @@ - - - - - true - $(IntermediateOutputPath)\$(TargetFramework)\$(AssemblyName).notsupported.cs - $(CoreCompileDependsOn);GenerateNotSupportedSource - - false - false - $(NoWarn);CS0618 - - - - - True - - - - - - - - <_referencePathDirectoriesWithDuplicates Include="@(ReferencePath->'%(RootDir)%(Directory)'->TrimEnd('\'))" /> - - <_referencePathDirectories Include="%(_referencePathDirectoriesWithDuplicates.Identity)" /> - - - - - - $(RepoRoot)tools/GenAPI/Microsoft.DotNet.GenAPI/bin/$(Configuration)/net10.0/ - - "$(DotnetPath)dotnet" $(GenApiPath)Microsoft.DotNet.GenAPI.dll - "%(ResolvedMatchingContract.Identity)" - -l:"@(_referencePathDirectories)" - -o:"$(NotSupportedSourceFile)" - - - $(GenApiCommand) - -t:"$(GeneratePlatformNotSupportedAssemblyMessage)" - - - $([System.Text.RegularExpressions.Regex]::Replace($(GenApiCommand), "\s+", " ")) - - - - - - - - - - - - diff --git a/tools/targets/ResolveContract.targets b/tools/targets/ResolveContract.targets deleted file mode 100644 index 98d3582716..0000000000 --- a/tools/targets/ResolveContract.targets +++ /dev/null @@ -1,29 +0,0 @@ - - - - $(NetCoreSource)ref/$(MSBuildProjectName).csproj - true - $(ContractOutputPath)/$(MSBuildProjectName).dll - - - - - - - false - ResolvedMatchingContract - - - - - - - - - - - - - - - diff --git a/tools/targets/TrimDocsForIntelliSense.targets b/tools/targets/TrimDocsForIntelliSense.targets deleted file mode 100644 index 05feb26739..0000000000 --- a/tools/targets/TrimDocsForIntelliSense.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - From 2b9665354be423d9d10997ff313bbc2c050f159e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:54:55 +0100 Subject: [PATCH 038/298] Fix | Enable SqlBulkCopy to target tables in Azure Synapse Analytics dedicated SQL pools (#4176) --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 31 +++++++++++++++++-- .../SQL/SqlBulkCopyTest/CopyAllFromReader.cs | 4 +-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index cc90802f08..fdda86b98e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -501,20 +501,47 @@ private string CreateInitialQuery() // interpolated directly into the SQL string because SQL Server does not allow // identifiers (database/schema/table names) to be passed as parameters. Both // values are escaped via SqlServerEscapeHelper before interpolation. + // + // SqlBulkCopy must remain compatible with Azure Synapse Analytics dedicated SQL pools + // and with SQL Server 2016. Azure Synapse Analytics does not allow variables assigned + // in the SELECT statement to appear in an expression, which prevents the consistent use of + // SELECT @Column_Names = COALESCE(@Column_Names + ', ', '') + QUOTENAME([name]) + // The alternative is to use STRING_AGG, but this was only introduced in SQL Server + // 2017. To meet both criteria, we review the EngineEdition server property. A value + // of 6 indicates that the bulk copy is going to run against Azure Synapse Analytics; + // we use STRING_AGG in that case and the COALESCE method otherwise. + // + // See: https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql return $""" SELECT @@TRANCOUNT; DECLARE @Object_ID INT = OBJECT_ID('{escapedObjectName}'); +DECLARE @Column_Name_Query_SELECT NVARCHAR(MAX); +DECLARE @Column_Name_Query_FILTER NVARCHAR(MAX); +DECLARE @Column_Name_Query_SORT NVARCHAR(MAX); DECLARE @Column_Name_Query NVARCHAR(MAX); DECLARE @Column_Names NVARCHAR(MAX) = NULL; + +IF CAST(SERVERPROPERTY('EngineEdition') AS INT) = 6 +BEGIN + SET @Column_Name_Query_SELECT = N'SELECT @Column_Names = STRING_AGG(CAST(QUOTENAME([name]) AS NVARCHAR(MAX)), '', '') WITHIN GROUP (ORDER BY [column_id] ASC)'; + SET @Column_Name_Query_SORT = N''; +END +ELSE +BEGIN + SET @Column_Name_Query_SELECT = 'SELECT @Column_Names = COALESCE(@Column_Names + '', '', '''') + QUOTENAME([name])'; + SET @Column_Name_Query_SORT = N'ORDER BY [column_id] ASC'; +END + IF EXISTS (SELECT TOP 1 * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.all_columns') AND [name] = 'graph_type') BEGIN - SET @Column_Name_Query = N'SELECT @Column_Names = COALESCE(@Column_Names + '', '', '''') + QUOTENAME([name]) FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) NOT IN (1, 3, 4, 6, 7) ORDER BY [column_id] ASC;'; + SET @Column_Name_Query_FILTER = N'WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) NOT IN (1, 3, 4, 6, 7)'; END ELSE BEGIN - SET @Column_Name_Query = N'SELECT @Column_Names = COALESCE(@Column_Names + '', '', '''') + QUOTENAME([name]) FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID ORDER BY [column_id] ASC;'; + SET @Column_Name_Query_FILTER = N'WHERE [object_id] = @Object_ID'; END +SET @Column_Name_Query = @Column_Name_Query_SELECT + ' FROM {CatalogName}.[sys].[all_columns] ' + @Column_Name_Query_FILTER + ' ' + @Column_Name_Query_SORT + ';' EXEC sp_executesql @Column_Name_Query, N'@Object_ID INT, @Column_Names NVARCHAR(MAX) OUTPUT', @Object_ID = @Object_ID, @Column_Names = @Column_Names OUTPUT; SELECT @Column_Names = COALESCE(@Column_Names, '*'); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs index f55e512a35..a0ccf69301 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs @@ -61,9 +61,9 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) DataTestUtility.AssertEqualsWithDescription((long)3, stats["BuffersReceived"], "Unexpected BuffersReceived value."); DataTestUtility.AssertEqualsWithDescription((long)3, stats["BuffersSent"], "Unexpected BuffersSent value."); DataTestUtility.AssertEqualsWithDescription((long)0, stats["IduCount"], "Unexpected IduCount value."); - DataTestUtility.AssertEqualsWithDescription((long)8, stats["SelectCount"], "Unexpected SelectCount value."); + DataTestUtility.AssertEqualsWithDescription((long)11, stats["SelectCount"], "Unexpected SelectCount value."); DataTestUtility.AssertEqualsWithDescription((long)3, stats["ServerRoundtrips"], "Unexpected ServerRoundtrips value."); - DataTestUtility.AssertEqualsWithDescription((long)11, stats["SelectRows"], "Unexpected SelectRows value."); + DataTestUtility.AssertEqualsWithDescription((long)14, stats["SelectRows"], "Unexpected SelectRows value."); DataTestUtility.AssertEqualsWithDescription((long)2, stats["SumResultSets"], "Unexpected SumResultSets value."); DataTestUtility.AssertEqualsWithDescription((long)0, stats["Transactions"], "Unexpected Transactions value."); } From 5b3587d7d459aed1a088c2019f415ec5b945d510 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 13 Apr 2026 08:56:38 -0300 Subject: [PATCH 039/298] Quarantine flaky tests (#4170) --- .../tests/FunctionalTests/LocalizationTest.cs | 1 + .../tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs | 1 + .../ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs | 1 + .../tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs | 1 + .../tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs | 1 + .../tests/ManualTests/TracingTests/MetricsTest.cs | 1 + .../SimulatedServerTests/ConnectionFailoverTests.cs | 5 +++++ .../UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs | 1 + 8 files changed, 12 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalizationTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalizationTest.cs index d78d801fde..250c009b27 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalizationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalizationTest.cs @@ -40,6 +40,7 @@ public class LocalizationTest [InlineData("ru-RU")] [InlineData("zh-Hans")] [InlineData("zh-Hant")] + [Trait("Category", "flaky")] public void Localization_Tests(string culture) { string localized = GetLocalizedErrorMessage(culture); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs index 834157aeec..bc514dce92 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs @@ -28,6 +28,7 @@ public enum AsyncAPI // Synapse: WAITFOR DELAY not supported [Parse error at line: 1, column: 1: Incorrect syntax near 'WAITFOR'.] [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [ClassData(typeof(AsyncTimeoutTestVariations))] + [Trait("Category", "flaky")] public static void TestDelayedAsyncTimeout(AsyncAPI api, string commonObj, int delayPeriod, bool marsEnabled) => RunTest(api, commonObj, delayPeriod, marsEnabled); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs index 478f78a5b9..10c3939774 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs @@ -36,6 +36,7 @@ public static class ConnectionPoolTest /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [ClassData(typeof(ConnectionPoolConnectionStringProvider))] + [Trait("Category", "flaky")] public static void BasicConnectionPoolingTest(string connectionString) { SqlConnection.ClearAllPools(); 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 5cf8b1e4b4..b21626aaad 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -944,6 +944,7 @@ public _Row(int id, Guid documentIdentificationId, string name, string value) } } + [Trait("Category", "flaky")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static async Task CanReadAwkwardDataLengths() { 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 2de13c98ac..118f709d89 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -180,6 +180,7 @@ public static void GetXmlReader_ReturnsXmlReaderCorrectly(string connectionStrin [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(ConnectionStrings))] + [Trait("Category", "flaky")] public static void ReadStream_ReadsStreamDataCorrectly(string connectionString) { ReadStream(connectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs index fbcb27bb8c..af10a5824e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs @@ -278,6 +278,7 @@ public void ReclaimedConnectionsCounter_Functional() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [Trait("Category", "flaky")] public void ConnectionPoolGroupsCounter_Functional() { SqlConnection.ClearAllPools(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index 7c8e67a09a..d3a0bcd07b 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -15,6 +15,7 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests public class ConnectionFailoverTests { //TODO parameterize for transient errors + [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -74,6 +75,7 @@ public void TransientFault_NoFailover_DoesNotClearPool(uint errorCode) Assert.Equal(0, failoverServer.PreLoginCount); } + [Trait("Category", "flaky")] [Fact] public void NetworkError_TriggersFailover_ClearsPool() { @@ -239,6 +241,7 @@ public void NetworkDelay_ShouldConnectToPrimary() } [Fact] + [Trait("Category", "flaky")] public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFailoverPartner() { using TdsServer failoverServer = new( @@ -286,6 +289,7 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa } [Fact] + [Trait("Category", "flaky")] public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFailoverPartner() { using TdsServer failoverServer = new( @@ -430,6 +434,7 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) [InlineData(40613)] [InlineData(42108)] [InlineData(42109)] + [Trait("Category", "flaky")] public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint errorCode) { // Arrange diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs index b763f2b55c..0eff08139f 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs @@ -156,6 +156,7 @@ public void NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed(bool multiS } [Fact] + [Trait("Category", "flaky")] public void NetworkTimeoutAtRoutedLocation_RetryDisabled_ShouldFail() { // Arrange From 8ddc464d867ef09abf4d926efe7294229be1d50f Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:42:24 -0300 Subject: [PATCH 040/298] Add SQL Server readiness checks to Windows pipeline setup (#4172) --- .../steps/configure-sql-server-win-step.yml | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) 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 749c30a79a..50925d00d5 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 @@ -162,10 +162,28 @@ steps: #Change the access level for FileStream for SQLServer Set-ExecutionPolicy Unrestricted Import-Module "sqlps" - Invoke-Sqlcmd -ServerInstance "$machineName" @" - EXEC sp_configure filestream_access_level, 2; - RECONFIGURE; + + # Retry loop: SQL Server may be temporarily unavailable after enabling FileStream via WMI. + # Worst-case budget: 10 attempts x (5s connection timeout + 5s sleep) = ~100s + $tries = 0 + while ($true) { + $tries++ + try { + Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @" + EXEC sp_configure filestream_access_level, 2; + RECONFIGURE; "@ + Write-Host "FileStream access level configured successfully." + break + } catch { + if ($tries -ge 10) { + Write-Host "##[error]Failed to configure FileStream access level after $tries tries." + throw + } + Write-Host "Failed to connect to SQL Server (attempt $tries/10). Retrying in 5 seconds..." + Start-Sleep -Seconds 5 + } + } displayName: 'Enable FileStream [Win]' env: SQL_USER: ${{parameters.user }} @@ -250,6 +268,31 @@ steps: Restart-Service -Name "$serviceName" -Force Restart-Service -Name MSSQLSERVER* -Force + # Wait for SQL Server to be ready to accept connections after restart. + # Worst-case budget: 20 attempts x (5s connection timeout + 3s sleep) = ~160s + $machineName = $env:COMPUTERNAME + if ("${{parameters.instanceName }}" -ne "MSSQLSERVER") { + $machineName += "\${{parameters.instanceName }}" + } + + Import-Module "sqlps" + $tries = 0 + while ($true) { + $tries++ + try { + Invoke-Sqlcmd -ServerInstance "$machineName" -Query "SELECT @@VERSION" -ConnectionTimeout 5 + Write-Host "SQL Server is ready after restart (attempt $tries)." + break + } catch { + if ($tries -ge 20) { + Write-Host "##[error]SQL Server did not become ready after $tries attempts." + throw + } + Write-Host "Waiting for SQL Server to start (attempt $tries/20)..." + Start-Sleep -Seconds 3 + } + } + displayName: 'Restart SQL Server [Win]' - powershell: | From 32610dafdfb22bc82df504e51abeb14b3f4cb3e0 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:43:07 -0300 Subject: [PATCH 041/298] Use Rosetta 2 and improve robustness for macOS SQL Server CI setup (#4173) --- .../steps/configure-sql-server-macos-step.yml | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml index c1b788717d..a989bf19bf 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml @@ -29,36 +29,42 @@ steps: export PS4='+ [$(date "+%Y-%m-%d %H:%M:%S")] ' set -x - # Install Docker and SQLCMD tools. + # Install Docker CLI (not Desktop — Colima provides the daemon) and SQLCMD tools. brew install colima - brew install --cask docker + brew install 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 + + # Start Colima with Virtualization.framework for x86_64 binary translation + # on Apple Silicon. Rosetta/binfmt emulation is enabled by default in + # Colima >= 0.8 when using --vm-type vz, which is dramatically faster than + # --arch x86_64 (full QEMU VM emulation). + # Requires macOS >= 13 (Ventura). + colima start --vm-type vz --cpu 4 --memory 4 docker --version - docker pull mcr.microsoft.com/mssql/server:2025-latest + docker pull --platform linux/amd64 mcr.microsoft.com/mssql/server:2025-latest # Password for the SA user (required) MSSQL_SA_PW="${{ parameters.saPassword }}" - 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:2025-latest + docker run --platform linux/amd64 -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:2025-latest - sleep 5 + sleep 10 docker ps -a # Connect to the SQL Server container and get its version. # - # It can take a while for the docker container to start listening and be - # ready for connections, so we will wait for up to 2 minutes, checking every - # 3 seconds. + # With Rosetta 2 emulation, SQL Server starts much faster than under full + # QEMU emulation, but it can still take a minute or two. We allow up to + # 6 minutes (72 attempts × 5 seconds) as a generous upper bound. - # Wait 3 seconds between attempts. - delay=3 + # Wait 5 seconds between attempts. + delay=5 - # Try up to 40 times (2 minutes) to connect. - maxAttempts=40 + # Try up to 72 times (~6 minutes) to connect. + maxAttempts=72 # Attempt counter. attempt=1 @@ -71,7 +77,8 @@ steps: echo "Waiting for SQL Server to start (attempt #$attempt of $maxAttempts)..." - sqlcmd -S 127.0.0.1 -No -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" >> $SQLCMD_ERRORS 2>&1 + # -C trusts the self-signed certificate inside the container. + sqlcmd -S 127.0.0.1 -No -C -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" >> $SQLCMD_ERRORS 2>&1 # If the command was successful, then the SQL Server is ready. if [ $? -eq 0 ]; then @@ -79,6 +86,16 @@ steps: break fi + # Verify the container is still running; no point retrying if it crashed. + if ! docker ps --filter "name=^/sql1$" --filter "status=running" --format '{{.Names}}' | grep -Fxq 'sql1'; then + echo "ERROR: sql1 container is no longer running." + docker ps -a --filter "name=^/sql1$" + echo "--- Container logs ---" + docker logs sql1 2>&1 | tail -50 + rm -f $SQLCMD_ERRORS + exit 1 + fi + # Increment the attempt counter. ((attempt++)) @@ -91,8 +108,13 @@ steps: if [ $ready -eq 0 ] then # No, so report the error(s) and exit. - echo Cannot connect to SQL Server; installation aborted; errors were: + echo "Cannot connect to SQL Server after $maxAttempts attempts; installation aborted." + echo "--- sqlcmd errors ---" cat $SQLCMD_ERRORS + echo "--- Container status ---" + docker ps -a --filter "name=^/sql1$" + echo "--- Container logs (last 80 lines) ---" + docker logs sql1 2>&1 | tail -80 rm -f $SQLCMD_ERRORS exit 1 fi @@ -101,18 +123,18 @@ steps: 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 + sqlcmd -S 0.0.0.0 -No -C -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 + sqlcmd -S 127.0.0.1 -No -C -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 + sqlcmd -S ::1 -No -C -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 + sqlcmd -S localhost -No -C -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 + sqlcmd -No -C -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" -l 2 echo "Configuring Dedicated Administer Connections to allow remote connections..." - sqlcmd -S 127.0.0.1 -No -U sa -P "$MSSQL_SA_PW" -Q "sp_configure 'remote admin connections', 1; RECONFIGURE;" + sqlcmd -S 127.0.0.1 -No -C -U sa -P "$MSSQL_SA_PW" -Q "sp_configure 'remote admin connections', 1; RECONFIGURE;" if [ $? = 1 ] then echo "Error configuring DAC for remote access." From f5c41ee33478f60cdab1d385b10d8f7ebc58122d Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:43:43 -0300 Subject: [PATCH 042/298] Add retry and verification to ARM64 dotnet-install script (#4178) --- eng/pipelines/steps/install-dotnet-arm64.ps1 | 140 +++++++++++++++++-- 1 file changed, 129 insertions(+), 11 deletions(-) diff --git a/eng/pipelines/steps/install-dotnet-arm64.ps1 b/eng/pipelines/steps/install-dotnet-arm64.ps1 index b8057a703a..95d580543b 100644 --- a/eng/pipelines/steps/install-dotnet-arm64.ps1 +++ b/eng/pipelines/steps/install-dotnet-arm64.ps1 @@ -4,15 +4,15 @@ .DESCRIPTION Special handling is required for ARM64 due to a bug in UseDotNet@2: - + [BUG]: UseDotNet@2 task installs x86 build https://github.com/microsoft/azure-pipelines-tasks/issues/20300 - + The downloaded dotnet-install.ps1 script is kept in the $InstallDir to avoid downloading it multiple times during the pipeline job. - + The following environment variables are set for subsequent steps in the pipeline: - + DOTNET_ROOT: Set to $InstallDir. PATH: $DOTNET_ROOT is prepended to the PATH environment variable. @@ -58,6 +58,72 @@ param # Stop on all errors. $ErrorActionPreference = 'Stop' +# Maximum number of retry attempts for transient install failures (e.g. +# corrupt downloads, network timeouts). +$maxAttempts = 3 +$retryDelaySec = 10 + +#------------------------------------------------------------------------------ +# Invoke dotnet-install.ps1 with retry logic. On each attempt the script is +# called with the supplied $Params. If a non-zero exit code is returned, or +# if the optional $Verify script-block throws, the attempt is considered failed +# and will be retried after a short delay. + +function Invoke-DotNetInstall +{ + param + ( + [Parameter(Mandatory)] + [string]$Description, + + [Parameter(Mandatory)] + [hashtable]$Params, + + [scriptblock]$Verify = $null + ) + + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) + { + try + { + Write-Host "$Description (attempt $attempt of $maxAttempts)" + + $global:LASTEXITCODE = 0 + & "$InstallDir/dotnet-install.ps1" -Verbose:$Debug -DryRun:$DryRun @Params + $installSucceeded = $? + + if (-not $installSucceeded) + { + throw "dotnet-install.ps1 failed." + } + + if ($global:LASTEXITCODE -ne 0) + { + throw "dotnet-install.ps1 failed with exit code $global:LASTEXITCODE" + } + + if ($Verify) + { + & $Verify + } + + return + } + catch + { + Write-Warning "Attempt $attempt failed: $_" + + if ($attempt -ge $maxAttempts) + { + throw "$Description failed after $maxAttempts attempts. Last error: $_" + } + + Write-Host "Retrying in $retryDelaySec seconds..." + Start-Sleep -Seconds $retryDelaySec + } + } +} + #------------------------------------------------------------------------------ # Emit our command-line arguments. @@ -80,7 +146,7 @@ if (-not (Test-Path -Path "$InstallDir/dotnet-install.ps1" -PathType Leaf)) } Write-Host "Downloading dotnet-install.ps1..." - + $params = @{ Uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1" @@ -113,8 +179,6 @@ if ($Debug) #------------------------------------------------------------------------------ # Install the SDK. -Write-Host "Installing .NET SDK version: $sdkVersion" - $installParams = @{ Architecture = "arm64" @@ -128,15 +192,40 @@ if ($Debug) Write-Host ($installParams | ConvertTo-Json -Depth 1) } -& "$InstallDir/dotnet-install.ps1" -Verbose:$Debug -DryRun:$DryRun @installParams +# Verify the SDK was actually installed. dotnet-install.ps1 can silently fail +# with exit code 0 when the package download is corrupt or the size cannot be +# measured. +$verifySdk = + if (-not $DryRun) + { + { + $dotnetExe = Join-Path $InstallDir "dotnet" + $installedSdks = & $dotnetExe --list-sdks 2>&1 + $installedSdksText = [string]::Join("`n", @($installedSdks)) + + Write-Host "Installed SDKs:`n$installedSdksText" + + $sdkPattern = "(?m)^$([regex]::Escape($sdkVersion))\s+\[" + + if (-not [regex]::IsMatch($installedSdksText, $sdkPattern)) + { + throw "SDK $sdkVersion is not present after installation." + } + + Write-Host "Verified SDK $sdkVersion is installed." + } + } + +Invoke-DotNetInstall ` + -Description "Installing .NET SDK version: $sdkVersion" ` + -Params $installParams ` + -Verify $verifySdk #------------------------------------------------------------------------------ # Install the Runtimes, if any. foreach ($channel in $Runtimes) { - Write-Host "Installing .NET Runtime GA channel: $channel" - $installParams = @{ Architecture = "arm64" @@ -152,7 +241,36 @@ foreach ($channel in $Runtimes) Write-Host ($installParams | ConvertTo-Json -Depth 1) } - & "$InstallDir/dotnet-install.ps1" -Verbose:$Debug -DryRun:$DryRun @installParams + # Verify the runtime was actually installed. Use the same guard against + # silent corruption that we use for the SDK. + $verifyRuntime = + if (-not $DryRun) + { + # Capture $channel in a local variable so the script-block closure + # binds to the current iteration value. + $ch = $channel + { + $dotnetExe = Join-Path $InstallDir "dotnet" + $installedRuntimes = & $dotnetExe --list-runtimes 2>&1 + $installedRuntimesText = [string]::Join("`n", @($installedRuntimes)) + + Write-Host "Installed runtimes:`n$installedRuntimesText" + + $runtimePattern = "Microsoft\.NETCore\.App $([regex]::Escape($ch))\." + + if (-not [regex]::IsMatch($installedRuntimesText, $runtimePattern)) + { + throw "Runtime $ch is not present after installation." + } + + Write-Host "Verified runtime $ch is installed." + } + } + + Invoke-DotNetInstall ` + -Description "Installing .NET Runtime GA channel: $channel" ` + -Params $installParams ` + -Verify $verifyRuntime } #------------------------------------------------------------------------------ From b1b93a8947df8d0d9eadf79f7dcf5a22fc533492 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 16 Apr 2026 19:41:35 +0100 Subject: [PATCH 043/298] Cleanup | Preparation for OS-independent builds (#3844) * Implement early infrastructure for OS-independent builds. Also clean up the netfx-only s_isWindowsNT variable - this was originally used to distinguish between Windows 9x and Windows NT! * SqlFileStream We only throw a PlatformNotSupportedException in the constructor; if this throws, there's no way execution can reach a property or method. * SqlColumnEncryptionCngProvider * SqlColumnEncryptionCspProvider * Manually inline ThrowOnNonWindowsPlatform This is necessary because when an application using SqlClient is being published, the IL trimmer doesn't see that the throw helper throws an exception, and thus doesn't eliminate the code paths which call Windows-specific APIs. As a result, the Windows-specific API paths aren't removed from executables published for a Linux platform. * Correct preprocessor directive indentation --- .../src/Interop/Windows/Kernel32/FileTypes.cs | 4 - .../Windows/Kernel32/IoControlCodeAccess.cs | 4 - .../Windows/Kernel32/IoControlTransferType.cs | 4 - .../src/Interop/Windows/Kernel32/Kernel32.cs | 16 +--- .../Windows/NtDll/CreateDisposition.cs | 4 - .../Interop/Windows/NtDll/CreateOptions.cs | 4 - .../Interop/Windows/NtDll/DesiredAccess.cs | 4 - .../Windows/NtDll/FileFullEaInformation.cs | 4 - .../Windows/NtDll/ImpersonationLevel.cs | 4 - .../Interop/Windows/NtDll/IoStatusBlock.cs | 4 - .../src/Interop/Windows/NtDll/NtDll.cs | 4 - .../Windows/NtDll/ObjectAttributeFlags.cs | 4 - .../Interop/Windows/NtDll/ObjectAttributes.cs | 4 - .../Windows/NtDll/SecurityQualityOfService.cs | 4 - .../src/Interop/Windows/SystemErrors.cs | 4 - .../src/Interop/Windows/UnicodeString.cs | 4 - .../src/Microsoft/Data/Common/AdapterUtil.cs | 39 +++++--- .../Data/Common/AdapterUtil.netfx.cs | 4 - .../ConnectionPool/DbConnectionPoolGroup.cs | 7 -- ...s.cs => SqlColumnEncryptionCngProvider.cs} | 23 +++-- ...olumnEncryptionCngProvider.netcore.unix.cs | 56 ----------- ...s.cs => SqlColumnEncryptionCspProvider.cs} | 23 +++-- ...olumnEncryptionCspProvider.netcore.unix.cs | 56 ----------- ...FileStream.windows.cs => SqlFileStream.cs} | 9 +- .../SqlTypes/SqlFileStream.netcore.unix.cs | 92 ------------------- 25 files changed, 68 insertions(+), 317 deletions(-) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{SqlColumnEncryptionCngProvider.windows.cs => SqlColumnEncryptionCngProvider.cs} (94%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.unix.cs rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{SqlColumnEncryptionCspProvider.windows.cs => SqlColumnEncryptionCspProvider.cs} (95%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.unix.cs rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/{SqlFileStream.windows.cs => SqlFileStream.cs} (99%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.netcore.unix.cs diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/FileTypes.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/FileTypes.cs index 8dbbd6fbde..59b1c67dcf 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/FileTypes.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/FileTypes.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Kernel32 { internal class FileTypes @@ -14,5 +12,3 @@ internal class FileTypes internal const int FILE_TYPE_PIPE = 0x0003; } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/IoControlCodeAccess.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/IoControlCodeAccess.cs index 3a32378bba..2e20b2aa57 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/IoControlCodeAccess.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/IoControlCodeAccess.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; namespace Interop.Windows.Kernel32 @@ -33,5 +31,3 @@ internal enum IoControlCodeAccess FILE_WRITE_DATA = 0x02 } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/IoControlTransferType.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/IoControlTransferType.cs index 2573aa8bc0..9feea884ce 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/IoControlTransferType.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/IoControlTransferType.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Kernel32 { /// @@ -37,5 +35,3 @@ internal enum IoControlTransferType METHOD_NEITHER } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32.cs index a5b854f565..020726b104 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32.cs @@ -2,15 +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 _WINDOWS - using System; using System.Runtime.InteropServices; using System.Text; using Microsoft.Data.Common; using Microsoft.Win32.SafeHandles; -#if !NETFRAMEWORK +#if !NETFRAMEWORK && _WINDOWS using Interop.Windows.Handles; #endif @@ -21,7 +19,7 @@ internal static class Kernel32 internal const ushort FILE_DEVICE_FILE_SYSTEM = 0x0009; internal const uint SEM_FAILCRITICALERRORS = 1; - #if !NETFRAMEWORK + #if _WINDOWS && !NETFRAMEWORK internal const int LOAD_LIBRARY_AS_DATAFILE = 0x00000002; internal const int LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800; #endif @@ -56,7 +54,7 @@ internal static extern bool DeviceIoControl( out uint cbBytesReturned, IntPtr overlapped); - #if NET + #if NET && _WINDOWS [DllImport(DllName, ExactSpelling = true, SetLastError = true)] public static extern bool FreeLibrary([In] IntPtr hModule); #endif @@ -73,17 +71,13 @@ internal static extern int GetFullPathName( IntPtr lpFilePartOrNull); #endif - #if NET + #if NET && _WINDOWS [DllImport(DllName, CharSet = CharSet.Ansi, BestFitMapping = false)] public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); - #endif - #if NET [DllImport(DllName, CharSet = CharSet.Ansi, BestFitMapping = false)] public static extern IntPtr GetProcAddress(SafeLibraryHandle hModule, string lpProcName); - #endif - #if NET [DllImport(DllName, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] public static extern SafeLibraryHandle LoadLibraryExW([In] string lpwLibFileName, [In] IntPtr hFile, [In] uint dwFlags); #endif @@ -92,5 +86,3 @@ internal static extern int GetFullPathName( internal static extern bool SetThreadErrorMode(uint dwNewMode, out uint lpOldMode); } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/CreateDisposition.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/CreateDisposition.cs index 7502a94e64..8f03025bee 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/CreateDisposition.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/CreateDisposition.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.NtDll { /// @@ -56,5 +54,3 @@ internal enum CreateDisposition : uint FILE_OVERWRITE_IF = 5 } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/CreateOptions.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/CreateOptions.cs index 5c169ec5a0..39677ab3d5 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/CreateOptions.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/CreateOptions.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; namespace Interop.Windows.NtDll @@ -198,5 +196,3 @@ internal enum CreateOptions : uint // FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000 } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/DesiredAccess.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/DesiredAccess.cs index acf7f9ee0f..f15fbcf0dc 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/DesiredAccess.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/DesiredAccess.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; namespace Interop.Windows.NtDll @@ -190,5 +188,3 @@ internal enum DesiredAccess : uint FILE_GENERIC_EXECUTE = 0x20000000 // GENERIC_EXECUTE } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/FileFullEaInformation.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/FileFullEaInformation.cs index 5e545ce131..76322ce604 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/FileFullEaInformation.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/FileFullEaInformation.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System.Runtime.InteropServices; namespace Interop.Windows.NtDll @@ -37,5 +35,3 @@ internal struct FileFullEaInformation } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ImpersonationLevel.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ImpersonationLevel.cs index 70c606e95b..5386a4094b 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ImpersonationLevel.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ImpersonationLevel.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.NtDll { /// @@ -41,5 +39,3 @@ internal enum ImpersonationLevel SecurityDelegation = 3, } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/IoStatusBlock.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/IoStatusBlock.cs index 0e173186b8..7018ef3abd 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/IoStatusBlock.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/IoStatusBlock.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.Runtime.InteropServices; @@ -44,5 +42,3 @@ public struct IoStatus } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/NtDll.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/NtDll.cs index 20b5e314db..8dddda19d3 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/NtDll.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/NtDll.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.IO; using System.Runtime.InteropServices; @@ -120,5 +118,3 @@ private static extern unsafe int NtCreateFile( uint EaLength); } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ObjectAttributeFlags.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ObjectAttributeFlags.cs index 0f70ffa6cf..31dfd80ead 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ObjectAttributeFlags.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ObjectAttributeFlags.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; namespace Interop.Windows.NtDll @@ -54,5 +52,3 @@ internal enum ObjectAttributeFlags : uint // OBJ_VALID_ATTRIBUTES = 0x000001F2 } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ObjectAttributes.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ObjectAttributes.cs index 3093763429..f3198f923e 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ObjectAttributes.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/ObjectAttributes.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; namespace Interop.Windows.NtDll @@ -60,5 +58,3 @@ public ObjectAttributes( } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/SecurityQualityOfService.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/SecurityQualityOfService.cs index 0cfa77a2d5..7f268255d9 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/SecurityQualityOfService.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/NtDll/SecurityQualityOfService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System.Runtime.InteropServices; namespace Interop.Windows.NtDll @@ -50,5 +48,3 @@ public SecurityQualityOfService( public bool IsEffectiveOnly { get; set; } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/SystemErrors.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/SystemErrors.cs index 2265ee1427..40819ef4d9 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/SystemErrors.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/SystemErrors.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows { // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx @@ -22,5 +20,3 @@ internal class SystemErrors internal const int ERROR_MR_MID_NOT_FOUND = 317; } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/UnicodeString.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/UnicodeString.cs index 07ff56dbb4..7fc87decbf 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/UnicodeString.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/UnicodeString.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System.Runtime.InteropServices; namespace Interop.Windows @@ -36,5 +34,3 @@ public UnicodeString(char* buffer, int length) } } } - -#endif 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 7868c8c876..ecefa338c9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -22,6 +22,7 @@ using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient.Connection; using Microsoft.SqlServer.Server; +using Microsoft.Win32; using IsolationLevel = System.Data.IsolationLevel; using Microsoft.Data.SqlClient.Internal; @@ -30,10 +31,6 @@ using System.Security.Permissions; #endif -#if _WINDOWS -using Microsoft.Win32; -#endif - namespace Microsoft.Data.Common { /// @@ -68,6 +65,22 @@ internal static partial class ADP /// internal const int MaxBufferAccessTokenExpiry = 600; + /// + /// This member returns true if the current OS platform is Windows. + /// + /// + /// This is a const on .NET Framework, and a property on .NET Core, because of differing API availability and JIT requirements. + /// .NET Framework will perform basic dead branch elimination when a const value is encountered, while .NET Core can trim Windows-specific + /// code when published to non-Windows platforms. + /// .NET Core's trimming is very limited though, so this must be used inline within methods to throw PlatformNotSupportedException, + /// rather than in a throw helper. + /// + #if NETFRAMEWORK + public const bool IsWindows = true; + #else + public static bool IsWindows => OperatingSystem.IsWindows(); + #endif + #region UDT #if NETFRAMEWORK @@ -435,17 +448,16 @@ internal static ArgumentOutOfRangeException InvalidCommandBehavior(CommandBehavi return InvalidEnumerationValue(typeof(CommandBehavior), (int)value); } - - #if _UNIX - internal static object LocalMachineRegistryValue(string subkey, string queryvalue) - { - // No registry in non-Windows environments - return null; - } - #endif - #if _WINDOWS internal static object LocalMachineRegistryValue(string subkey, string queryvalue) { + #if NET + if (!IsWindows) + { + // No registry in non-Windows environments + return null; + } + #endif + #if NETFRAMEWORK new RegistryPermission(RegistryPermissionAccess.Read, $@"HKEY_LOCAL_MACHINE\{subkey}").Assert(); #endif @@ -471,7 +483,6 @@ internal static object LocalMachineRegistryValue(string subkey, string queryvalu } #endif } - #endif internal static void ValidateCommandBehavior(CommandBehavior value) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs index 4006136de4..eb125e5b71 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs @@ -33,10 +33,6 @@ internal static partial class ADP /// internal const int MinimumTimeoutForTnirMs = 500; - // @TODO: Use naming rules - // @TODO: All values but Unix and Windows32NT are used today, for netfx this should always be Win32NT. We can likely hard code this to true for netfx. - internal static readonly bool s_isWindowsNT = Environment.OSVersion.Platform == PlatformID.Win32NT; - #endregion #region Utility Methods diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 786f91684b..57597ae761 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -53,9 +53,6 @@ sealed internal class DbConnectionPoolGroup internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions) { Debug.Assert(connectionOptions != null, "null connection options"); -#if NETFRAMEWORK - Debug.Assert(poolGroupOptions == null || ADP.s_isWindowsNT, "should not have pooling options on Win9x"); -#endif _connectionOptions = connectionOptions; _poolKey = key; @@ -143,10 +140,6 @@ internal IDbConnectionPool GetConnectionPool(SqlConnectionFactory connectionFact IDbConnectionPool pool = null; if (_poolGroupOptions != null) { -#if NETFRAMEWORK - Debug.Assert(ADP.s_isWindowsNT, "should not be pooling on Win9x"); -#endif - DbConnectionPoolIdentity currentIdentity = DbConnectionPoolIdentity.NoIdentity; if (_poolGroupOptions.PoolByIdentity) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs similarity index 94% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs index 802472f83e..fc65808cf1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs @@ -2,8 +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 _WINDOWS - +using Microsoft.Data.Common; using Microsoft.Data.SqlClient.AlwaysEncrypted; using System; using System.Diagnostics.CodeAnalysis; @@ -150,6 +149,11 @@ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out /// public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) { + if (!ADP.IsWindows) + { + throw new PlatformNotSupportedException(); + } + // Validate the input parameters ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true); @@ -176,6 +180,11 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) { + if (!ADP.IsWindows) + { + throw new PlatformNotSupportedException(); + } + // Validate the input parameters ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false); @@ -202,15 +211,17 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) { - throw new NotSupportedException(); + throw ADP.IsWindows + ? new NotSupportedException() + : new PlatformNotSupportedException(); } /// public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature) { - throw new NotSupportedException(); + throw ADP.IsWindows + ? new NotSupportedException() + : new PlatformNotSupportedException(); } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.unix.cs deleted file mode 100644 index 6ab8567e2a..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.unix.cs +++ /dev/null @@ -1,56 +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. - -// @TODO: See if we can throw this when we would create this object -#if NET && _UNIX - -using System; - -#nullable enable - -namespace Microsoft.Data.SqlClient -{ - /// - public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvider - { - /// - public const string ProviderName = @"MSSQL_CNG_STORE"; - - /// - /// This encryption keystore uses an asymmetric key as the column master key. - /// - internal const string MasterKeyType = @"asymmetric key"; - - /// - /// This encryption keystore uses the master key path to reference a CNG provider. - /// - internal const string KeyPathReference = @"Microsoft Cryptography API: Next Generation (CNG) provider"; - - /// - public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) - { - throw new PlatformNotSupportedException(); - } - - /// - public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) - { - throw new PlatformNotSupportedException(); - } - - /// - public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) - { - throw new PlatformNotSupportedException(); - } - - /// - public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature) - { - throw new PlatformNotSupportedException(); - } - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs similarity index 95% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs index 33fe10fe66..73ee585dfa 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs @@ -2,8 +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 _WINDOWS - +using Microsoft.Data.Common; using Microsoft.Data.SqlClient.AlwaysEncrypted; using Microsoft.Win32; using System; @@ -152,6 +151,11 @@ private static int GetProviderType(string providerName, string keyPath, bool isS /// public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) { + if (!ADP.IsWindows) + { + throw new PlatformNotSupportedException(); + } + // Validate the input parameters ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true); @@ -178,6 +182,11 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) { + if (!ADP.IsWindows) + { + throw new PlatformNotSupportedException(); + } + // Validate the input parameters ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false); @@ -204,15 +213,17 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) { - throw new NotSupportedException(); + throw ADP.IsWindows + ? new NotSupportedException() + : new PlatformNotSupportedException(); } /// public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature) { - throw new NotSupportedException(); + throw ADP.IsWindows + ? new NotSupportedException() + : new PlatformNotSupportedException(); } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.unix.cs deleted file mode 100644 index f96841273c..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.unix.cs +++ /dev/null @@ -1,56 +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. - -// @TODO: See if we can throw this when we would create this object -#if NET && _UNIX - -using System; - -#nullable enable - -namespace Microsoft.Data.SqlClient -{ - /// - public class SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvider - { - /// - public const string ProviderName = @"MSSQL_CSP_PROVIDER"; - - /// - /// This encryption keystore uses an asymmetric key as the column master key. - /// - internal const string MasterKeyType = @"asymmetric key"; - - /// - /// This encryption keystore uses the master key path to reference a CSP provider. - /// - internal const string KeyPathReference = @"Microsoft Cryptographic Service Provider (CSP)"; - - /// - public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) - { - throw new PlatformNotSupportedException(); - } - - /// - public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) - { - throw new PlatformNotSupportedException(); - } - - /// - public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) - { - throw new PlatformNotSupportedException(); - } - - /// - public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature) - { - throw new PlatformNotSupportedException(); - } - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs index b1a7f45abb..4c48890fc4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.ComponentModel; using System.Diagnostics; @@ -104,6 +102,11 @@ public SqlFileStream( FileOptions options, long allocationSize) { + if (!ADP.IsWindows) + { + throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + // @TODO: Adopt netcore style format #if NETFRAMEWORK const string scopeFormat = " {0} access={1} options={2} path='{3}'"; @@ -957,5 +960,3 @@ private void ThrowIfDisposed() #endregion } } - -#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 deleted file mode 100644 index 3ba7e55237..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.netcore.unix.cs +++ /dev/null @@ -1,92 +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. - -#if NET && _UNIX - -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 From 85b22b6281513f5fa43b31147572362373861355 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 17 Apr 2026 07:12:39 +1000 Subject: [PATCH 044/298] Use using declaration for paramsCmd (#4010) --- .../src/Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 71845ddeb9..5ddb9115d2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -1350,7 +1350,7 @@ internal void DeriveParameters() useManagedDataType = false; } - SqlCommand paramsCmd = new SqlCommand(cmdText.ToString(), Connection, Transaction) + using SqlCommand paramsCmd = new SqlCommand(cmdText.ToString(), Connection, Transaction) { CommandType = CommandType.StoredProcedure }; From 4df2aaefbb94072a781ad302b3ce61075be04c12 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 17 Apr 2026 07:59:39 +1000 Subject: [PATCH 045/298] better perf by avoiding redundant casts (#4018) * better perf by avoiding cast duplication * Update SqlParameter.cs --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 4 +-- .../Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- .../SqlClient/SqlConnectionStringBuilder.cs | 8 ++--- .../Microsoft/Data/SqlClient/SqlParameter.cs | 8 ++--- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 3 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 32 +++++++++---------- 6 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index fdda86b98e..3ebf10e7fc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1757,9 +1757,9 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re 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) + if (value is XmlReader xmlReader) { - value = new XmlDataFeed((XmlReader)value); + value = new XmlDataFeed(xmlReader); typeChanged = true; coercedToDataFeed = true; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 5ddb9115d2..1073bb137c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -2273,7 +2273,7 @@ and not SqlDbType.Udt s = val as string; if (s is null) { - SqlString sval = val is SqlString ? (SqlString)val : SqlString.Null; + SqlString sval = val is SqlString sqlVal ? sqlVal : SqlString.Null; if (!sval.IsNull) { s = sval.Value; 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 6bc29aee36..046228d0c6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -732,13 +732,9 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul { throw ADP.ArgumentNull(nameof(destinationType)); } - if (typeof(InstanceDescriptor) == destinationType) + if (destinationType == typeof(InstanceDescriptor) && value is SqlConnectionStringBuilder obj) { - SqlConnectionStringBuilder obj = (value as SqlConnectionStringBuilder); - if (obj is not null) - { - return ConvertToInstanceDescriptor(obj); - } + return ConvertToInstanceDescriptor(obj); } return base.ConvertTo(context, culture, value, destinationType); } 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 01d1131789..13b71c2a29 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -95,9 +95,9 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul { throw ADP.ArgumentNull(nameof(destinationType)); } - if ((typeof(InstanceDescriptor) == destinationType) && (value is SqlParameter)) + if (destinationType == typeof(InstanceDescriptor) && value is SqlParameter parameter) { - return ConvertToInstanceDescriptor(value as SqlParameter); + return ConvertToInstanceDescriptor(parameter); } return base.ConvertTo(context, culture, value, destinationType); } @@ -1951,9 +1951,9 @@ private MetaType GetMetaTypeOnly() _value = _value.ToString(); valueType = typeof(string); } - else if (valueType == typeof(char[])) + else if (_value is char[] chars) { - _value = new string((char[])_value); + _value = new string(chars); valueType = typeof(string); } return MetaType.GetMetaTypeFromType(valueType); 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 ab2261f0e9..d10df22966 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -1931,9 +1931,8 @@ internal static Exception GetExceptionArray(string serverName, string errorMessa Exception exceptionToInclude = e.InnerException != null ? e.InnerException : e; sqlErs.Add(new SqlError(infoNumber: 0, errorState: (byte)0x00, errorClass: (byte)TdsEnums.MIN_ERROR_CLASS, server: serverName, errorMessage: errorMessage, procedure: null, lineNumber: 0)); - if (e is SqlException) + if (e is SqlException exThrown) { - SqlException exThrown = (SqlException)e; SqlErrorCollection errorList = exThrown.Errors; for (int i = 0; i < exThrown.Errors.Count; i++) { 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 088d41d94d..6b5516d067 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -10665,9 +10665,9 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet if (isSqlVal) { - if (value is SqlString) + if (value is SqlString sqlString) { - s = ((SqlString)value).Value; + s = sqlString.Value; } else { @@ -11995,9 +11995,9 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars break; case TdsEnums.SQLXMLTYPE: // Value here could be string or XmlReader - if (value is XmlReader) + if (value is XmlReader xmlReader) { - value = MetaType.GetStringFromXml((XmlReader)value); + value = MetaType.GetStringFromXml(xmlReader); } ccb = ((isSqlType) ? ((SqlString)value).Value.Length : ((string)value).Length) * 2; break; @@ -12469,9 +12469,9 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe WriteInt(actualLength, stateObj); // chunk length } - if (value is SqlBinary) + if (value is SqlBinary sqlBinary) { - return stateObj.WriteByteArray(((SqlBinary)value).Value, actualLength, offset, canAccumulate: false); + return stateObj.WriteByteArray(sqlBinary.Value, actualLength, offset, canAccumulate: false); } else { @@ -12538,9 +12538,9 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe { WriteInt(codePageByteSize, stateObj); // chunk length } - if (value is SqlChars) + if (value is SqlChars sqlCharsEnc) { - string sch = new string(((SqlChars)value).Value); + string sch = new string(sqlCharsEnc.Value); return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } @@ -12584,9 +12584,9 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe actualLength >>= 1; } - if (value is SqlChars) + if (value is SqlChars sqlCharsArr) { - return WriteCharArray(((SqlChars)value).Value, actualLength, offset, stateObj, canAccumulate: false); + return WriteCharArray(sqlCharsArr.Value, actualLength, offset, stateObj, canAccumulate: false); } else { @@ -13634,9 +13634,9 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act { byte[] b = new byte[actualLength]; - if (value is SqlBinary) + if (value is SqlBinary sqlBin) { - Buffer.BlockCopy(((SqlBinary)value).Value, offset, b, 0, actualLength); + Buffer.BlockCopy(sqlBin.Value, offset, b, 0, actualLength); } else { @@ -13687,9 +13687,9 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLBIGVARCHAR: case TdsEnums.SQLTEXT: - if (value is SqlChars) + if (value is SqlChars sqlChSer) { - String sch = new String(((SqlChars)value).Value); + String sch = new String(sqlChSer.Value); return SerializeEncodingChar(sch, actualLength, offset, _defaultEncoding); } else @@ -13710,9 +13710,9 @@ private byte[] SerializeUnencryptedSqlValue(object value, MetaType type, int act actualLength >>= 1; } - if (value is SqlChars) + if (value is SqlChars sqlChArr) { - return SerializeCharArray(((SqlChars)value).Value, actualLength, offset); + return SerializeCharArray(sqlChArr.Value, actualLength, offset); } else { From a8d349e7baebebd973cd4bc46d5f1ea03101e46d Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 16 Apr 2026 23:56:41 +0100 Subject: [PATCH 046/298] Perf | Reduce allocations when sending large strings (#4072) * Introduce shims for Encoding Shim GetByteCount and GetBytes methods, and make sure these are used more widely. Improves performance on netfx and netcore. * Address comments in EncodingExtensions * Add unit tests for EncodingExtensions This also highlighted that passing a null string should throw an exception rather than return 0/[]. --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 35 +--- .../Data/SqlClient/TdsValueSetter.cs | 5 +- .../System/Text/EncodingExtensions.netfx.cs | 71 ++++++++ .../UnitTests/System/Text/EncodingTest.cs | 161 ++++++++++++++++++ 4 files changed, 237 insertions(+), 35 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/System/Text/EncodingExtensions.netfx.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/System/Text/EncodingTest.cs 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 6b5516d067..2d1807e7b5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8651,36 +8651,18 @@ private Task WriteEncodingChar(string s, Encoding encoding, TdsParserStateObject private byte[] SerializeEncodingChar(string s, int numChars, int offset, Encoding encoding) { -#if NETFRAMEWORK - char[] charData; - byte[] byteData = null; - // if hitting 7.0 server, encoding will be null in metadata for columns or return values since // 7.0 has no support for multiple code pages in data - single code page support only - if (encoding == null) - { - encoding = _defaultEncoding; - } - - charData = s.ToCharArray(offset, numChars); - - byteData = new byte[encoding.GetByteCount(charData, 0, charData.Length)]; - encoding.GetBytes(charData, 0, charData.Length, byteData, 0); + encoding ??= _defaultEncoding; - return byteData; -#else return encoding.GetBytes(s, offset, numChars); -#endif } private Task WriteEncodingChar(string s, int numChars, int offset, Encoding encoding, TdsParserStateObject stateObj, bool canAccumulate = true) { // if hitting 7.0 server, encoding will be null in metadata for columns or return values since // 7.0 has no support for multiple code pages in data - single code page support only - if (encoding == null) - { - encoding = _defaultEncoding; - } + encoding ??= _defaultEncoding; // Optimization: if the entire string fits in the current buffer, then copy it directly int bytesLeft = stateObj._outBuff.Length - stateObj._outBytesUsed; @@ -8692,23 +8674,14 @@ private Task WriteEncodingChar(string s, int numChars, int offset, Encoding enco } else { -#if NETFRAMEWORK - char[] charData = s.ToCharArray(offset, numChars); - byte[] byteData = encoding.GetBytes(charData, 0, numChars); - Debug.Assert(byteData != null, "no data from encoding"); - return stateObj.WriteByteArray(byteData, byteData.Length, 0, canAccumulate); -#else byte[] byteData = encoding.GetBytes(s, offset, numChars); Debug.Assert(byteData != null, "no data from encoding"); return stateObj.WriteByteArray(byteData, byteData.Length, 0, canAccumulate); -#endif } } internal int GetEncodingCharLength(string value, int numChars, int charOffset, Encoding encoding) { - // UNDONE: (PERF) this is an expensive way to get the length. Also, aren't we - // UNDONE: (PERF) going through these steps twice when we write out a value? if (string.IsNullOrEmpty(value)) { return 0; @@ -8726,9 +8699,7 @@ internal int GetEncodingCharLength(string value, int numChars, int charOffset, E encoding = _defaultEncoding; } - char[] charData = value.ToCharArray(charOffset, numChars); - - return encoding.GetByteCount(charData, 0, numChars); + return encoding.GetByteCount(value, charOffset, numChars); } // diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs index 198460791b..fd9d64943e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsValueSetter.cs @@ -354,8 +354,7 @@ internal void SetString(string value, int offset, int length) } else { - char[] chars = value.ToCharArray(offset, length); - bytes = _stateObj.Parser._defaultEncoding.GetBytes(chars); + bytes = _stateObj.Parser._defaultEncoding.GetBytes(value, offset, length); } SetBytes(0, bytes, 0, bytes.Length); SetBytesLength(bytes.Length); @@ -376,7 +375,7 @@ internal void SetString(string value, int offset, int length) } else { - bytes = _stateObj.Parser._defaultEncoding.GetBytes(value.ToCharArray(offset, length)); + bytes = _stateObj.Parser._defaultEncoding.GetBytes(value, offset, length); } _stateObj.Parser.WriteSqlVariantHeader(9 + bytes.Length, TdsEnums.SQLBIGVARCHAR, 7, _stateObj); _stateObj.Parser.WriteUnsignedInt(collation._info, _stateObj); // propbytes: collation.Info diff --git a/src/Microsoft.Data.SqlClient/src/System/Text/EncodingExtensions.netfx.cs b/src/Microsoft.Data.SqlClient/src/System/Text/EncodingExtensions.netfx.cs new file mode 100644 index 0000000000..baf2a275e5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/System/Text/EncodingExtensions.netfx.cs @@ -0,0 +1,71 @@ +// 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; + +#nullable enable + +namespace System.Text; + +internal static class EncodingExtensions +{ + public static int GetByteCount(this Encoding encoding, string? s, int offset, int count) + { + if (s is null) + { + throw new ArgumentNullException(nameof(s)); + } + + ReadOnlySpan slicedString = s.AsSpan(offset, count); + + if (slicedString.Length == 0) + { + return 0; + } + + unsafe + { + fixed (char* str = slicedString) + { + return encoding.GetByteCount(str, slicedString.Length); + } + } + } + + public static byte[] GetBytes(this Encoding encoding, string? s, int index, int count) + { + if (s is null) + { + throw new ArgumentNullException(nameof(s)); + } + + ReadOnlySpan slicedString = s.AsSpan(index, count); + + if (slicedString.Length == 0) + { + return Array.Empty(); + } + + unsafe + { + fixed (char* str = slicedString) + { + int byteCount = encoding.GetByteCount(str, slicedString.Length); + byte[] bytes = new byte[byteCount]; + + fixed (byte* destArray = &bytes[0]) + { + int bytesWritten = encoding.GetBytes(str, slicedString.Length, destArray, bytes.Length); + + Debug.Assert(bytesWritten == byteCount); + return bytes; + } + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/System/Text/EncodingTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/System/Text/EncodingTest.cs new file mode 100644 index 0000000000..9b5b7ec785 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/System/Text/EncodingTest.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. + +using Xunit; + +namespace System.Text.UnitTests; + +/// +/// Tests that the Encoding polyfills in netfx operate correctly and handle +/// invalid parameter values. +/// +/// +/// In the netcore cases, we're testing the built-in GetBytes and GetByteCount +/// methods. The contract for our extension polyfills must match these implementations. +/// +public class EncodingTest +{ + private const string ExampleStringValue = "ABCDéFG1234567abcdefg"; + + /// + /// Represents a series of invalid [offset, count] pairs into the + /// constant. + /// + public static TheoryData InvalidOffsetsAndCounts => + new() + { + // Group 1: offset starts before the string. + // * Count extends beyond it. + { -1, 999 }, + // * Count is valid. + { -1, 5 }, + // Group 2: offset is valid. + // * Count extends beyond the end of it. + { 0, 999 }, + // * Count extends backwards to the start it. + { 5, -5 }, + // Group 3: offset starts beyond the end of the string. + // * Count extends beyond the end of it. + { 999, 999 }, + // * Count extends backwards into the string. + { 999, -1005 } + }; + + #if NET + static EncodingTest() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } +#endif + + /// + /// Verifies that GetByteCount throws an ArgumentNullException when passed a null string. + /// + [Fact] + public void GetByteCount_ThrowsOnNullString() + { + string nullString = null!; + Action act = () => Encoding.Unicode.GetByteCount(nullString, 0, 0); + + Assert.Throws(act); + } + + /// + /// Verifies that GetBytes throws an ArgumentNullException when passed a null string. + /// + [Fact] + public void GetBytes_ThrowsOnNullString() + { + string nullString = null!; + Action act = () => Encoding.Unicode.GetBytes(nullString, 0, 0); + + Assert.Throws(act); + } + + /// + /// Verifies that GetByteCount throws an ArgumentOutOfRangeException when passes an offset + /// or count which is outside of the string. + /// + /// offset parameter of GetByteCount. + /// count parameter of GetByteCount. + /// + [Theory] + [MemberData(nameof(InvalidOffsetsAndCounts))] + public void GetByteCount_ThrowsOnOutOfRangeOffsetOrCount(int offset, int count) + { + Action act = () => Encoding.Unicode.GetByteCount(ExampleStringValue, offset, count); + + Assert.Throws(act); + } + + /// + /// Verifies that GetBytes throws an ArgumentOutOfRangeException when passes an offset + /// or count which is outside of the string. + /// + /// offset parameter of GetBytes. + /// count parameter of GetBytes. + [Theory] + [MemberData(nameof(InvalidOffsetsAndCounts))] + public void GetBytes_ThrowsOnOutOfRangeOffsetOrCount(int offset, int count) + { + Action act = () => Encoding.Unicode.GetBytes(ExampleStringValue, offset, count); + + Assert.Throws(act); + } + + /// + /// Verifies that when using the new GetByteCount and GetBytes polyfills to encode the entire string, the return + /// value is equal to passing the string as-is to GetByteCount(string) and GetBytes(string). + /// + [Fact] + public void GetBytesOfFullStringByLength_MatchesGetBytesOfFullString() + { + byte[] fullStringBytes = Encoding.Unicode.GetBytes(ExampleStringValue); + int fullStringByteCount = Encoding.Unicode.GetByteCount(ExampleStringValue); + + byte[] partialStringBytes = Encoding.Unicode.GetBytes(ExampleStringValue, 0, ExampleStringValue.Length); + int partialStringByteCount = Encoding.Unicode.GetByteCount(ExampleStringValue, 0, ExampleStringValue.Length); + + Assert.Equal(fullStringByteCount, partialStringByteCount); + Assert.Equal(fullStringByteCount, partialStringBytes.Length); + Assert.Equal(fullStringBytes, partialStringBytes); + } + + /// + /// Verifies that encoding a specific substring returns a byte array which can be decoded into the same string, in + /// various code pages. + /// + /// The code page identifier to use for transcoding. + [Theory] + // Unicode + [InlineData(1200)] + // UTF8 + [InlineData(65001)] + public void GetBytes_Roundtrips(int codePage) + { + Encoding encoding = Encoding.GetEncoding(codePage); + byte[] partialStringBytes = encoding.GetBytes(ExampleStringValue, 4, 5); + string expectedRoundtrippedValue = ExampleStringValue.Substring(4, 5); + string roundtrip = encoding.GetString(partialStringBytes); + + Assert.Equal(expectedRoundtrippedValue, roundtrip); + } + + /// + /// Verifies that when a string contains a multibyte character, the byte array returns the correct number of + /// elements for the encoding. + /// + [Fact] + public void GetByteCount_ReturnsCorrectValueOnMultiCharacterRune() + { + // The character é is two bytes in UTF8. + Assert.Equal(6, Encoding.UTF8.GetByteCount(ExampleStringValue, 4, 5)); + + // All Unicode characters in our sample string are two bytes long. + Assert.Equal(10, Encoding.Unicode.GetByteCount(ExampleStringValue, 4, 5)); + + // Code page 1251 does not have the é character, so treats it as the single-byte character "e". + Assert.Equal(5, Encoding.GetEncoding(1251).GetByteCount(ExampleStringValue, 4, 5)); + } +} From 19b43052d0890db151263c522e5d1c8929f7fa45 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Fri, 17 Apr 2026 01:09:32 +0100 Subject: [PATCH 047/298] Tests | Introduce RAII SQL object primitives (#4050) * Create DatabaseObject base class for fixture This contains GenerateLongName and GenerateShortName, which are lifted from DataTestUtility.GetLongName and GetShortName in ManualTests. * Create derived types, use these in a few locations This eliminates DropUserDefinedType from DataTestUtility. * Further removal of ad-hoc utility methods in ParametersTest * Further removal of ad-hoc CREATE/DROP scripts in ParametersTest * Use Table type in SqlGraphTables.cs * Use Table and StoredProcedure types in JsonTest.cs * Use Table type in JsonStreamTest.cs * Remove duplicate helper function from ApiShould.cs --- .../DatabaseObjects/DatabaseObject.cs | 263 ++++++++ .../DatabaseObjects/StoredProcedure.cs | 41 ++ .../Common/Fixtures/DatabaseObjects/Table.cs | 61 ++ .../DatabaseObjects/UserDefinedType.cs | 41 ++ .../ManualTests/AlwaysEncrypted/ApiShould.cs | 83 +-- .../ManualTests/DataCommon/DataTestUtility.cs | 184 +----- .../SQL/JsonTest/JsonStreamTest.cs | 37 +- .../ManualTests/SQL/JsonTest/JsonTest.cs | 574 +++++++---------- .../SQL/ParameterTest/ParametersTest.cs | 590 +++++++----------- .../SQL/SqlBulkCopyTest/SqlGraphTables.cs | 22 +- .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 110 ++-- 11 files changed, 937 insertions(+), 1069 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs new file mode 100644 index 0000000000..2dc539b5f5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.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. + +using System; +using System.Text; +using System.Threading; + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// Base class for a transient database object (such as a table, type or +/// stored procedure.) +/// +public abstract class DatabaseObject : IDisposable +{ + private readonly bool _shouldDrop; + + protected SqlConnection Connection { get; } + + public string Name { get; } + + protected DatabaseObject(SqlConnection connection, string name, string definition, bool shouldCreate, bool shouldDrop) + { + _shouldDrop = shouldDrop; + + Connection = connection; + Name = name; + + if (shouldCreate) + { + EnsureConnectionOpen(); + DropObject(); + CreateObject(definition); + } + } + + private void EnsureConnectionOpen() + { + const int MaxWaits = 2; + int counter = MaxWaits; + + if (Connection.State is System.Data.ConnectionState.Closed) + { + Connection.Open(); + } + while (counter-- > 0 && Connection.State is System.Data.ConnectionState.Connecting) + { + Thread.Sleep(80); + } + } + + /// + /// Generate a new GUID and return the characters from its 1st and 4th + /// parts, as shown here: + /// + /// + /// 7ff01cb8-88c7-11f0-b433-00155d7e531e + /// ^^^^^^^^ ^^^^ + /// + /// + /// These 12 characters are concatenated together without any + /// separators. These 2 parts typically comprise a timestamp and clock + /// sequence, most likely to be unique for tests that generate names in + /// quick succession. + /// + private static string GetGuidParts() + { + var guid = Guid.NewGuid().ToString(); + // GOTCHA: The slice operator is inclusive of the start index and + // exclusive of the end index! + return guid.Substring(0, 8) + guid.Substring(19, 4); + } + + /// + /// Generate a long unique database object name, whose maximum length is + /// 96 characters, with the format: + /// + /// {Prefix}_{GuidParts}_{UserName}_{MachineName} + /// + /// The Prefix will be truncated to satisfy the overall maximum length. + /// + /// The GUID Parts will be the characters from the 1st and 4th blocks + /// from a traditional string representation, as shown here: + /// + /// + /// 7ff01cb8-88c7-11f0-b433-00155d7e531e + /// ^^^^^^^^ ^^^^ + /// + /// + /// These 2 parts typically comprise a timestamp and clock sequence, + /// most likely to be unique for tests that generate names in quick + /// succession. The 12 characters are concatenated together without any + /// separators. + /// + /// The UserName and MachineName are obtained from the Environment, + /// and will be truncated to satisfy the maximum overall length. + /// + /// + /// + /// The prefix to use when generating the unique name, truncated to at + /// most 32 characters. + /// + /// This should not contain any characters that cannot be used in + /// database object names. See: + /// + /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers + /// + /// + /// + /// When true, the entire generated name will be enclosed in square + /// brackets, for example: + /// + /// [MyPrefix_7ff01cb811f0_test_user_ci_agent_machine_name] + /// + /// + /// + /// A unique database object name, no more than 96 characters long. + /// + public static string GenerateLongName(string prefix, bool escape = true) + { + StringBuilder name = new(96); + + if (escape) + { + name.Append('['); + } + + if (prefix.Length > 32) + { + prefix = prefix.Substring(0, 32); + } + + name.Append(prefix); + name.Append('_'); + name.Append(GetGuidParts()); + name.Append('_'); + + var suffix = + Environment.UserName + '_' + + Environment.MachineName; + + int maxSuffixLength = 96 - name.Length; + if (escape) + { + --maxSuffixLength; + } + if (suffix.Length > maxSuffixLength) + { + suffix = suffix.Substring(0, maxSuffixLength); + } + + name.Append(suffix); + + if (escape) + { + name.Append(']'); + } + + return name.ToString(); + } + + /// + /// Generate a short unique database object name, whose maximum length + /// is 30 characters, with the format: + /// + /// {Prefix}_{GuidParts} + /// + /// The Prefix will be truncated to satisfy the overall maximum length. + /// + /// The GUID parts will be the characters from the 1st and 4th blocks + /// from a traditional string representation, as shown here: + /// + /// + /// 7ff01cb8-88c7-11f0-b433-00155d7e531e + /// ^^^^^^^^ ^^^^ + /// + /// + /// These 2 parts typically comprise a timestamp and clock sequence, + /// most likely to be unique for tests that generate names in quick + /// succession. The 12 characters are concatenated together without any + /// separators. + /// + /// + /// + /// The prefix to use when generating the unique name, truncated to at + /// most 18 characters when withBracket is false, and 16 characters when + /// withBracket is true. + /// + /// This should not contain any characters that cannot be used in + /// database object names. See: + /// + /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers + /// + /// + /// + /// When true, the entire generated name will be enclosed in square + /// brackets, for example: + /// + /// [MyPrefix_7ff01cb811f0] + /// + /// + /// + /// A unique database object name, no more than 30 characters long. + /// + public static string GenerateShortName(string prefix, bool escape = true) + { + StringBuilder name = new(30); + + if (escape) + { + name.Append('['); + } + + int maxPrefixLength = escape ? 16 : 18; + if (prefix.Length > maxPrefixLength) + { + prefix = prefix.Substring(0, maxPrefixLength); + } + + name.Append(prefix); + name.Append('_'); + name.Append(GetGuidParts()); + + if (escape) + { + name.Append(']'); + } + + return name.ToString(); + } + + /// + /// Creates the object with a given definition. + /// + /// Definition of the object to create. + /// + /// By the time this is called, will be open. + /// + protected abstract void CreateObject(string definition); + + /// + /// Drops the object created by . + /// + /// + /// By the time this is called, will be open. + /// Must not throw an exception if the object does not exist. + /// + protected abstract void DropObject(); + + public void Dispose() + { + if (_shouldDrop) + { + EnsureConnectionOpen(); + DropObject(); + } + // This explicitly does not drop the wrapped SqlConnection; this is sometimes + // used in a loop to create multiple UDTs. + + GC.SuppressFinalize(this); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs new file mode 100644 index 0000000000..d1b60612d1 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs @@ -0,0 +1,41 @@ +// 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.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient stored procedure, created at the start of its scope and dropped when disposed. +/// +public sealed class StoredProcedure : DatabaseObject +{ + /// + /// Initializes a new instance of the StoredProcedure class using the specified SQL connection, + /// name and definition. + /// + /// + /// If a stored procedure with the specified name already exists, it will be dropped automatically + /// before creation. + /// + /// The SQL connection used to interact with the database. + /// The stored procedure name. Can begin with '#' or '##' to indicate a temporary procedure. + /// The SQL definition of the stored procedure. + public StoredProcedure(SqlConnection connection, string prefix, string definition) + : base(connection, GenerateLongName(prefix), definition, shouldCreate: true, shouldDrop: true) + { + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE PROCEDURE {Name} {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + using SqlCommand dropCommand = new($"IF (OBJECT_ID('{Name}') IS NOT NULL) DROP PROCEDURE {Name}", Connection); + + dropCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs new file mode 100644 index 0000000000..2838ae2272 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs @@ -0,0 +1,61 @@ +// 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.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient table, created at the start of its scope and dropped when disposed. +/// +public sealed class Table : DatabaseObject +{ + /// + /// Initializes a new instance of the Table class using the specified SQL connection, table name prefix, and table + /// definition. + /// + /// + /// If a table with the specified name already exists, it will be dropped automatically before + /// creation. + /// + /// The SQL connection used to interact with the database. + /// The prefix for the table name. Can begin with '#' or '##' to indicate a temporary table. + /// The SQL definition describing the structure of the table, including columns and data types. + public Table(SqlConnection connection, string prefix, string definition) + : base(connection, GenerateLongName(prefix), definition, shouldCreate: true, shouldDrop: true) + { + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE TABLE {Name} {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + using SqlCommand dropCommand = new($"IF (OBJECT_ID('{Name}') IS NOT NULL) DROP TABLE {Name}", Connection); + + dropCommand.ExecuteNonQuery(); + } + + /// + /// Deletes all data from the table. + /// + public void DeleteData() + { + using SqlCommand deleteCommand = new($"DELETE FROM {Name}", Connection); + + deleteCommand.ExecuteNonQuery(); + } + + /// + /// Truncates the table. + /// + public void Truncate() + { + using SqlCommand truncateCommand = new($"TRUNCATE TABLE {Name}", Connection); + + truncateCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs new file mode 100644 index 0000000000..6d536d7550 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs @@ -0,0 +1,41 @@ +// 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.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient user-defined type, created at the start of its scope and dropped when disposed. +/// +public sealed class UserDefinedType : DatabaseObject +{ + /// + /// Initializes a new instance of the UserDefinedType class using the specified SQL connection, + /// name and definition. + /// + /// + /// If a user-defined type with the specified name already exists, it will be dropped automatically + /// before creation. + /// + /// The SQL connection used to interact with the database. + /// The type name. + /// The SQL definition of the type. + public UserDefinedType(SqlConnection connection, string prefix, string definition) + : base(connection, "[dbo]." + GenerateLongName(prefix), definition, shouldCreate: true, shouldDrop: true) + { + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE TYPE {Name} AS {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + using SqlCommand dropCommand = new($"IF (OBJECT_ID('{Name}') IS NOT NULL) DROP TYPE {Name}", Connection); + + dropCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index 7d63e9b98f..1c16e90ac4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -735,44 +735,28 @@ public async Task TestExecuteReaderAsyncWithLargeQuery(string connectionString) int columnsCount = 50; // Arrange - drops the table with long name and re-creates it with 52 columns (ID, name, ColumnName0..49) - try - { - CreateTable(connectionString, tableName, columnsCount); - string name = "nobody"; + using SqlConnection connection = new SqlConnection(connectionString); + await connection.OpenAsync(); - using (SqlConnection connection = new SqlConnection(connectionString)) - { - await connection.OpenAsync(); - // This creates a "select top 100" query that has over 40k characters - using (SqlCommand sqlCommand = new SqlCommand(GenerateSelectQuery(tableName, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"), - connection, - transaction: null, - columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled)) - { - sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int); - sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar, name.Length); + using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects.Table wideTable = new(connection, tableName, GenerateBitTableDefinition(columnsCount)); + string name = "nobody"; - sqlCommand.Parameters[0].Value = 0; - sqlCommand.Parameters[1].Value = name; + // This creates a "select top 100" query that has over 40k characters + using SqlCommand sqlCommand = new(GenerateSelectQuery(wideTable.Name, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"), + connection, + transaction: null, + columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled); + sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int); + sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar, name.Length); - // Act and Assert - // Test that execute reader async does not throw an exception. - // The table is empty so there should be no results; however, the bug previously found is that it causes a TDS RPC exception on enclave. - using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync()) - { - Assert.False(sqlDataReader.HasRows, "The table should be empty"); - } - } - } - } - catch - { - throw; - } - finally - { - DropTableIfExists(connectionString, tableName); - } + sqlCommand.Parameters[0].Value = 0; + sqlCommand.Parameters[1].Value = name; + + // Act and Assert + // Test that execute reader async does not throw an exception. + // The table is empty so there should be no results; however, the bug previously found is that it causes a TDS RPC exception on enclave. + using SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync(); + Assert.False(sqlDataReader.HasRows, "The table should be empty"); } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] @@ -3055,30 +3039,15 @@ private void CleanUpTable(string connString, string tableName) } } - private static void CreateTable(string connString, string tableName, int columnsCount) - => DataTestUtility.RunNonQuery(connString, GenerateCreateQuery(tableName, columnsCount)); - /// - /// Drops the table if the specified table exists - /// - /// The connection string to the database - /// The name of the table to be dropped - private static void DropTableIfExists(string connString, string tableName) - { - using var sqlConnection = new SqlConnection(connString); - sqlConnection.Open(); - DataTestUtility.DropTable(sqlConnection, tableName); - } - /// - /// Generates the query for creating a table with the number of bit columns specified. + /// Generates the definition of a table with the number of bit columns specified. /// /// The name of the table /// The number of columns for the table /// - private static string GenerateCreateQuery(string tableName, int columnsCount) + private static string GenerateBitTableDefinition(int columnsCount) { StringBuilder builder = new StringBuilder(); - builder.Append(string.Format("CREATE TABLE [dbo].[{0}]", tableName)); builder.Append('('); builder.AppendLine("[ID][bigint] NOT NULL,"); builder.AppendLine("[Name] [varchar] (200) NOT NULL"); @@ -3104,16 +3073,16 @@ private static string GenerateSelectQuery(string tableName, int columnsCount, in { StringBuilder builder = new StringBuilder(); builder.AppendLine($"SELECT TOP 100"); - builder.AppendLine($"[{tableName}].[ID],"); - builder.AppendLine($"[{tableName}].[Name]"); + builder.AppendLine($"{tableName}.[ID],"); + builder.AppendLine($"{tableName}.[Name]"); for (int i = 0; i < columnsCount; i++) { builder.Append(","); - builder.AppendLine($"[{tableName}].[ColumnName{i}]"); + builder.AppendLine($"{tableName}.[ColumnName{i}]"); } - string extra = string.IsNullOrEmpty(where) ? $"(NOLOCK) [{tableName}]" : where; - builder.AppendLine($"FROM [{tableName}] {extra};"); + string extra = string.IsNullOrEmpty(where) ? $"(NOLOCK) {tableName}" : where; + builder.AppendLine($"FROM {tableName} {extra};"); StringBuilder builder2 = new StringBuilder(); for (int i = 0; i < repeat; i++) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 2e652841c9..0145978d72 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -22,6 +22,7 @@ using System.Threading.Tasks; using Azure.Core; using Azure.Identity; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Microsoft.Data.SqlClient.TestUtilities; using Microsoft.Identity.Client; using Xunit; @@ -645,177 +646,11 @@ public static bool DoesHostAddressContainBothIPv4AndIPv6() } } - // Generate a new GUID and return the characters from its 1st and 4th - // parts, as shown here: - // - // 7ff01cb8-88c7-11f0-b433-00155d7e531e - // ^^^^^^^^ ^^^^ - // - // These 12 characters are concatenated together without any - // separators. These 2 parts typically comprise a timestamp and clock - // sequence, most likely to be unique for tests that generate names in - // quick succession. - private static string GetGuidParts() - { - var guid = Guid.NewGuid().ToString(); - // GOTCHA: The slice operator is inclusive of the start index and - // exclusive of the end index! - return guid.Substring(0, 8) + guid.Substring(19, 4); - } - - /// - /// Generate a short unique database object name, whose maximum length - /// is 30 characters, with the format: - /// - /// _ - /// - /// The Prefix will be truncated to satisfy the overall maximum length. - /// - /// The GUID parts will be the characters from the 1st and 4th blocks - /// from a traditional string representation, as shown here: - /// - /// 7ff01cb8-88c7-11f0-b433-00155d7e531e - /// ^^^^^^^^ ^^^^ - /// - /// These 2 parts typically comprise a timestamp and clock sequence, - /// most likely to be unique for tests that generate names in quick - /// succession. The 12 characters are concatenated together without any - /// separators. - /// - /// - /// - /// The prefix to use when generating the unique name, truncated to at - /// most 18 characters when withBracket is false, and 16 characters when - /// withBracket is true. - /// - /// This should not contain any characters that cannot be used in - /// database object names. See: - /// - /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers - /// - /// - /// - /// When true, the entire generated name will be enclosed in square - /// brackets, for example: - /// - /// [MyPrefix_7ff01cb811f0] - /// - /// - /// - /// A unique database object name, no more than 30 characters long. - /// - public static string GetShortName(string prefix, bool withBracket = true) - { - StringBuilder name = new(30); - - if (withBracket) - { - name.Append('['); - } - - int maxPrefixLength = withBracket ? 16 : 18; - if (prefix.Length > maxPrefixLength) - { - prefix = prefix.Substring(0, maxPrefixLength); - } - - name.Append(prefix); - name.Append('_'); - name.Append(GetGuidParts()); - - if (withBracket) - { - name.Append(']'); - } - - return name.ToString(); - } - - /// - /// Generate a long unique database object name, whose maximum length is - /// 96 characters, with the format: - /// - /// ___ - /// - /// The Prefix will be truncated to satisfy the overall maximum length. - /// - /// The GUID Parts will be the characters from the 1st and 4th blocks - /// from a traditional string representation, as shown here: - /// - /// 7ff01cb8-88c7-11f0-b433-00155d7e531e - /// ^^^^^^^^ ^^^^ - /// - /// These 2 parts typically comprise a timestamp and clock sequence, - /// most likely to be unique for tests that generate names in quick - /// succession. The 12 characters are concatenated together without any - /// separators. - /// - /// The UserName and MachineName are obtained from the Environment, - /// and will be truncated to satisfy the maximum overall length. - /// - /// - /// - /// The prefix to use when generating the unique name, truncated to at - /// most 32 characters. - /// - /// This should not contain any characters that cannot be used in - /// database object names. See: - /// - /// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers - /// - /// - /// - /// When true, the entire generated name will be enclosed in square - /// brackets, for example: - /// - /// [MyPrefix_7ff01cb811f0_test_user_ci_agent_machine_name] - /// - /// - /// - /// A unique database object name, no more than 96 characters long. - /// - public static string GetLongName(string prefix, bool withBracket = true) - { - StringBuilder name = new(96); + public static string GetShortName(string prefix, bool withBracket = true) => + DatabaseObject.GenerateShortName(prefix, withBracket); - if (withBracket) - { - name.Append('['); - } - - if (prefix.Length > 32) - { - prefix = prefix.Substring(0, 32); - } - - name.Append(prefix); - name.Append('_'); - name.Append(GetGuidParts()); - name.Append('_'); - - var suffix = - Environment.UserName + '_' + - Environment.MachineName; - - int maxSuffixLength = 96 - name.Length; - if (withBracket) - { - --maxSuffixLength; - } - if (suffix.Length > maxSuffixLength) - { - suffix = suffix.Substring(0, maxSuffixLength); - } - - name.Append(suffix); - - if (withBracket) - { - name.Append(']'); - } - - return name.ToString(); - } + public static string GetLongName(string prefix, bool withBracket = true) => + DatabaseObject.GenerateLongName(prefix, withBracket); public static void CreateTable(SqlConnection sqlConnection, string tableName, string createBody) { @@ -848,15 +683,6 @@ public static void DropTable(SqlConnection sqlConnection, string tableName) } } - public static void DropUserDefinedType(SqlConnection sqlConnection, string typeName) - { - ResurrectConnection(sqlConnection); - using (SqlCommand cmd = new SqlCommand(string.Format("IF (TYPE_ID('{0}') IS NOT NULL) \n DROP TYPE {0}", typeName), sqlConnection)) - { - cmd.ExecuteNonQuery(); - } - } - public static void DropStoredProcedure(SqlConnection sqlConnection, string spName) { ResurrectConnection(sqlConnection); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs index eee3b2d515..cbba92153a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -13,6 +13,7 @@ using Xunit; using Xunit.Abstractions; using Newtonsoft.Json.Linq; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -164,15 +165,20 @@ private void DeleteFile(string filename) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonStreaming() { - GenerateJsonFile(1000, _jsonFile); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + try { + GenerateJsonFile(1000, _jsonFile); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); connection.Open(); - var tableName = DataTestUtility.GetLongName("jsonTab"); - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - StreamJsonFileToServer(connection, tableName); - PrintJsonDataToFile(connection, tableName); + + using Table jsonTable = new(connection, "jsonTab", "(data json)"); + + StreamJsonFileToServer(connection, jsonTable.Name); + PrintJsonDataToFile(connection, jsonTable.Name); CompareJsonFiles(); + } + finally + { DeleteFile(_jsonFile); DeleteFile(_outputFile); } @@ -181,15 +187,20 @@ public void TestJsonStreaming() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public async Task TestJsonStreamingAsync() { - GenerateJsonFile(1000, _jsonFile); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + try { + GenerateJsonFile(1000, _jsonFile); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); await connection.OpenAsync(); - var tableName = DataTestUtility.GetLongName("jsonTab"); - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - await StreamJsonFileToServerAsync(connection, tableName); - await PrintJsonDataToFileAsync(connection, tableName); + + using Table jsonTable = new(connection, "jsonTab", "(data json)"); + + await StreamJsonFileToServerAsync(connection, jsonTable.Name); + await PrintJsonDataToFileAsync(connection, jsonTable.Name); CompareJsonFiles(); + } + finally + { DeleteFile(_jsonFile); 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 516f9d1750..0652251447 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,19 +11,20 @@ using Xunit.Abstractions; using System.Text.Json; using Microsoft.Data.SqlTypes; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class JsonTest { + private const string JsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]"; + private readonly ITestOutputHelper _output; public JsonTest(ITestOutputHelper output) { _output = output; } - - private static readonly string JsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]"; private void ValidateRowsAffected(int rowsAffected) { @@ -76,380 +77,264 @@ private void ValidateNullJson(SqlDataReader reader) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonWrite() { - string tableName = DataTestUtility.GenerateObjectName(); - string spName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string spCreate = "CREATE PROCEDURE " + spName + " (@jsonData json) AS " + tableInsert; + using Table jsonTable = new(connection, nameof(TestJsonWrite), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + using StoredProcedure insertJsonProcedure = new(connection, nameof(TestJsonWrite), $"(@jsonData json) AS {tableInsert}"); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) - { - connection.Open(); + using SqlCommand command = connection.CreateCommand(); - using (SqlCommand command = connection.CreateCommand()) - { - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - - //Create SP for writing json values - DataTestUtility.DropStoredProcedure(connection, spName); - command.CommandText = spCreate; - command.ExecuteNonQuery(); - - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - command.Parameters.Add(parameter); - - //Test 1 - //Write json value using a parameterized query - parameter.Value = JsonDataString; - int rowsAffected = command.ExecuteNonQuery(); - ValidateRowsAffected(rowsAffected); - - //Test 2 - //Write a SqlString type as json - parameter.Value = new SqlString(JsonDataString); - int rowsAffected2 = command.ExecuteNonQuery(); - ValidateRowsAffected(rowsAffected2); - - //Test 3 - //Write json value using SP - using (SqlCommand command2 = connection.CreateCommand()) - { - command2.CommandText = spName; - command2.CommandType = CommandType.StoredProcedure; - var parameter2 = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - 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); - } + command.CommandText = tableInsert; + var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); + command.Parameters.Add(parameter); + + //Test 1 + //Write json value using a parameterized query + parameter.Value = JsonDataString; + int rowsAffected = command.ExecuteNonQuery(); + ValidateRowsAffected(rowsAffected); + + //Test 2 + //Write a SqlString type as json + parameter.Value = new SqlString(JsonDataString); + int rowsAffected2 = command.ExecuteNonQuery(); + ValidateRowsAffected(rowsAffected2); + + //Test 3 + //Write json value using SP + using (SqlCommand command2 = connection.CreateCommand()) + { + command2.CommandText = insertJsonProcedure.Name; + command2.CommandType = CommandType.StoredProcedure; + command2.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + 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); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public async Task TestJsonWriteAsync() { - string tableName = DataTestUtility.GenerateObjectName(); - string spName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string spCreate = "CREATE PROCEDURE " + spName + " (@jsonData json) AS " + tableInsert; + using Table jsonTable = new(connection, nameof(TestJsonWriteAsync), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + using StoredProcedure insertJsonProcedure = new(connection, nameof(TestJsonWriteAsync), $"(@jsonData json) AS {tableInsert}"); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) - { - await connection.OpenAsync(); + using SqlCommand command = connection.CreateCommand(); + command.CommandText = tableInsert; + SqlParameter parameter = new("@jsonData", SqlDbTypeExtensions.Json); + command.Parameters.Add(parameter); - using (SqlCommand command = connection.CreateCommand()) - { - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - - //Create SP for writing json values - DataTestUtility.DropStoredProcedure(connection, spName); - command.CommandText = spCreate; - await command.ExecuteNonQueryAsync(); - - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - command.Parameters.Add(parameter); - - //Test 1 - //Write json value using a parameterized query - parameter.Value = JsonDataString; - int rowsAffected = await command.ExecuteNonQueryAsync(); - ValidateRowsAffected(rowsAffected); - - //Test 2 - //Write a SqlString type as json - parameter.Value = new SqlString(JsonDataString); - int rowsAffected2 = await command.ExecuteNonQueryAsync(); - ValidateRowsAffected(rowsAffected2); - - //Test 3 - //Write json value using SP - using (SqlCommand command2 = connection.CreateCommand()) - { - command2.CommandText = spName; - command2.CommandType = CommandType.StoredProcedure; - var parameter2 = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - 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); - } + //Test 1 + //Write json value using a parameterized query + parameter.Value = JsonDataString; + int rowsAffected = await command.ExecuteNonQueryAsync(); + ValidateRowsAffected(rowsAffected); + + //Test 2 + //Write a SqlString type as json + parameter.Value = new SqlString(JsonDataString); + int rowsAffected2 = await command.ExecuteNonQueryAsync(); + ValidateRowsAffected(rowsAffected2); + + //Test 3 + //Write json value using SP + using (SqlCommand command2 = connection.CreateCommand()) + { + command2.CommandText = insertJsonProcedure.Name; + command2.CommandType = CommandType.StoredProcedure; + command2.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + 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); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonRead() { - string tableName = DataTestUtility.GenerateObjectName(); - string spName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string tableRead = "SELECT * FROM " + tableName; - string spCreate = "CREATE PROCEDURE " + spName + " AS " + tableRead; + using Table jsonTable = new(connection, nameof(TestJsonRead), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + string tableRead = $"SELECT * FROM {jsonTable.Name}"; + using StoredProcedure readJsonProcedure = new(connection, nameof(TestJsonRead), $"AS {tableRead}"); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + using SqlCommand command = connection.CreateCommand(); + + //Insert sample json data + //This will be used for reading + command.CommandText = tableInsert; + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + command.ExecuteNonQuery(); + + //Test 1 + //Read json value using query + command.CommandText = tableRead; + using (SqlDataReader reader = command.ExecuteReader()) { - connection.Open(); - using (SqlCommand command = connection.CreateCommand()) - { - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - - //Create SP for reading from json column - DataTestUtility.DropStoredProcedure(connection, spName); - command.CommandText = spCreate; - command.ExecuteNonQuery(); - - //Insert sample json data - //This will be used for reading - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = JsonDataString; - command.Parameters.Add(parameter); - command.ExecuteNonQuery(); - - //Test 1 - //Read json value using query - command.CommandText = tableRead; - var reader = command.ExecuteReader(); - ValidateRows(reader); - - //Test 2 - //Read the column metadata - ValidateSchema(reader); - reader.Close(); - - //Test 3 - //Read json value using SP - using (SqlCommand command2 = connection.CreateCommand()) - { - command2.CommandText = spName; - command2.CommandType = CommandType.StoredProcedure; - var reader2 = command2.ExecuteReader(); - ValidateRows(reader2); - reader2.Close(); - } - - DataTestUtility.DropTable(connection, tableName); - DataTestUtility.DropStoredProcedure(connection, spName); - } + ValidateRows(reader); + + //Test 2 + //Read the column metadata + ValidateSchema(reader); } + + //Test 3 + //Read json value using SP + using SqlCommand command2 = connection.CreateCommand(); + command2.CommandText = readJsonProcedure.Name; + command2.CommandType = CommandType.StoredProcedure; + using SqlDataReader reader2 = command2.ExecuteReader(); + ValidateRows(reader2); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public async Task TestJsonReadAsync() { - string tableName = DataTestUtility.GenerateObjectName(); - string spName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string tableRead = "SELECT * FROM " + tableName; - string spCreate = "CREATE PROCEDURE " + spName + " AS " + tableRead; + using Table jsonTable = new(connection, nameof(TestJsonReadAsync), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + string tableRead = $"SELECT * FROM {jsonTable.Name}"; + using StoredProcedure readJsonProcedure = new(connection, nameof(TestJsonRead), $"AS {tableRead}"); + + using SqlCommand command = connection.CreateCommand(); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + //Insert sample json data + //This will be used for reading + command.CommandText = tableInsert; + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + await command.ExecuteNonQueryAsync(); + + //Test 1 + //Read json value using query + command.CommandText = tableRead; + using (SqlDataReader reader = await command.ExecuteReaderAsync()) { - await connection.OpenAsync(); - using (SqlCommand command = connection.CreateCommand()) - { - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(data json)"); - - //Create SP for reading from json column - DataTestUtility.DropStoredProcedure(connection, spName); - command.CommandText = spCreate; - await command.ExecuteNonQueryAsync(); - - //Insert sample json data - //This will be used for reading - command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = JsonDataString; - command.Parameters.Add(parameter); - await command.ExecuteNonQueryAsync(); - - //Test 1 - //Read json value using query - command.CommandText = tableRead; - var reader = await command.ExecuteReaderAsync(); - await ValidateRowsAsync(reader); - - //Test 2 - //Read the column metadata - ValidateSchema(reader); - reader.Close(); - - //Test 3 - //Read json value using SP - using (SqlCommand command2 = connection.CreateCommand()) - { - command2.CommandText = spName; - command2.CommandType = CommandType.StoredProcedure; - var reader2 = await command2.ExecuteReaderAsync(); - await ValidateRowsAsync(reader2); - reader2.Close(); - } - - DataTestUtility.DropTable(connection, tableName); - DataTestUtility.DropStoredProcedure(connection, spName); - } + await ValidateRowsAsync(reader); + + //Test 2 + //Read the column metadata + ValidateSchema(reader); } + + //Test 3 + //Read json value using SP + using SqlCommand command2 = connection.CreateCommand(); + command2.CommandText = readJsonProcedure.Name; + command2.CommandType = CommandType.StoredProcedure; + using SqlDataReader reader2 = await command2.ExecuteReaderAsync(); + await ValidateRowsAsync(reader2); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestNullJson() { - string tableName = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string tableRead = "SELECT * FROM " + tableName; + using Table jsonTable = new(connection, nameof(TestNullJson), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + string tableRead = $"SELECT * FROM {jsonTable.Name}"; - using SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString); - connection.Open(); using SqlCommand command = connection.CreateCommand(); - //Create Table - DataTestUtility.CreateTable(connection, tableName, "(Data json)"); - //Insert Null value command.CommandText = tableInsert; - var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = DBNull.Value; - command.Parameters.Add(parameter); + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = DBNull.Value }); command.ExecuteNonQuery(); //Query the table command.CommandText = tableRead; - var reader = command.ExecuteReader(); + using SqlDataReader reader = command.ExecuteReader(); ValidateNullJson(reader); - - reader.Close(); - DataTestUtility.DropTable(connection, tableName); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonAPIs() { - string tableName = DataTestUtility.GenerateObjectName(); - string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; - string tableRead = "SELECT * FROM " + tableName; + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + using Table jsonTable = new(connection, nameof(TestJsonAPIs), "(data json)"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@jsonData)"; + string tableRead = $"SELECT * FROM {jsonTable.Name}"; + + using SqlCommand command = connection.CreateCommand(); + //Insert + command.CommandText = tableInsert; + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + command.ExecuteNonQuery(); + + // Query the table + command.CommandText = tableRead; + using SqlDataReader reader = command.ExecuteReader(); + while (reader.Read()) { - 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); - } - } + 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); } } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] public void TestJsonWithMARS() { - string table1Name = DataTestUtility.GenerateObjectName(); - string table2Name = DataTestUtility.GenerateObjectName(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString + "MultipleActiveResultSets=True;"); + connection.Open(); + + using Table jsonTable1 = new(connection, nameof(TestJsonWithMARS), "(Data json)"); + using Table jsonTable2 = new(connection, nameof(TestJsonWithMARS), "(Id int, Data json)"); - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString + "MultipleActiveResultSets=True;")) + // Insert Data + string table1Insert = $"INSERT INTO {jsonTable1.Name} VALUES ('{JsonDataString}')"; + string table2Insert = $"INSERT INTO {jsonTable2.Name} VALUES (1,'{JsonDataString}')"; + using (SqlCommand command = connection.CreateCommand()) { - connection.Open(); - try + command.CommandText = table1Insert; + command.ExecuteNonQuery(); + command.CommandText = table2Insert; + command.ExecuteNonQuery(); + } + + // Read Data + using SqlCommand command1 = new($"select * from {jsonTable1.Name}", connection); + using SqlCommand command2 = new($"select * from {jsonTable2.Name}", connection); + + using (SqlDataReader reader1 = command1.ExecuteReader()) + { + while (reader1.Read()) { - // 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"]); - } - } - } - } + Assert.Equal(JsonDataString, reader1["data"]); } - finally + + using SqlDataReader reader2 = command2.ExecuteReader(); + while (reader2.Read()) { - DataTestUtility.DropTable(connection, table1Name); - DataTestUtility.DropTable(connection, table2Name); + Assert.Equal(1, reader2["Id"]); + Assert.Equal(JsonDataString, reader2["data"]); } } } @@ -457,53 +342,34 @@ public void TestJsonWithMARS() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] 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)"); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - // Create Stored Procedure - string createSP = $@" + using Table jsonTable = new(connection, nameof(TestJsonSPParams), "(Id int, Data json)"); + using StoredProcedure readJsonProcedure = new(connection, nameof(TestJsonRead), $@" @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); - } - } + SELECT @jsonData = (SELECT Data FROM {jsonTable.Name} WHERE Id = @id) + END;"); + string tableInsert = $"INSERT INTO {jsonTable.Name} VALUES (@id, @jsonData)"; + + // Insert Data + using SqlCommand command = new(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(readJsonProcedure.Name, connection); + + spCommand.CommandType = CommandType.StoredProcedure; + spCommand.Parameters.Add(new SqlParameter("@id", SqlDbType.Int) { Direction = ParameterDirection.Input, Value = 1 }); + SqlParameter outputParam = new("@jsonData", SqlDbTypeExtensions.Json) { Direction = ParameterDirection.Output }; + spCommand.Parameters.Add(outputParam); + spCommand.ExecuteNonQuery(); + Assert.Equal(JsonDataString, (string)outputParam.Value); } } } 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 d53209e912..782ef68ca8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -12,6 +12,8 @@ using Xunit; using System.Globalization; using Microsoft.Data.SqlClient.Tests.Common; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + #if !NETFRAMEWORK @@ -120,49 +122,32 @@ public static void CodeCoverageSqlClient() public static void Test_Copy_SqlParameter() { using var conn = new SqlConnection(s_connString); - string cTableName = DataTestUtility.GetLongName("#tmp"); - try - { - // Create tmp table - var sCreateTable = "IF NOT EXISTS("; - sCreateTable += $"SELECT * FROM sysobjects WHERE name= '{ cTableName }' and xtype = 'U')"; - sCreateTable += $"CREATE TABLE { cTableName }( BinValue binary(16) null)"; + conn.Open(); - conn.Open(); - var cmd = new SqlCommand(sCreateTable, conn); - cmd.ExecuteNonQuery(); + using Table binTable = new(conn, "#tmp", "(BinValue binary(16) null)"); - var dt = new DataTable("SourceDataTable"); - dt.Columns.Add("SourceBinValue", typeof(byte[])); + using var dt = new DataTable("SourceDataTable"); + dt.Columns.Add("SourceBinValue", typeof(byte[])); - dt.Rows.Add(Guid.NewGuid().ToByteArray()); - dt.Rows.Add(DBNull.Value); + dt.Rows.Add(Guid.NewGuid().ToByteArray()); + dt.Rows.Add(DBNull.Value); - var cmdInsert = new SqlCommand - { - UpdatedRowSource = UpdateRowSource.None, - Connection = conn, + using var cmdInsert = new SqlCommand + { + UpdatedRowSource = UpdateRowSource.None, + Connection = conn, - CommandText = $"INSERT { cTableName } (BinValue) " - }; - cmdInsert.CommandText += "Values(@BinValue)"; - cmdInsert.Parameters.Add("@BinValue", SqlDbType.Binary, 16, "SourceBinValue"); + CommandText = $"INSERT {binTable.Name} (BinValue) VALUES (@BinValue)" + }; + cmdInsert.Parameters.Add("@BinValue", SqlDbType.Binary, 16, "SourceBinValue"); - var da = new SqlDataAdapter - { - InsertCommand = cmdInsert, - UpdateBatchSize = 2, - AcceptChangesDuringUpdate = false - }; - da.Update(dt); - } - finally + using var da = new SqlDataAdapter { - // End of test, cleanup tmp table; - var sDropTable = $"DROP TABLE IF EXISTS {cTableName}"; - using SqlCommand cmd = new(sDropTable, conn); - cmd.ExecuteNonQuery(); - } + InsertCommand = cmdInsert, + UpdateBatchSize = 2, + AcceptChangesDuringUpdate = false + }; + da.Update(dt); } // TODO Synapse: Remove dependency on Northwind database @@ -170,8 +155,8 @@ public static void Test_Copy_SqlParameter() public static void Test_SqlParameter_Constructor() { using var conn = new SqlConnection(s_connString); - var dataTable = new DataTable(); - var adapter = new SqlDataAdapter + using var dataTable = new DataTable(); + using var adapter = new SqlDataAdapter { SelectCommand = new SqlCommand("SELECT CustomerID, ContactTitle FROM dbo.Customers WHERE ContactTitle = @ContactTitle", conn) }; @@ -202,7 +187,7 @@ public static void Test_WithEnumValue_ShouldInferToUnderlyingType() { using var conn = new SqlConnection(s_connString); conn.Open(); - var cmd = new SqlCommand("select @input", conn); + using var cmd = new SqlCommand("select @input", conn); cmd.Parameters.AddWithValue("@input", MyEnum.B); object value = cmd.ExecuteScalar(); Assert.Equal(MyEnum.B, (MyEnum)value); @@ -213,7 +198,7 @@ public static void Test_WithOutputEnumParameter_ShouldReturnEnum() { using var conn = new SqlConnection(s_connString); conn.Open(); - var cmd = new SqlCommand("set @output = @input", conn); + using var cmd = new SqlCommand("set @output = @input", conn); cmd.Parameters.AddWithValue("@input", MyEnum.B); SqlParameter outputParam = cmd.CreateParameter(); outputParam.ParameterName = "@output"; @@ -229,7 +214,7 @@ public static void Test_WithDecimalValue_ShouldReturnDecimal() { using var conn = new SqlConnection(s_connString); conn.Open(); - var cmd = new SqlCommand("select @foo", conn); + using var cmd = new SqlCommand("select @foo", conn); cmd.Parameters.AddWithValue("@foo", new SqlDecimal(0.5)); var result = (decimal)cmd.ExecuteScalar(); Assert.Equal((decimal)0.5, result); @@ -242,7 +227,7 @@ public static void Test_WithGuidValue_ShouldReturnGuid() using var conn = new SqlConnection(s_connString); conn.Open(); var expectedGuid = Guid.NewGuid(); - var cmd = new SqlCommand("select @input", conn); + using var cmd = new SqlCommand("select @input", conn); cmd.Parameters.AddWithValue("@input", expectedGuid); var result = cmd.ExecuteScalar(); @@ -262,67 +247,41 @@ public static void Test_WithGuidValue_ShouldReturnGuid() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestParametersWithDatatablesTVPInsert() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); int x = 4, y = 5; - DataTable table = new() + using DataTable table = new() { Columns = { { "x", typeof(int) }, { "y", typeof(int) } }, Rows = { { x, y } } }; - using SqlConnection connection = new(builder.ConnectionString); - string tableName = DataTestUtility.GetLongName("Table"); - string procName = DataTestUtility.GetLongName("Proc"); - string typeName = DataTestUtility.GetShortName("Type"); - try - { - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandText = $"CREATE TYPE {typeName} AS TABLE (x INT, y INT)"; - cmd.ExecuteNonQuery(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - cmd.CommandText = $"CREATE TABLE {tableName} (x INT, y INT)"; - cmd.ExecuteNonQuery(); + using UserDefinedType udtCoordPair = new(connection, "Type", "TABLE (x INT, y INT)"); + using Table tableCoordPair = new(connection, "Table", "(x INT, y INT)"); + using StoredProcedure spInsertCoordPair = new(connection, "Proc", $"@TVP {udtCoordPair.Name} READONLY AS " + + $"SET NOCOUNT ON INSERT INTO {tableCoordPair.Name}(x, y) SELECT * FROM @TVP"); - cmd.CommandText = $"CREATE PROCEDURE {procName} @TVP {typeName} READONLY AS " + - $"SET NOCOUNT ON INSERT INTO {tableName}(x, y) SELECT * FROM @TVP"; - cmd.ExecuteNonQuery(); + using SqlCommand cmd = connection.CreateCommand(); + // Update Data Using TVPs + cmd.CommandText = spInsertCoordPair.Name; + cmd.CommandType = CommandType.StoredProcedure; - } - using (SqlCommand cmd = connection.CreateCommand()) - { - // Update Data Using TVPs - cmd.CommandText = procName; - cmd.CommandType = CommandType.StoredProcedure; - - SqlParameter parameter = cmd.Parameters.AddWithValue("@TVP", table); - parameter.TypeName = typeName; + SqlParameter parameter = cmd.Parameters.AddWithValue("@TVP", table); + parameter.TypeName = udtCoordPair.Name; - cmd.ExecuteNonQuery(); + cmd.ExecuteNonQuery(); - // Verify if the data was updated - cmd.CommandText = "select * from " + tableName; - cmd.CommandType = CommandType.Text; - using SqlDataReader reader = cmd.ExecuteReader(); - DataTable dbData = new(); - dbData.Load(reader); - Assert.Equal(1, dbData.Rows.Count); - Assert.Equal(x, dbData.Rows[0][0]); - Assert.Equal(y, dbData.Rows[0][1]); - } - } - finally - { - using SqlCommand cmd = connection.CreateCommand(); - cmd.CommandText = "DROP PROCEDURE " + procName; - cmd.ExecuteNonQuery(); - cmd.CommandText = "DROP TABLE " + tableName; - cmd.ExecuteNonQuery(); - cmd.CommandText = "DROP TYPE " + typeName; - cmd.ExecuteNonQuery(); - } + // Verify if the data was updated + cmd.CommandText = $"select * from {tableCoordPair.Name}"; + cmd.CommandType = CommandType.Text; + using SqlDataReader reader = cmd.ExecuteReader(); + using DataTable dbData = new(); + dbData.Load(reader); + Assert.Equal(1, dbData.Rows.Count); + Assert.Equal(x, dbData.Rows[0][0]); + Assert.Equal(y, dbData.Rows[0][1]); } #if !NETFRAMEWORK @@ -331,8 +290,6 @@ public static void TestParametersWithDatatablesTVPInsert() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestParametersWithSqlRecordsTVPInsert() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); - SqlGeography geog = SqlGeography.Point(43, -81, 4326); SqlMetaData[] metadata = new SqlMetaData[] @@ -353,181 +310,127 @@ public static void TestParametersWithSqlRecordsTVPInsert() record2, }; - using SqlConnection connection = new(builder.ConnectionString); - string procName = DataTestUtility.GetLongName("Proc"); - string typeName = DataTestUtility.GetShortName("Type"); - try - { - connection.Open(); - - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandText = $"CREATE TYPE {typeName} AS TABLE([Id] [uniqueidentifier] NULL, [geom] [geography] NULL)"; - cmd.ExecuteNonQuery(); + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); - cmd.CommandText = @$"CREATE PROCEDURE {procName} - @newRoads as {typeName} READONLY - AS - BEGIN - SELECT* FROM @newRoads - END"; - cmd.ExecuteNonQuery(); + using UserDefinedType udtGeographyTable = new(connection, "Type", "TABLE ([Id] [uniqueidentifier] NULL, [geom] [geography] NULL)"); + using StoredProcedure spSelectFromTvp = new(connection, "Proc", $"@newRoads as {udtGeographyTable.Name} READONLY AS SELECT * FROM @newRoads"); - } - using (SqlCommand cmd = connection.CreateCommand()) - { - // Update Data Using TVPs - cmd.CommandText = procName; - cmd.CommandType = CommandType.StoredProcedure; + using SqlCommand cmd = connection.CreateCommand(); - SqlParameter param = new SqlParameter("@newRoads", SqlDbType.Structured); - param.Value = featureInserts; - param.TypeName = typeName; + // Update Data Using TVPs + cmd.CommandText = spSelectFromTvp.Name; + cmd.CommandType = CommandType.StoredProcedure; - cmd.Parameters.Add(param); + SqlParameter param = new SqlParameter("@newRoads", SqlDbType.Structured); + param.Value = featureInserts; + param.TypeName = udtGeographyTable.Name; - using var reader = cmd.ExecuteReader(); + cmd.Parameters.Add(param); - Assert.True(reader.HasRows); + using var reader = cmd.ExecuteReader(); - int count = 0; - while (reader.Read()) - { - Assert.NotNull(reader[0]); - Assert.NotNull(reader[1]); - count++; - } + Assert.True(reader.HasRows); - Assert.Equal(2, count); - } - } - finally + int count = 0; + while (reader.Read()) { - using SqlCommand cmd = connection.CreateCommand(); - cmd.CommandText = "DROP PROCEDURE " + procName; - cmd.ExecuteNonQuery(); - cmd.CommandText = "DROP TYPE " + typeName; - cmd.ExecuteNonQuery(); + Assert.NotNull(reader[0]); + Assert.NotNull(reader[1]); + count++; } + + Assert.Equal(2, count); } [Trait("Category", "flaky")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestDateOnlyTVPDataTable_CommandSP() { - string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlyTVP"); - string spName = DataTestUtility.GetLongName("spTestDateOnlyTVP"); - 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; - - DataTable dtTest = new(); - dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly))); - dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly))); - var dataRow = dtTest.NewRow(); - dataRow["DateColumn"] = new DateOnly(2023, 11, 15); - dataRow["TimeColumn"] = new TimeOnly(12, 30, 45); - dtTest.Rows.Add(dataRow); - - cmd.Parameters.Add(new SqlParameter - { - ParameterName = "@dates", - SqlDbType = SqlDbType.Structured, - TypeName = tableTypeName, - Value = dtTest, - }); + using SqlConnection connection = new(s_connString); - cmd.ExecuteNonQuery(); - } - } - finally + connection.Open(); + + using UserDefinedType udtTableType = new(connection, "UDTTTestDateOnlyTVP", "TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"); + using StoredProcedure storedProcedure = new(connection, "spTestDateOnlyTVP", $"(@dates {udtTableType.Name} READONLY) AS SELECT COUNT(*) FROM @dates"); + using SqlCommand cmd = connection.CreateCommand(); + + cmd.CommandText = storedProcedure.Name; + cmd.CommandType = CommandType.StoredProcedure; + + DataTable dtTest = new(); + dtTest.Columns.Add(new DataColumn("DateColumn", typeof(DateOnly))); + dtTest.Columns.Add(new DataColumn("TimeColumn", typeof(TimeOnly))); + + DataRow dataRow = dtTest.NewRow(); + dataRow["DateColumn"] = new DateOnly(2023, 11, 15); + dataRow["TimeColumn"] = new TimeOnly(12, 30, 45); + dtTest.Rows.Add(dataRow); + + cmd.Parameters.Add(new SqlParameter { - DataTestUtility.DropStoredProcedure(connection, spName); - DataTestUtility.DropUserDefinedType(connection, tableTypeName); - } + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = udtTableType.Name, + Value = dtTest, + }); + + cmd.ExecuteNonQuery(); } [Trait("Category", "flaky")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestDateOnlyTVPSqlDataRecord_CommandSP() { - string tableTypeName = "[dbo]." + DataTestUtility.GetLongName("UDTTTestDateOnlySqlDataRecordTVP"); - string spName = DataTestUtility.GetLongName("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; + using SqlConnection connection = new(s_connString); - SqlMetaData[] metadata = new SqlMetaData[] - { - new SqlMetaData("DateColumn", SqlDbType.Date), - new SqlMetaData("TimeColumn", SqlDbType.Time) - }; + connection.Open(); - SqlDataRecord record1 = new SqlDataRecord(metadata); - record1.SetValues(new DateOnly(2023, 11, 15), new TimeOnly(12, 30, 45)); + using UserDefinedType udtTableType = new(connection, "UDTTTestDateOnlySqlDataRecordTVP", "TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"); + using StoredProcedure storedProcedure = new(connection, "spTestDateOnlySqlDataRecordTVP", $"(@dates {udtTableType.Name} READONLY) AS SELECT COUNT(*) FROM @dates"); + using SqlCommand cmd = connection.CreateCommand(); - SqlDataRecord record2 = new SqlDataRecord(metadata); - record2.SetValues(new DateOnly(2025, 11, 15), new TimeOnly(13, 31, 46)); + cmd.CommandText = storedProcedure.Name; + cmd.CommandType = CommandType.StoredProcedure; - IList featureInserts = new List + 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, - }); + cmd.Parameters.Add(new SqlParameter + { + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = udtTableType.Name, + Value = featureInserts, + }); - using var reader = cmd.ExecuteReader(); + using var reader = cmd.ExecuteReader(); - Assert.True(reader.HasRows); + Assert.True(reader.HasRows); - int count = 0; - while (reader.Read()) - { - Assert.NotNull(reader[0]); - count++; - } - - Assert.Equal(1, count); - } - } - finally + int count = 0; + while (reader.Read()) { - DataTestUtility.DropStoredProcedure(connection, spName); - DataTestUtility.DropUserDefinedType(connection, tableTypeName); + Assert.NotNull(reader[0]); + count++; } + + Assert.Equal(1, count); } #endif @@ -587,33 +490,29 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); - string tableName = DataTestUtility.GetLongName("TestDecimalParameterCMD"); - using SqlConnection connection = InitialDatabaseTable(connectionString, tableName); - try + using SqlConnection connection = new(connectionString); + connection.Open(); + + using Table decimalTable = new(connection, "TestDecimalParameterCMD", "(Id INT, Value Decimal(38, 2))"); + + using (SqlCommand cmd = connection.CreateCommand()) { - using (SqlCommand cmd = connection.CreateCommand()) - { - appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; - var p = new SqlParameter("@Value", null) - { - Precision = 18, - Scale = 2 - }; - cmd.Parameters.Add(p); - for (int i = 0; i < s_testValues.Length; i++) - { - p.Value = s_testValues[i]; - cmd.CommandText = $"INSERT INTO {tableName} (Id, [Value]) VALUES({i}, @Value)"; - cmd.ExecuteNonQuery(); - } + var p = new SqlParameter("@Value", null) + { + Precision = 18, + Scale = 2 + }; + cmd.Parameters.Add(p); + for (int i = 0; i < s_testValues.Length; i++) + { + p.Value = s_testValues[i]; + cmd.CommandText = $"INSERT INTO {decimalTable.Name} (Id, [Value]) VALUES({i}, @Value)"; + cmd.ExecuteNonQuery(); } - Assert.True(ValidateInsertedValues(connection, tableName, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); - } - finally - { - DataTestUtility.DropTable(connection, tableName); } + Assert.True(ValidateInsertedValues(connection, decimalTable.Name, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); } [Theory] @@ -622,33 +521,29 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString, { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); - string tableName = DataTestUtility.GetLongName("TestDecimalParameterBC"); - using SqlConnection connection = InitialDatabaseTable(connectionString, tableName); - try + using SqlConnection connection = new(connectionString); + connection.Open(); + + using Table decimalTable = new(connection, "TestDecimalParameterCMD", "(Id INT, Value Decimal(38, 2))"); + + using (SqlBulkCopy bulkCopy = new(connection)) { - using (SqlBulkCopy bulkCopy = new(connection)) + using DataTable table = new(decimalTable.Name); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("Value", typeof(decimal)); + for (int i = 0; i < s_testValues.Length; i++) { - DataTable table = new(tableName); - table.Columns.Add("Id", typeof(int)); - table.Columns.Add("Value", typeof(decimal)); - for (int i = 0; i < s_testValues.Length; i++) - { - DataRow newRow = table.NewRow(); - newRow["Id"] = i; - newRow["Value"] = s_testValues[i]; - table.Rows.Add(newRow); - } - - bulkCopy.DestinationTableName = tableName; - appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; - bulkCopy.WriteToServer(table); + DataRow newRow = table.NewRow(); + newRow["Id"] = i; + newRow["Value"] = s_testValues[i]; + table.Rows.Add(newRow); } - Assert.True(ValidateInsertedValues(connection, tableName, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); - } - finally - { - DataTestUtility.DropTable(connection, tableName); + + bulkCopy.DestinationTableName = decimalTable.Name; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; + bulkCopy.WriteToServer(table); } + Assert.True(ValidateInsertedValues(connection, decimalTable.Name, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); } // Synapse: Parse error at line: 2, column: 8: Incorrect syntax near 'TYPE'. @@ -659,45 +554,40 @@ public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); - string tableName = DataTestUtility.GetLongName("TestDecimalParameterBC"); - string tableTypeName = DataTestUtility.GetLongName("UDTTTestDecimalParameterBC"); - string spName = DataTestUtility.GetLongName("spTestDecimalParameterBC"); - using SqlConnection connection = InitialDatabaseUDTT(connectionString, tableName, tableTypeName, spName); - try + using SqlConnection connection = new(connectionString); + connection.Open(); + + using Table decimalTable = new(connection, "TestDecimalParameterBC", "(Id INT, Value Decimal(38, 2))"); + using UserDefinedType udtDecimal = new(connection, "UDTTTestDecimalParameterBC", "TABLE (Id INT, Value Decimal(38, 2))"); + using StoredProcedure insertDecimalSp = new(connection, "spTestDecimalParameterBC", + $"(@tvp {udtDecimal.Name} READONLY) AS \n INSERT INTO {decimalTable.Name} (Id, Value) SELECT * FROM @tvp ORDER BY Id"); + + using (SqlCommand cmd = connection.CreateCommand()) { - using (SqlCommand cmd = connection.CreateCommand()) + var p = new SqlParameter("@tvp", SqlDbType.Structured) { - var p = new SqlParameter("@tvp", SqlDbType.Structured) - { - TypeName = $"dbo.{tableTypeName}" - }; - cmd.CommandText = spName; - cmd.CommandType = CommandType.StoredProcedure; - cmd.Parameters.Add(p); - - DataTable table = new(tableName); - table.Columns.Add("Id", typeof(int)); - table.Columns.Add("Value", typeof(decimal)); - for (int i = 0; i < s_testValues.Length; i++) - { - DataRow newRow = table.NewRow(); - newRow["Id"] = i; - newRow["Value"] = s_testValues[i]; - table.Rows.Add(newRow); - } - p.Value = table; - appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; - cmd.ExecuteNonQuery(); + TypeName = udtDecimal.Name + }; + cmd.CommandText = insertDecimalSp.Name; + cmd.CommandType = CommandType.StoredProcedure; + cmd.Parameters.Add(p); + + DataTable table = new(decimalTable.Name); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("Value", typeof(decimal)); + for (int i = 0; i < s_testValues.Length; i++) + { + DataRow newRow = table.NewRow(); + newRow["Id"] = i; + newRow["Value"] = s_testValues[i]; + table.Rows.Add(newRow); } - // TVP always rounds data without attention to the configuration. - Assert.True(ValidateInsertedValues(connection, tableName, false && truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); - } - finally - { - DataTestUtility.DropTable(connection, tableName); - DataTestUtility.DropStoredProcedure(connection, spName); - DataTestUtility.DropUserDefinedType(connection, tableTypeName); + p.Value = table; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; + cmd.ExecuteNonQuery(); } + // TVP always rounds data without attention to the configuration. + Assert.True(ValidateInsertedValues(connection, decimalTable.Name, false && truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); } #region Decimal parameter test setup @@ -706,35 +596,6 @@ public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool private static readonly decimal[] s_expectedTruncatedValues = new[] { 4210862852.86m, 19.15m, 19.15m, 19.15m }; private const string TruncateDecimalSwitch = "Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; - private static SqlConnection InitialDatabaseUDTT(string cnnString, string tableName, string tableTypeName, string spName) - { - SqlConnection connection = new(cnnString); - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TABLE {tableName} (Id INT, Value Decimal(38, 2)) \n"; - cmd.CommandText += $"CREATE TYPE {tableTypeName} AS TABLE (Id INT, Value Decimal(38, 2)) "; - cmd.ExecuteNonQuery(); - cmd.CommandText = $"CREATE PROCEDURE {spName} (@tvp {tableTypeName} READONLY) AS \n INSERT INTO {tableName} (Id, Value) SELECT * FROM @tvp ORDER BY Id"; - cmd.ExecuteNonQuery(); - } - return connection; - } - - private static SqlConnection InitialDatabaseTable(string cnnString, string tableName) - { - SqlConnection connection = new(cnnString); - connection.Open(); - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TABLE {tableName} (Id INT, Value Decimal(38, 2))"; - cmd.ExecuteNonQuery(); - } - return connection; - } - private static bool ValidateInsertedValues(SqlConnection connection, string tableName, bool truncateScaledDecimal) { bool exceptionHit; @@ -748,7 +609,7 @@ private static bool ValidateInsertedValues(SqlConnection connection, string tabl cmd.CommandText = $"SELECT [Value] FROM {tableName} ORDER BY Id ASC"; cmd.CommandType = CommandType.Text; using SqlDataReader reader = cmd.ExecuteReader(); - DataTable dbData = new(); + using DataTable dbData = new(); dbData.Load(reader); Assert.Equal(expectedValues.Length, dbData.Rows.Count); for (int i = 0; i < expectedValues.Length; i++) @@ -787,15 +648,6 @@ private enum MyEnum B = 2 } - private static void ExecuteNonQueryCommand(string connectionString, string cmdText) - { - using SqlConnection conn = new(connectionString); - using SqlCommand cmd = conn.CreateCommand(); - conn.Open(); - cmd.CommandText = cmdText; - cmd.ExecuteNonQuery(); - } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] private static void EnableOptimizedParameterBinding_ParametersAreUsedByName() { @@ -946,36 +798,20 @@ private static void EnableOptimizedParameterBinding_ReturnSucceeds() { int firstInput = 12; - string sprocName = DataTestUtility.GetShortName("P"); - // input, output - string createSprocQuery = - "CREATE PROCEDURE " + sprocName + " @in int " + - "AS " + - "RETURN(@in)"; - - string dropSprocQuery = "DROP PROCEDURE " + sprocName; - - try - { - ExecuteNonQueryCommand(DataTestUtility.TCPConnectionString, createSprocQuery); + using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); + connection.Open(); - using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); - connection.Open(); + using StoredProcedure sproc = new(connection, "P", "@in int AS RETURN(@in)"); - using var command = new SqlCommand(sprocName, connection) { CommandType = CommandType.StoredProcedure }; - command.EnableOptimizedParameterBinding = true; - command.Parameters.AddWithValue("@in", firstInput); - SqlParameter returnParameter = command.Parameters.AddWithValue("@retval", 0); - returnParameter.Direction = ParameterDirection.ReturnValue; + using var command = new SqlCommand(sproc.Name, connection) { CommandType = CommandType.StoredProcedure }; + command.EnableOptimizedParameterBinding = true; + command.Parameters.AddWithValue("@in", firstInput); + SqlParameter returnParameter = command.Parameters.AddWithValue("@retval", 0); + returnParameter.Direction = ParameterDirection.ReturnValue; - command.ExecuteNonQuery(); + command.ExecuteNonQuery(); - Assert.Equal(firstInput, Convert.ToInt32(returnParameter.Value)); - } - finally - { - ExecuteNonQueryCommand(DataTestUtility.TCPConnectionString, dropSprocQuery); - } + Assert.Equal(firstInput, Convert.ToInt32(returnParameter.Value)); } [SkipOnPlatform(TestPlatforms.OSX, "Flaky on macOS: https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/42351")] @@ -999,7 +835,7 @@ public static void ClosedConnection_SqlParameterValueTest() private static void RunParameterTest() { - var cancellationToken = new CancellationTokenSource(50); + using var cancellationToken = new CancellationTokenSource(50); var expectedGuid = Guid.NewGuid(); using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs index 837ce96e76..621784bafa 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.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 System; using System.Data; -using System.Data.Common; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SqlBulkCopyTests @@ -15,7 +14,6 @@ public class SqlGraphTables public void WriteToServer_CopyToSqlGraphNodeTable_Succeeds() { string connectionString = DataTestUtility.TCPConnectionString; - string destinationTable = DataTestUtility.GetShortName("SqlGraphNodeTable"); using SqlConnection dstConn = new SqlConnection(connectionString); using DataTable nodes = new DataTable() @@ -30,20 +28,12 @@ public void WriteToServer_CopyToSqlGraphNodeTable_Succeeds() nodes.Rows.Add($"Name {i}"); } - try - { - DataTestUtility.CreateTable(dstConn, destinationTable, "(Id INT PRIMARY KEY IDENTITY(1,1), [Name] VARCHAR(100)) AS NODE"); - - using SqlBulkCopy nodeCopy = new SqlBulkCopy(dstConn); + using Table dstNodeTable = new(dstConn, "SqlGraphNodeTable", "(Id INT PRIMARY KEY IDENTITY(1,1), [Name] VARCHAR(100)) AS NODE"); + using SqlBulkCopy nodeCopy = new SqlBulkCopy(dstConn); - nodeCopy.DestinationTableName = destinationTable; - nodeCopy.ColumnMappings.Add("Name", "Name"); - nodeCopy.WriteToServer(nodes); - } - finally - { - DataTestUtility.DropTable(dstConn, destinationTable); - } + nodeCopy.DestinationTableName = dstNodeTable.Name; + nodeCopy.ColumnMappings.Add("Name", "Name"); + nodeCopy.WriteToServer(nodes); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 74e6aaa277..490bfe74b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -1,10 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -32,7 +33,6 @@ public DateTimeOffsetVariableScale(DateTimeOffset dateTimeOffset, int scale) public class UdtDateTimeOffsetTest { private readonly string _connectionString = null; - private readonly string _udtTableType = DataTestUtility.GetLongName("DataTimeOffsetTableType"); private readonly ITestOutputHelper _testOutputHelper; public UdtDateTimeOffsetTest(ITestOutputHelper testOutputHelper) @@ -47,30 +47,24 @@ public void SelectFromSqlParameterShouldSucceed() { using SqlConnection connection = new(_connectionString); connection.Open(); - SetupUserDefinedTableType(connection, _udtTableType); - try + using UserDefinedType udtTableType = new(connection, nameof(SelectFromSqlParameterShouldSucceed), "TABLE ([Value] DATETIMEOFFSET(1) NOT NULL)"); + + DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, 500, TimeSpan.Zero); + var param = new SqlParameter { - DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, 500, TimeSpan.Zero); - var param = new SqlParameter - { - ParameterName = "@params", - SqlDbType = SqlDbType.Structured, - TypeName = $"dbo.{_udtTableType}", - Value = new DateTimeOffsetList[] { new DateTimeOffsetList(dateTimeOffset) } - }; + ParameterName = "@params", + SqlDbType = SqlDbType.Structured, + TypeName = udtTableType.Name, + Value = new DateTimeOffsetList[] { new DateTimeOffsetList(dateTimeOffset) } + }; - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "SELECT * FROM @params"; - cmd.Parameters.Add(param); - var result = cmd.ExecuteScalar(); - Assert.Equal(dateTimeOffset, result); - } - } - finally + using (var cmd = connection.CreateCommand()) { - DataTestUtility.DropUserDefinedType(connection, _udtTableType); + cmd.CommandText = "SELECT * FROM @params"; + cmd.Parameters.Add(param); + var result = cmd.ExecuteScalar(); + Assert.Equal(dateTimeOffset, result); } } @@ -87,7 +81,8 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() for (int scale = fromScale; scale <= toScale; scale++) { - string tvpTypeName = DataTestUtility.GetLongName("tvpType"); // Need a unique name per scale, else we get errors. See https://github.com/dotnet/SqlClient/issues/3011 + // Need a unique name per scale, else we get errors. See https://github.com/dotnet/SqlClient/issues/3011 + using UserDefinedType udtTableType = new(connection, "tvpType", $"TABLE ([Value] DATETIMEOFFSET({scale}) NOT NULL)"); DateTimeOffset dateTimeOffset = new DateTimeOffset(2024, 1, 1, 23, 59, 59, TimeSpan.Zero); @@ -95,63 +90,32 @@ public void DateTimeOffsetAllScalesTestShouldSucceed() TimeSpan subSeconds = TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond / Math.Pow(10, scale))); dateTimeOffset = dateTimeOffset.Add(subSeconds); - DataTestUtility.DropUserDefinedType(connection, tvpTypeName); + var param = new SqlParameter + { + ParameterName = "@params", + SqlDbType = SqlDbType.Structured, + Scale = (byte)scale, + TypeName = udtTableType.Name, + Value = new DateTimeOffsetVariableScale[] { new DateTimeOffsetVariableScale(dateTimeOffset, scale) } + }; - try + using (var cmd = connection.CreateCommand()) { - SetupDateTimeOffsetTableType(connection, tvpTypeName, scale); + cmd.CommandText = "SELECT * FROM @params"; + cmd.Parameters.Add(param); - var param = new SqlParameter + object result = null; + try { - ParameterName = "@params", - SqlDbType = SqlDbType.Structured, - Scale = (byte)scale, - TypeName = $"dbo.{tvpTypeName}", - Value = new DateTimeOffsetVariableScale[] { new DateTimeOffsetVariableScale(dateTimeOffset, scale) } - }; - - using (var cmd = connection.CreateCommand()) + result = cmd.ExecuteScalar(); + Assert.Equal(dateTimeOffset, result); + } + catch (Exception) { - cmd.CommandText = "SELECT * FROM @params"; - cmd.Parameters.Add(param); - - object result = null; - try - { - result = cmd.ExecuteScalar(); - Assert.Equal(dateTimeOffset, result); - } - catch (Exception) - { - _testOutputHelper.WriteLine($"{DateTime.UtcNow:O}: Failed for scale {scale} DateTimeOffset: {dateTimeOffset} Result: {result ?? "No result"}"); - throw; - } + _testOutputHelper.WriteLine($"{DateTime.UtcNow:O}: Failed for scale {scale} DateTimeOffset: {dateTimeOffset} Result: {result ?? "No result"}"); + throw; } } - finally - { - DataTestUtility.DropUserDefinedType(connection, tvpTypeName); - } - } - } - - private static void SetupUserDefinedTableType(SqlConnection connection, string tableTypeName) - { - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([Value] DATETIMEOFFSET(1) NOT NULL) "; - cmd.ExecuteNonQuery(); - } - } - - private static void SetupDateTimeOffsetTableType(SqlConnection connection, string tableTypeName, int scale) - { - using (SqlCommand cmd = connection.CreateCommand()) - { - cmd.CommandType = CommandType.Text; - cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([Value] DATETIMEOFFSET({scale}) NOT NULL) "; - cmd.ExecuteNonQuery(); } } } From ea0b777047203d541dd1e310a4872c4c9cc44176 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 17 Apr 2026 16:19:04 +1000 Subject: [PATCH 048/298] remove more duplicate lookups (#4089) * remove duplicate lookups in EnclaveDelegate.Crypto.cs * Use TryGet methods to avoid double lookups Replace ContainsKey + indexer patterns with TryGetParsetableValue (for connection options) and TryGetValue (for _appDomainKeyHash) to avoid double dictionary lookups, remove variable shadowing, and make intent clearer. Existing logic and assertions are preserved. --- .../SqlConnectionPoolGroupProviderInfo.cs | 4 ++-- .../Data/SqlClient/EnclaveDelegate.Crypto.cs | 15 ++++++--------- .../Data/SqlClient/SqlDependencyListener.cs | 3 +-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs index b946d839a8..db2a17bd56 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs @@ -105,8 +105,8 @@ private PermissionSet CreateFailoverPermission(SqlConnectionString userConnectio // the server, we will use that name over what was specified // in the original connection string. - if (userConnectionOptions.ContainsKey(DbConnectionStringKeywords.FailoverPartner) && - userConnectionOptions[DbConnectionStringKeywords.FailoverPartner] == null) + if (userConnectionOptions.TryGetParsetableValue(DbConnectionStringKeywords.FailoverPartner, out string failoverPartnerValue) && + failoverPartnerValue == null) { keywordToReplace = DbConnectionStringKeywords.DataSource; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs index db1bdea90f..8b461087bd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs @@ -92,21 +92,18 @@ private SqlColumnEncryptionEnclaveProvider GetEnclaveProvider(SqlConnectionAttes switch (attestationProtocol) { case SqlConnectionAttestationProtocol.AAS: - AzureAttestationEnclaveProvider azureAttestationEnclaveProvider = new AzureAttestationEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = azureAttestationEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; + sqlColumnEncryptionEnclaveProvider = new AzureAttestationEnclaveProvider(); + s_enclaveProviders[attestationProtocol] = sqlColumnEncryptionEnclaveProvider; break; case SqlConnectionAttestationProtocol.HGS: - HostGuardianServiceEnclaveProvider hostGuardianServiceEnclaveProvider = new HostGuardianServiceEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = hostGuardianServiceEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; + sqlColumnEncryptionEnclaveProvider = new HostGuardianServiceEnclaveProvider(); + s_enclaveProviders[attestationProtocol] = sqlColumnEncryptionEnclaveProvider; break; case SqlConnectionAttestationProtocol.None: - NoneAttestationEnclaveProvider noneAttestationEnclaveProvider = new NoneAttestationEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = noneAttestationEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; + sqlColumnEncryptionEnclaveProvider = new NoneAttestationEnclaveProvider(); + s_enclaveProviders[attestationProtocol] = sqlColumnEncryptionEnclaveProvider; break; default: 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 e97d8589a3..b3d5191e1a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs @@ -837,9 +837,8 @@ internal bool Stop(string appDomainKey, out bool appDomainStop) // If null, then this was called from SqlDependencyProcessDispatcher, we ignore appDomainKeyHash. lock (_appDomainKeyHash) { - if (_appDomainKeyHash.ContainsKey(appDomainKey)) + if (_appDomainKeyHash.TryGetValue(appDomainKey, out int value)) { // Do nothing if AppDomain did not call Start! - int value = _appDomainKeyHash[appDomainKey]; Debug.Assert(value > 0, "Unexpected count for appDomainKey"); SqlClientEventSource.Log.TryNotificationTraceEvent(" _appDomainKeyHash contained AppDomainKey: '{0}', pre-decrement Count: '{1}'.", appDomainKey, value); From b0b17f7a2dd15d918d40c99dfef894bfabc07f0c Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 18 Apr 2026 05:35:15 +1000 Subject: [PATCH 049/298] remove some redundant null checks (#4091) --- .../SqlClient/Reliability/SqlConfigurableRetryLogicLoader.cs | 2 +- .../src/Microsoft/Data/SqlClient/SqlParameter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 dfc7861dc3..96eadb9d56 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 @@ -317,7 +317,7 @@ private static ICollection SplitErrorNumberList(string list) if (!string.IsNullOrEmpty(list)) { string[] parts = list.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - if (parts != null && parts.Length > 0) + if (parts.Length > 0) { HashSet set = new HashSet(); for (int index = 0; index < parts.Length; index++) 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 13b71c2a29..621413f9c1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -1261,7 +1261,7 @@ private void GetActualFieldsAndProperties(out List fields, // set up primary key as unique key list // do this prior to general metadata loop to favor the primary key - if (dt.PrimaryKey != null && 0 < dt.PrimaryKey.Length) + if (dt.PrimaryKey.Length > 0) { foreach (DataColumn col in dt.PrimaryKey) { From ed1661feb1999ea4562a28efbcbe9af7b74345fb Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Fri, 17 Apr 2026 22:37:35 +0100 Subject: [PATCH 050/298] Add conditional ProjectReference to Microsoft.SqlServer.Server (#4003) This enables builds via Visual Studio --- .../src/Microsoft.Data.SqlClient.csproj | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 2f7c726500..31a13c5801 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -144,7 +144,6 @@ - @@ -171,6 +170,17 @@ + + + + + + + + From e0176e1ff1b7613ed0edebd73ca92496ceab023d Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 18 Apr 2026 07:56:34 +1000 Subject: [PATCH 051/298] fix null ref in SqlDataReader (#4159) --- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 2 +- .../SQL/DataReaderTest/DataReaderTest.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 0809f322e2..d0bd8dc247 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -2137,7 +2137,7 @@ override public long GetChars(int i, long dataIndex, char[] buffer, int bufferIn // if bad buffer index, throw if ((bufferIndex < 0) || (buffer != null && bufferIndex >= buffer.Length)) { - throw ADP.InvalidDestinationBufferIndex(buffer.Length, bufferIndex, nameof(bufferIndex)); + throw ADP.InvalidDestinationBufferIndex(buffer?.Length ?? 0, bufferIndex, nameof(bufferIndex)); } // if there is not enough room in the buffer for data 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 b21626aaad..2fbf2714f9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -741,6 +741,22 @@ DROP TABLE IF EXISTS [{tableName}] } } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task GetCharsSequentialAccess_NullBufferNegativeBufferIndex_ThrowsArgumentOutOfRange() + { + using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using var command = connection.CreateCommand(); + command.CommandText = "SELECT CONVERT(NVARCHAR(MAX), 'test')"; + + using var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + Assert.True(await reader.ReadAsync()); + + var ex = Assert.Throws(() => reader.GetChars(0, 0, null, -1, 0)); + Assert.Equal("bufferIndex", ex.ParamName); + } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static async Task CanGetCharsSequentially() { From eacf112fa410e9c87827e6a88b87bb5c0e87d700 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 20 Apr 2026 02:36:45 -0300 Subject: [PATCH 052/298] Move symbol publishing into a dedicated stage and refactor build artifacts (#4175) --- .../onebranch-pipeline-design.instructions.md | 30 +- .../jobs/build-signed-csproj-package-job.yml | 98 ++--- .../build-signed-sqlclient-package-job.yml | 106 +++--- .../jobs/publish-nuget-package-job.yml | 21 +- .../onebranch/jobs/publish-symbols-job.yml | 95 +++++ .../jobs/validate-signed-package-job.yml | 21 +- .../onebranch/scripts/publish-symbols.ps1 | 236 ++++++++++++ .../onebranch/scripts/tests/README.md | 43 +++ .../scripts/tests/publish-symbols.Tests.ps1 | 356 ++++++++++++++++++ .../onebranch/sqlclient-non-official.yml | 31 +- .../onebranch/sqlclient-official.yml | 31 +- .../onebranch/stages/build-stages.yml | 64 ---- .../stages/publish-symbols-stage.yml | 222 +++++++++++ .../onebranch/stages/release-stages.yml | 22 +- .../copy-apiscan-files-sqlclient-step.yml | 39 -- .../onebranch/steps/esrp-dll-signing-step.yml | 7 +- .../steps/esrp-nuget-signing-step.yml | 5 +- .../onebranch/steps/pack-csproj-step.yml | 4 +- .../onebranch/steps/pack-sqlclient-step.yml | 4 +- .../onebranch/steps/publish-symbols-step.yml | 155 ++++---- .../onebranch/variables/common-variables.yml | 22 +- .../variables/onebranch-variables.yml | 8 +- 22 files changed, 1270 insertions(+), 350 deletions(-) create mode 100644 eng/pipelines/onebranch/jobs/publish-symbols-job.yml create mode 100644 eng/pipelines/onebranch/scripts/publish-symbols.ps1 create mode 100644 eng/pipelines/onebranch/scripts/tests/README.md create mode 100644 eng/pipelines/onebranch/scripts/tests/publish-symbols.Tests.ps1 create mode 100644 eng/pipelines/onebranch/stages/publish-symbols-stage.yml delete mode 100644 eng/pipelines/onebranch/steps/copy-apiscan-files-sqlclient-step.yml diff --git a/.github/instructions/onebranch-pipeline-design.instructions.md b/.github/instructions/onebranch-pipeline-design.instructions.md index f2319ee5b2..0860d5db52 100644 --- a/.github/instructions/onebranch-pipeline-design.instructions.md +++ b/.github/instructions/onebranch-pipeline-design.instructions.md @@ -36,6 +36,8 @@ Defined in `stages/build-stages.yml`. Four build stages plus validation, ordered - **`build_addons`** (Stage 4) — AKV Provider; `dependsOn: build_dependent`; downloads SqlClient + Abstractions + Logging artifacts - **`mds_package_validation`** — Validates signed SqlClient package; `dependsOn: build_dependent`; runs in parallel with Stage 4 +Each build job copies PDB files into `$(JOB_OUTPUT)/symbols/` so they are included in the auto-published pipeline artifact alongside the NuGet packages in `$(JOB_OUTPUT)/packages/`. + Stage conditional rules: - Wrap stages/jobs in `${{ if }}` compile-time conditionals based on build parameters - `buildSqlClient` controls Stages 2, 3, validation, and Logging (when AKV is disabled) @@ -45,10 +47,11 @@ Stage conditional rules: ## Job Templates -- **`build-signed-csproj-package-job.yml`** — Generic job for csproj-based packages (Logging, SqlServer.Server, Abstractions, Azure, AKV Provider). Flow: Build DLLs → ESRP DLL signing → NuGet pack (`NoBuild=true`) → ESRP NuGet signing -- **`build-signed-sqlclient-package-job.yml`** — SqlClient-specific job (nuspec-based). Flow: Build all configurations → ESRP DLL signing (main + resource DLLs) → NuGet pack via nuspec → ESRP NuGet signing +- **`build-signed-csproj-package-job.yml`** — Generic job for csproj-based packages (Logging, SqlServer.Server, Abstractions, Azure, AKV Provider). Flow: Build DLLs → ESRP DLL signing → NuGet pack (`NoBuild=true`) → ESRP NuGet signing → Copy PDBs to artifact +- **`build-signed-sqlclient-package-job.yml`** — SqlClient-specific job (nuspec-based). Flow: Build all configurations → ESRP DLL signing (main + resource DLLs) → NuGet pack via nuspec → ESRP NuGet signing → Copy PDBs to artifact - **`validate-signed-package-job.yml`** — Validates signed MDS package (signature, strong names, folder structure, target frameworks) - **`publish-nuget-package-job.yml`** — Reusable release job using OneBranch `templateContext.type: releaseJob` with `inputs` for artifact download; pushes via `NuGetCommand@2` +- **`publish-symbols-job.yml`** — Reusable symbols job: downloads a build artifact, locates PDBs under `symbols/`, and invokes `publish-symbols-step.yml` When adding a new csproj-based package: - Use `build-signed-csproj-package-job.yml` with appropriate `packageName`, `packageFullName`, `versionProperties`, and `downloadArtifacts` @@ -56,6 +59,18 @@ When adding a new csproj-based package: - Add version variables to `variables/common-variables.yml` - Add artifact name variable to `variables/onebranch-variables.yml` +## Symbols Publishing Stage + +- Defined in `stages/publish-symbols-stage.yml`; produces stage `publish_symbols` +- Entire stage excluded at compile time when `publishSymbols` is false +- `dependsOn` is conditional based on which `build*` parameters are set, mirroring the build stage dependency graph +- One job per package (`publish-symbols-job.yml`), each downloading its build artifact and publishing PDBs from `symbols/` +- Each package's PDBs are published separately with unique artifact names and version information +- Build jobs copy PDBs into `$(JOB_OUTPUT)/symbols/` so they are included in the auto-published artifact +- The `publish-symbols-step.yml` accepts a `symbolsFolder` parameter to point at the downloaded PDB location +- The publish step calls an extracted `publish-symbols.ps1` script with structured error handling and diagnostic logging +- Symbols publishing credentials come from the `Symbols Publishing` variable group + ## Release Stage - Defined in `stages/release-stages.yml`; produces stage `release_production` (official) or `release_test` (non-official) via `stageNameSuffix` parameter @@ -98,8 +113,7 @@ When `isPreview` is true, pipeline resolves `effective*Version` variables to pre - When adding a new package, add GA version, preview version, and assembly file version entries Variable groups: -- `Release Variables` — release configuration (in `common-variables.yml`) -- `Symbols publishing` — symbol publishing credentials (in `common-variables.yml`) +- `Symbols Publishing` — symbol publishing credentials (in `onebranch-variables.yml`) - `ESRP Federated Creds (AME)` — ESRP signing credentials (in `common-variables.yml`) ## Code Signing (ESRP) @@ -115,7 +129,7 @@ Variable groups: - TSA: enabled only in official pipeline; disabled in non-official to avoid spurious alerts - ApiScan: enabled in both; currently `break: false` pending package registration -- Each build job sets `ob_sdl_apiscan_*` variables pointing to `$(Build.SourcesDirectory)/apiScan//` +- Each build job sets `ob_sdl_apiscan_softwareFolder` to `$(JOB_OUTPUT)/assemblies` and `ob_sdl_apiscan_symbolsFolder` to `$(JOB_OUTPUT)/symbols` - CodeQL, SBOM, Policheck (`break: true`): enabled in both pipelines - asyncSdl `enabled: false` in both; individual sub-tools (CredScan, BinSkim, Armory, Roslyn) configured underneath - Policheck exclusions: `$(REPO_ROOT)\.config\PolicheckExclusions.xml` @@ -123,7 +137,11 @@ Variable groups: ## Artifact Conventions -- `ob_outputDirectory` set to `$(PACK_OUTPUT)` (= `$(REPO_ROOT)/output`) — OneBranch auto-publishes this directory +- `ob_outputDirectory` set to `$(JOB_OUTPUT)` (= `$(REPO_ROOT)/output`) — OneBranch auto-publishes this directory +- Each published artifact uses subdirectories to separate file types: + - `assemblies/` — DLL assemblies for APIScan (preserving TFM folder structure) + - `packages/` — NuGet packages (`.nupkg`, `.snupkg`) + - `symbols/` — PDB symbol files (preserving TFM folder structure, shared by APIScan and symbol publishing) - Artifact names follow `drop__` — defined in `variables/onebranch-variables.yml` - Downstream jobs download artifacts via `DownloadPipelineArtifact@2` into `$(Build.SourcesDirectory)/packages` - Downloaded packages serve as a local NuGet source for `dotnet restore` diff --git a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml index 6dc72983cc..94e6438ef5 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml @@ -36,26 +36,6 @@ parameters: - name: signingEsrpConnectedServiceName type: string - # Symbols Publishing Parameters ------------------------------------------ - - - name: symbolsAzureSubscription - type: string - default: 'Symbols publishing Workload Identity federation service-ADO.Net' - - - name: symbolsPublishProjectName - type: string - default: 'Microsoft.Data.SqlClient.SNI' - - - name: symbolsPublishServer - type: string - - - name: symbolsPublishTokenUri - type: string - - - name: symbolsUploadAccount - type: string - default: 'SqlClientDrivers' - # OTHERS +===================================== # Short package name used in the job name, display strings, filesystem paths, and as a suffix for @@ -74,11 +54,6 @@ parameters: - name: packageFullName type: string - # The version of the package. This is used for symbol publishing. It is not used for the DLL or - # NuGet package versions since those are supplied via the versionProperties parameter. - - name: packageVersion - type: string - # The MSBuild build target in build.proj (e.g. BuildLogging). If not specified, defaults to # Build. - name: buildTarget @@ -106,10 +81,6 @@ parameters: - name: assemblyFileVersion type: string - # True to publish symbols to private and public servers. - - name: publishSymbols - type: boolean - # Optional list of pipeline artifacts to download before building. Each entry is an object # with 'artifactName' (the pipeline artifact name) and 'displayName' (used in the task label). # This replaces hard-coded packageName conditionals so callers declare their own dependencies. @@ -125,12 +96,12 @@ jobs: variables: # Inform OneBranch that files put in this directory should be uploaded as artifacts. - ob_outputDirectory: $(PACK_OUTPUT) + ob_outputDirectory: $(JOB_OUTPUT) # APIScan configuration for this Extension package ob_sdl_apiscan_enabled: true - ob_sdl_apiscan_softwareFolder: $(REPO_ROOT)/apiScan/${{ parameters.packageName }}/dlls - ob_sdl_apiscan_symbolsFolder: $(REPO_ROOT)/apiScan/${{ parameters.packageName }}/pdbs + ob_sdl_apiscan_softwareFolder: $(JOB_OUTPUT)/assemblies + ob_sdl_apiscan_symbolsFolder: $(JOB_OUTPUT)/symbols ob_sdl_apiscan_softwarename: ${{ parameters.packageFullName }} ob_sdl_apiscan_versionNumber: ${{ parameters.assemblyFileVersion }} @@ -149,7 +120,7 @@ jobs: displayName: Download ${{ artifact.displayName }} inputs: artifactName: ${{ artifact.artifactName }} - targetPath: $(REPO_ROOT)/packages + targetPath: $(JOB_INPUT) # Install the .NET SDK. - template: /eng/pipelines/steps/install-dotnet.yml@self @@ -179,26 +150,42 @@ jobs: authSignCertName: '${{ parameters.signingAuthSignCertName }}' esrpClientId: '${{ parameters.signingEsrpClientId }}' esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - pattern: '${{ parameters.packageFullName }}.dll' - - # Copy signed/unsigned DLLs and PDBs to APIScan folders. + # Minimatch pattern (multi-line, one pattern per line). + # **/ matches any nested directory (e.g. net8.0/, netstandard2.0/fr/). + # + # Matches: net8.0/.dll + # netstandard2.0/fr/.resources.dll + pattern: | + **/${{ parameters.packageFullName }}.dll + **/${{ parameters.packageFullName }}.resources.dll + + # Copy DLLs to the assemblies/ subdirectory for APIScan. - task: CopyFiles@2 displayName: Copy DLLs for APIScan inputs: SourceFolder: $(BUILD_OUTPUT)/Package/bin - Contents: "**/${{ parameters.packageFullName }}.dll" - TargetFolder: ${{ variables.ob_sdl_apiscan_softwareFolder }} + # Matches: .dll (main assembly) across all TFM subdirs + # .resources.dll (localized satellite assemblies in locale subdirs, if any) + Contents: | + **/${{ parameters.packageFullName }}.dll + **/${{ parameters.packageFullName }}.resources.dll + TargetFolder: $(JOB_OUTPUT)/assemblies # We must preserve the folder structure since our C# projects may produce multiple # identically named DLLs for different target frameworks (e.g. netstandard2.0, net5.0, # etc.), and we need to keep those separate for APIScan to work correctly. flattenFolders: false + # Copy PDBs into the output directory so they are included in the published pipeline + # artifact. The symbols publishing stage will download this artifact and publish PDBs + # for this package using the files under symbols/. - task: CopyFiles@2 - displayName: Copy PDBs for APIScan + displayName: Copy PDBs for APIScan and Symbols Publishing inputs: SourceFolder: $(BUILD_OUTPUT)/Package/bin - Contents: "**/${{ parameters.packageFullName }}.pdb" - TargetFolder: ${{ variables.ob_sdl_apiscan_symbolsFolder }} + # Matches: .pdb across all TFM subdirs. + # Note: Resource DLLs are resource-only satellite assemblies and do not produce PDBs. + Contents: '**/${{ parameters.packageFullName }}.pdb' + TargetFolder: $(JOB_OUTPUT)/symbols flattenFolders: false # Pack the signed DLLs into NuGet package (NoBuild=true). @@ -217,22 +204,13 @@ jobs: authSignCertName: '${{ parameters.signingAuthSignCertName }}' esrpClientId: '${{ parameters.signingEsrpClientId }}' esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - searchPath: $(PACK_OUTPUT) - searchPattern: '${{ parameters.packageFullName}}.*nupkg' - - # Publish symbols to servers - # @TODO: Get these parameters from variables/libraries - - ${{ if eq(parameters.publishSymbols, true) }}: - - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self - parameters: - artifactName: '${{ parameters.packageFullName }}_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.packageVersion }}_$(System.TimelineId)' - azureSubscription: '${{ parameters.symbolsAzureSubscription }}' - packageName: '${{ parameters.packageFullName }}' - publishProjectName: '${{ parameters.symbolsPublishProjectName }}' - publishServer: '${{ parameters.symbolsPublishServer }}' - publishToInternal: 'true' - publishToPublic: 'true' - publishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - searchPattern: '**/${{ parameters.packageFullName }}*.pdb' - uploadAccount: '${{ parameters.symbolsUploadAccount }}' - version: '${{ parameters.packageVersion }}' + searchPath: $(JOB_OUTPUT)/packages + # Minimatch pattern with extglob. + # [0-9] matches a single digit, anchoring to the version segment so + # similarly-prefixed package names are not matched. + # ?(s) is an extglob that optionally matches 's'. + # + # Matches: .1.0.0.nupkg + # .1.0.0-preview.1.snupkg + # Excludes: .SomeOther.1.0.0.nupkg + searchPattern: '${{ parameters.packageFullName }}.[0-9]*.?(s)nupkg' diff --git a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml index 752e459b84..5bf33f9c09 100644 --- a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml +++ b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml @@ -7,20 +7,11 @@ # Job that performs a build of Microsoft.Data.SqlClient using the build2.proj file. parameters: - - name: apiScanDllPath - type: string - - - name: apiScanPdbPath - type: string - # Whether this build is for an "official" OneBranch pipeline. This is used to enable ESRP signing # on the artifacts of this job. - name: isOfficial type: boolean - - name: publishSymbols - type: boolean - - name: signingAppRegistrationClientId type: string @@ -39,21 +30,6 @@ parameters: - name: signingEsrpConnectedServiceName type: string - - name: symbolsAzureSubscription - type: string - - - name: symbolsPublishProjectName - type: string - - - name: symbolsPublishServer - type: string - - - name: symbolsPublishTokenUri - type: string - - - name: symbolsUploadAccount - type: string - # Package Parameters - name: abstractionsArtifactName type: string @@ -80,9 +56,9 @@ jobs: type: windows variables: - ob_outputDirectory: '$(PACK_OUTPUT)' - ob_sdl_apiscan_softwareFolder: ${{ parameters.apiScanDllPath }} - ob_sdl_apiscan_symbolsFolder: ${{ parameters.apiScanPdbPath }} + ob_outputDirectory: '$(JOB_OUTPUT)' + ob_sdl_apiscan_softwareFolder: $(JOB_OUTPUT)/assemblies + ob_sdl_apiscan_symbolsFolder: $(JOB_OUTPUT)/symbols ob_sdl_apiscan_softwarename: 'Microsoft.Data.SqlClient' ob_sdl_apiscan_versionNumber: ${{ parameters.sqlClientAssemblyFileVersion }} @@ -102,13 +78,13 @@ jobs: displayName: Download Microsoft.Data.SqlClient.Extensions.Abstractions Artifact inputs: artifactName: '${{ parameters.abstractionsArtifactName }}' - targetPath: '$(REPO_ROOT)/packages' + targetPath: '$(JOB_INPUT)' - task: DownloadPipelineArtifact@2 displayName: Download Microsoft.Data.SqlClient.Extensions.Logging Artifact inputs: artifactName: '${{ parameters.loggingArtifactName }}' - targetPath: '$(REPO_ROOT)/packages' + targetPath: '$(JOB_INPUT)' # Install the .NET SDK - template: /eng/pipelines/steps/install-dotnet.yml@self @@ -127,13 +103,6 @@ jobs: loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' - # Copy the built DLLs and PDBs to the APIScan output folder for APIScanning post-build - - template: /eng/pipelines/onebranch/steps/copy-apiscan-files-sqlclient-step.yml@self - parameters: - dllPath: '${{ parameters.apiScanDllPath }}' - pdbPath: '${{ parameters.apiScanPdbPath }}' - referenceType: 'Package' - # Sign the DLLs - ${{ if parameters.isOfficial }}: - template: /eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml@self @@ -144,7 +113,43 @@ jobs: authSignCertName: '${{ parameters.signingAuthSignCertName }}' esrpClientId: '${{ parameters.signingEsrpClientId }}' esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - pattern: 'Microsoft.Data.SqlClient*.dll' + # Minimatch pattern (multi-line, one pattern per line). + # **/ matches any nested directory (e.g. net8.0/, net462/fr/). + # + # Matches: net8.0/Microsoft.Data.SqlClient.dll + # net462/fr/Microsoft.Data.SqlClient.resources.dll + # Excludes: net8.0/Microsoft.Data.SqlClient.SNI.dll (no ** path matches) + pattern: | + **/Microsoft.Data.SqlClient.dll + **/Microsoft.Data.SqlClient.resources.dll + + # Copy DLLs to the assemblies/ subdirectory for APIScan. + - task: CopyFiles@2 + displayName: Copy DLLs for APIScan + inputs: + SourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/Package-Release' + # Matches: Microsoft.Data.SqlClient.dll (main assembly) + # Microsoft.Data.SqlClient.resources.dll (localized satellite assemblies in locale subdirs) + # Excludes: Microsoft.Data.SqlClient.SNI*.dll (native SNI — not part of this package) + Contents: | + **/Microsoft.Data.SqlClient.dll + **/Microsoft.Data.SqlClient.resources.dll + TargetFolder: '$(JOB_OUTPUT)/assemblies' + flattenFolders: false + + # Copy PDBs into the output directory so they are included in the published pipeline + # artifact. The symbols publishing stage will download this artifact and publish PDBs + # for this package using the files under symbols/. + - task: CopyFiles@2 + displayName: Copy PDBs for APIScan and Symbols Publishing + inputs: + SourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/Package-Release' + # Matches: Microsoft.Data.SqlClient.pdb (main assembly symbols) + # Excludes: Microsoft.Data.SqlClient.SNI*.pdb (native SNI — symbols published separately) + # Note: Resource DLLs are resource-only satellite assemblies and do not produce PDBs. + Contents: '**/Microsoft.Data.SqlClient.pdb' + TargetFolder: '$(JOB_OUTPUT)/symbols' + flattenFolders: false # Package the build output into a NuGet package - template: /eng/pipelines/onebranch/steps/pack-sqlclient-step.yml @@ -163,20 +168,13 @@ jobs: authSignCertName: '${{ parameters.signingAuthSignCertName }}' esrpClientId: '${{ parameters.signingEsrpClientId }}' esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - searchPath: '$(PACK_OUTPUT)' - searchPattern: 'Microsoft.Data.SqlClient.*nupkg' - - - ${{ if parameters.publishSymbols }}: - - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self - parameters: - artifactName: 'Microsoft.Data.SqlClient_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.sqlClientPackageVersion }}_$(System.TimelineId)' - azureSubscription: '${{ parameters.symbolsAzureSubscription }}' - packageName: 'Microsoft.Data.SqlClient' - publishProjectName: '${{ parameters.symbolsPublishProjectName }}' - publishServer: '${{ parameters.symbolsPublishServer }}' - publishToInternal: true - publishToPublic: true - publishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - searchPattern: '**/Microsoft.Data.SqlClient*.pdb' # @TODO: This seems very heavy - uploadAccount: '${{ parameters.symbolsUploadAccount }}' - version: '${{ parameters.sqlClientPackageVersion }}' + searchPath: '$(JOB_OUTPUT)/packages' + # Minimatch pattern with extglob. + # [0-9] matches a single digit, anchoring to the version segment so + # names like Microsoft.Data.SqlClient.SNI are not matched. + # ?(s) is an extglob that optionally matches 's'. + # + # Matches: Microsoft.Data.SqlClient.6.1.0.nupkg + # Microsoft.Data.SqlClient.6.1.0.snupkg + # Excludes: Microsoft.Data.SqlClient.SNI.6.0.0.nupkg + searchPattern: 'Microsoft.Data.SqlClient.[0-9]*.?(s)nupkg' diff --git a/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml b/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml index b7a1d8f62e..21d36b9640 100644 --- a/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml +++ b/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml @@ -38,15 +38,18 @@ parameters: # artifact. For example, if we're publishing the SqlClient package, and the build job publishes a # pipeline artifact with the following structure: # - # drop_BuildAndTest_PackageReference/ - # ├── Microsoft.Data.SqlClient.5.0.0.nupkg - # ├── Microsoft.Data.SqlClient.5.0.0.snupkg - # ├── Microsoft.Data.SqlClient.Extensions.Abstractions.1.0.0.nupkg - # └── other-file.txt + # drop_build_independent_build_logging/ + # ├── packages/ + # │ ├── Microsoft.Data.SqlClient.5.0.0.nupkg + # │ └── Microsoft.Data.SqlClient.5.0.0.snupkg + # └── symbols/ + # └── ... # - # Then the packagePath should be 'Microsoft.Data.SqlClient.5.0.0.nupkg'. + # Then the packagePath should be 'packages/Microsoft.Data.SqlClient.5.0.0.nupkg'. # - # Defaults to '${{ parameters.packageName }}.*.nupkg' to match any version of the package. + # Defaults to 'packages/${{ parameters.packageName }}.*.nupkg' to match any version of the + # package. Only .nupkg files are pushed; .snupkg files are included automatically by NuGet + # when they exist alongside the .nupkg in the same directory. # - name: packagePath type: string @@ -65,13 +68,13 @@ jobs: variables: - name: ob_outputDirectory - value: $(PACK_OUTPUT) + value: $(JOB_OUTPUT) - name: artifactPath value: $(Pipeline.Workspace)/${{ parameters.artifactName }} - name: packageToPush - value: $(artifactPath)/${{ coalesce(parameters.packagePath, format('{0}.*.nupkg', parameters.packageName)) }} + value: $(artifactPath)/${{ coalesce(parameters.packagePath, format('packages/{0}.*.nupkg', parameters.packageName)) }} # Template context inputs are used to pass parameters to the deployment job since it doesn't # automatically download pipeline artifacts. diff --git a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml new file mode 100644 index 0000000000..21b20476cd --- /dev/null +++ b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml @@ -0,0 +1,95 @@ +################################################################################# +# 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. # +################################################################################# + +# Reusable job template for publishing symbols for a single package. Downloads the pipeline +# artifact produced by a build job, then invokes publish-symbols-step.yml to upload and publish +# the PDB files to the internal and public symbol servers. +# +# Each package's PDBs are published separately to maintain unique naming and versioning. +# PDBs are expected to be located under the 'symbols/' directory within the downloaded +# artifact. + +parameters: + # The pipeline artifact name to download (OneBranch naming: drop__). + - name: artifactName + type: string + + # Short package name used in the job name (e.g. Logging, SqlServer, SqlClient). + - name: packageName + type: string + + # The full NuGet package name (e.g. Microsoft.Data.SqlClient.Internal.Logging). + - name: packageFullName + type: string + + # The package version, used for symbol versioning. + - name: packageVersion + type: string + + # Pattern to use to search for PDB files to upload/publish, relative to the symbols folder. + - name: searchPattern + type: string + + # Symbols Publishing Parameters ------------------------------------------ + + - name: symbolsAzureSubscription + type: string + + - name: symbolsPublishProjectName + type: string + + - name: symbolsPublishServer + type: string + + - name: symbolsPublishTokenUri + type: string + + - name: symbolsUploadAccount + type: string + +jobs: + - job: publish_symbols_${{ parameters.packageName }} + displayName: 'Publish Symbols: ${{ parameters.packageFullName }}' + pool: + type: windows + + variables: + # OneBranch requires ob_outputDirectory to be set, even if this job produces no artifacts. + ob_outputDirectory: $(JOB_OUTPUT) + + # Disable SDL scanning — this job only uploads/publishes PDBs and produces no + # assemblies to scan. APIScan and BinSkim are handled by the build jobs. + ob_sdl_apiscan_enabled: false + ob_sdl_binskim_break: false + ob_sdl_binskim_enabled: false + + # Path where the downloaded artifact will be placed. + artifactPath: $(Pipeline.Workspace)/${{ parameters.artifactName }} + + # Path to the PDB files within the downloaded artifact. + symbolsPath: $(Pipeline.Workspace)/${{ parameters.artifactName }}/symbols + + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download ${{ parameters.packageFullName }} Artifact' + inputs: + artifactName: '${{ parameters.artifactName }}' + targetPath: '$(artifactPath)' + + - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self + parameters: + artifactName: '${{ parameters.packageFullName }}_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.packageVersion }}_$(System.TimelineId)_$(System.JobAttempt)' + azureSubscription: '${{ parameters.symbolsAzureSubscription }}' + packageName: '${{ parameters.packageFullName }}' + publishProjectName: '${{ parameters.symbolsPublishProjectName }}' + publishServer: '${{ parameters.symbolsPublishServer }}' + publishToInternal: true + publishToPublic: true + publishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + searchPattern: '${{ parameters.searchPattern }}' + symbolsFolder: '$(symbolsPath)' + uploadAccount: '${{ parameters.symbolsUploadAccount }}' + version: '${{ parameters.packageVersion }}' diff --git a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml index 27369eb6cc..a99b46d45f 100644 --- a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml @@ -48,8 +48,9 @@ jobs: variables: # More settings at https://aka.ms/obpipelines/yaml/jobs - - name: artifactPath - value: '$(Pipeline.Workspace)\${{ parameters.artifactName }}' + # Path within the downloaded artifact where NuGet packages are located. + - name: packagesPath + value: '$(Pipeline.Workspace)\${{ parameters.artifactName }}\packages' # Path to the SqlClient NuGet package after installation. This path will only exist once the package # been installed. @@ -85,22 +86,22 @@ jobs: - ${{ if eq(parameters.isOfficial, true) }}: - powershell: | # Propagate parameters to PS variables ####################### - $artifactPath = "${{ variables.artifactPath }}" - echo "artifactPath= $artifactPath" + $packagesPath = "${{ variables.packagesPath }}" + echo "packagesPath= $packagesPath" # Verify package signatures ################################## echo "> 1. Verify signature of source package(s)" - nuget verify -All $artifactPath\*.nupkg + nuget verify -All $packagesPath\*.nupkg echo "> 2. Verify signature of symbols package(s)" - nuget verify -All $artifactPath\*.snupkg + nuget verify -All $packagesPath\*.snupkg displayName: "Verify nuget signature" # Install NuGet package to the temporary directory - powershell: | # Propagate pipeline to PS variables ############################# - $artifactPath = "${{ variables.artifactPath }}" - echo "artifactPath= $artifactPath" + $packagesPath = "${{ variables.packagesPath }}" + echo "packagesPath= $packagesPath" $nugetPackageInstallRoot = "${{ variables.nugetPackageInstallRoot }}" echo "nugetPackageInstallRoot= $nugetPackageInstallRoot" @@ -109,7 +110,7 @@ jobs: echo "> 1. Installing Microsoft.Data.SqlClient NuGet package..." Install-Package ` -Name "Microsoft.Data.SqlClient" ` - -Source $artifactPath ` + -Source "$packagesPath" ` -Destination $nugetPackageInstallRoot ` -Force ` -SkipDependencies @@ -117,6 +118,8 @@ jobs: echo "> 2. Listing contents of installed Microsoft.Data.SqlClient NuGet package:" Write-Host $nugetPackageInstallRoot Get-ChildItem $nugetPackageInstallRoot + Write-Host "$packagesPath" + Get-ChildItem "$packagesPath" displayName: "Install NuGet Package" # Find all DLL files in the installed NuGet package, verify each is signed with a strong name diff --git a/eng/pipelines/onebranch/scripts/publish-symbols.ps1 b/eng/pipelines/onebranch/scripts/publish-symbols.ps1 new file mode 100644 index 0000000000..aeaa665a92 --- /dev/null +++ b/eng/pipelines/onebranch/scripts/publish-symbols.ps1 @@ -0,0 +1,236 @@ +<# +.SYNOPSIS + Publishes symbols to the Microsoft symbol publishing service (SymWeb/MSDL). + +.DESCRIPTION + This script is Step 2 of the two-step symbols publishing process. It requests + the Symbols Publishing Pipeline to publish previously uploaded PDB files to + internal (SymWeb) and/or public (MSDL) Microsoft symbol servers. + + The two-step process: + Step 1 (PublishSymbols@2 task in publish-symbols-step.yml): + Uploads PDB files to the Azure DevOps symbol store under a unique + artifact name (SymbolsArtifactName). This stores the symbols but does + NOT make them available on SymWeb or MSDL. + + Step 2 (this script): + Calls the Symbols Publishing Pipeline REST API to request that the + uploaded symbols be published to the symbol servers. + + Step 2 depends on Step 1: the -ArtifactName parameter MUST match the + SymbolsArtifactName used by the PublishSymbols@2 upload task so that + both steps reference the same uploaded artifact. + + This script performs four sub-steps: + 1. Acquires a bearer token from Azure CLI for the symbol publishing service. + 2. Registers a unique request name with the publishing service. + 3. Submits the request to publish symbols to the specified servers. + 4. Queries the publishing status for confirmation. + + For more details on the Symbols Publishing Pipeline, see: + https://www.osgwiki.com/wiki/Symbols_Publishing_Pipeline_to_SymWeb_and_MSDL + +.PARAMETER PublishServer + The hostname prefix of the symbol publishing service. This value is prepended to + '.trafficmanager.net' to construct the service base URL. + +.PARAMETER PublishTokenUri + The resource URI used to acquire a bearer token from Azure CLI + (via 'az account get-access-token --resource '). + +.PARAMETER PublishProjectName + The project name registered with the symbol publishing service (decided during onboarding). + +.PARAMETER ArtifactName + The name of the publishing request. This must match the SymbolsArtifactName used by + the PublishSymbols@2 upload task so that upload and publish reference the same artifact. + +.PARAMETER PublishToInternal + Whether to publish symbols to the internal symbol server. Defaults to $true. + +.PARAMETER PublishToPublic + Whether to publish symbols to the public symbol server. Defaults to $true. + +.EXAMPLE + .\publish-symbols.ps1 ` + -PublishServer "mysymbolserver" ` + -PublishTokenUri "https://login.microsoftonline.com/..." ` + -PublishProjectName "Microsoft.Data.SqlClient.SNI" ` + -ArtifactName "mds_symbols_MyProject_dotnet-sqlclient_main_7.0.0_abc123_1" + + Publishes symbols to both internal and public servers using the specified parameters. + +.EXAMPLE + .\publish-symbols.ps1 ` + -PublishServer "mysymbolserver" ` + -PublishTokenUri "https://login.microsoftonline.com/..." ` + -PublishProjectName "Microsoft.Data.SqlClient.SNI" ` + -ArtifactName "mds_symbols_MyProject_dotnet-sqlclient_main_7.0.0_abc123_2" ` + -PublishToPublic $false + + Publishes symbols to the internal server only (retry attempt 2). + +.NOTES + File Name : publish-symbols.ps1 + Requires : Azure CLI (az) must be installed and authenticated. + Called by : publish-symbols-step.yml (Azure Pipelines template) + + Publishing status codes returned by the service: + + PublishingStatus: + 0 - NotRequested: The request has not been requested to publish. + 1 - Submitted: The request is submitted to be published. + 2 - Processing: The request is still being processed. + 3 - Completed: Processing finished. Check PublishingResult for details. + + PublishingResult: + 0 - Pending: The request has not completed or has not been requested. + 1 - Succeeded: The request published successfully. + 2 - Failed: The request failed to publish. + 3 - Cancelled: The request was cancelled. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true, HelpMessage = "Hostname prefix of the symbol publishing service (prepended to .trafficmanager.net).")] + [ValidateNotNullOrEmpty()] + [string]$PublishServer, + + [Parameter(Mandatory = $true, HelpMessage = "Resource URI for acquiring a bearer token via Azure CLI.")] + [ValidateNotNullOrEmpty()] + [string]$PublishTokenUri, + + [Parameter(Mandatory = $true, HelpMessage = "Project name registered with the symbol publishing service.")] + [ValidateNotNullOrEmpty()] + [string]$PublishProjectName, + + [Parameter(Mandatory = $true, HelpMessage = "Artifact name for the publishing request (must match PublishSymbols@2 SymbolsArtifactName).")] + [ValidateNotNullOrEmpty()] + [string]$ArtifactName, + + [Parameter(Mandatory = $false, HelpMessage = "Publish symbols to the internal symbol server.")] + [bool]$PublishToInternal = $true, + + [Parameter(Mandatory = $false, HelpMessage = "Publish symbols to the public symbol server.")] + [bool]$PublishToPublic = $true +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +# --- Log input parameters --- +Write-Host "=== Publish Symbols Parameters ===" +Write-Host "PublishServer: ${PublishServer}" +Write-Host "PublishTokenUri: ${PublishTokenUri}" +Write-Host "PublishProjectName: ${PublishProjectName}" +Write-Host "ArtifactName: ${ArtifactName}" +Write-Host "PublishToInternal: ${PublishToInternal}" +Write-Host "PublishToPublic: ${PublishToPublic}" +Write-Host "==================================" + +# --- Build request name and URLs --- +$requestName = ${ArtifactName} +$baseUrl = "https://${PublishServer}.trafficmanager.net/projects/${PublishProjectName}" +$registerUrl = "${baseUrl}/requests" +$requestUrl = "${baseUrl}/requests/${requestName}" + +Write-Host "=== Constructed URLs ===" +Write-Host "Request Name: ${requestName}" +Write-Host "Base URL: ${baseUrl}" +Write-Host "Register URL: ${registerUrl}" +Write-Host "Request URL: ${requestUrl}" +Write-Host "========================" + +# --- Step 1: Acquire token --- +Write-Host "> 1. Acquiring symbol publishing token..." +$symbolPublishingToken = az account get-access-token --resource ${PublishTokenUri} --query accessToken -o tsv +if ($LASTEXITCODE -ne 0) { + throw "Failed to acquire symbol publishing token via Azure CLI (exit code: ${LASTEXITCODE})." +} +if ($null -ne $symbolPublishingToken) { + $symbolPublishingToken = $symbolPublishingToken.Trim() +} +if ([string]::IsNullOrWhiteSpace($symbolPublishingToken)) { + throw "Failed to acquire symbol publishing token via Azure CLI: received an empty or whitespace-only access token." +} +Write-Host "> 1. Symbol publishing token acquired." + +$authHeaders = @{ Authorization = "Bearer ${symbolPublishingToken}" } + +# --- Step 2: Register request name --- +Write-Host "> 2. Registering request name..." +$requestNameRegistrationBody = @{ requestName = $requestName } | ConvertTo-Json -Compress +try { + Invoke-RestMethod -Method POST -Uri ${registerUrl} -Headers ${authHeaders} -ContentType "application/json" -Body ${requestNameRegistrationBody} +} catch { + throw "Failed to register request name. URI: ${registerUrl} | Body: ${requestNameRegistrationBody} | Error: $_" +} +Write-Host "> 2. Request name registered successfully." + +# --- Step 3: Publish symbols --- +Write-Host "> 3. Submitting request to publish symbols..." +$publishSymbolsBody = @{ + publishToInternalServer = $PublishToInternal + publishToPublicServer = $PublishToPublic +} | ConvertTo-Json -Compress +Write-Host "Publishing symbols request body: ${publishSymbolsBody}" +try { + Invoke-RestMethod -Method POST -Uri ${requestUrl} -Headers ${authHeaders} -ContentType "application/json" -Body ${publishSymbolsBody} +} catch { + throw "Failed to publish symbols. URI: ${requestUrl} | Body: ${publishSymbolsBody} | Error: $_" +} +Write-Host "> 3. Request to publish symbols submitted successfully." + +# --- Step 4: Check status --- +Write-Host "> 4. Checking the status of the request..." +try { + $status = Invoke-RestMethod -Method GET -Uri ${requestUrl} -Headers ${authHeaders} -ContentType "application/json" + $status +} catch { + throw "Failed to check request status. URI: ${requestUrl} | Error: $_" +} + +# Validate publishing results — fail the task when the service reports a terminal failure. +# PublishingResult: 0=Pending, 1=Succeeded, 2=Failed, 3=Cancelled +$resultLabels = @{ 0 = 'Pending'; 1 = 'Succeeded'; 2 = 'Failed'; 3 = 'Cancelled' } +$failures = @() + +if ($PublishToInternal) { + $internalResult = $status.publishToInternalServerResult + if ($null -ne $internalResult -and $internalResult -ge 2) { + $label = if ($resultLabels.ContainsKey([int]$internalResult)) { $resultLabels[[int]$internalResult] } else { "Unknown($internalResult)" } + $failures += "Internal server publishing result: ${label} (${internalResult})" + } +} + +if ($PublishToPublic) { + $publicResult = $status.publishToPublicServerResult + if ($null -ne $publicResult -and $publicResult -ge 2) { + $label = if ($resultLabels.ContainsKey([int]$publicResult)) { $resultLabels[[int]$publicResult] } else { "Unknown($publicResult)" } + $failures += "Public server publishing result: ${label} (${publicResult})" + } +} + +if ($failures.Count -gt 0) { + $failureMessage = $failures -join '; ' + throw "Symbol publishing reported a terminal failure. ${failureMessage}. URI: ${requestUrl}" +} + +Write-Host "> 4. Status check completed - no terminal failures detected." + +Write-Host "" +Write-Host "Use below tables to interpret the xxxServerStatus and xxxServerResult fields from the response." +Write-Host "" +Write-Host "PublishingStatus" +Write-Host "-----------------" +Write-Host "0 NotRequested - The request has not been requested to publish." +Write-Host "1 Submitted - The request is submitted to be published." +Write-Host "2 Processing - The request is still being processed." +Write-Host "3 Completed - Processing finished. Check PublishingResult for details." +Write-Host "" +Write-Host "PublishingResult" +Write-Host "-----------------" +Write-Host "0 Pending - The request has not completed or has not been requested." +Write-Host "1 Succeeded - The request published successfully." +Write-Host "2 Failed - The request failed to publish." +Write-Host "3 Cancelled - The request was cancelled." diff --git a/eng/pipelines/onebranch/scripts/tests/README.md b/eng/pipelines/onebranch/scripts/tests/README.md new file mode 100644 index 0000000000..447dda5d32 --- /dev/null +++ b/eng/pipelines/onebranch/scripts/tests/README.md @@ -0,0 +1,43 @@ +# Publish-Symbols Tests + +Pester tests for the `publish-symbols.ps1` script used by the symbol publishing pipeline step. + +## Prerequisites + +- PowerShell 5.1+ or PowerShell 7+ +- [Pester v5](https://pester.dev/) (`Install-Module Pester -MinimumVersion 5.0 -Scope CurrentUser`) + +## Running the Tests + +From this directory: + +```powershell +Invoke-Pester ./publish-symbols.Tests.ps1 +``` + +Or from the repository root: + +```powershell +Invoke-Pester ./eng/pipelines/onebranch/scripts/tests/ +``` + +For detailed output: + +```powershell +Invoke-Pester ./publish-symbols.Tests.ps1 -Output Detailed +``` + +## Test Coverage + +| Area | What's tested | +| --------------------- | ---------------------------------------------------------------- | +| Parameter validation | Empty strings rejected for all mandatory parameters | +| URL construction | Base URL, register URL, request URL built from parameters | +| Request bodies | Registration body, default publish flags, flag overrides | +| Error handling | Token failure, registration failure, publish failure, status failure — all verify expanded URI in error message | +| Status validation | Detects Failed/Cancelled results, respects PublishToInternal/PublishToPublic flags, passes on Succeeded/Pending | + +## Notes + +- All external calls (`az`, `Invoke-RestMethod`) are mocked — no network access or Azure credentials are required. +- Tests validate the script at `../publish-symbols.ps1` relative to this directory. diff --git a/eng/pipelines/onebranch/scripts/tests/publish-symbols.Tests.ps1 b/eng/pipelines/onebranch/scripts/tests/publish-symbols.Tests.ps1 new file mode 100644 index 0000000000..4d16d54e96 --- /dev/null +++ b/eng/pipelines/onebranch/scripts/tests/publish-symbols.Tests.ps1 @@ -0,0 +1,356 @@ +<# +.SYNOPSIS + Pester tests for publish-symbols.ps1 +#> + +BeforeAll { + $scriptPath = Join-Path $PSScriptRoot '..' 'publish-symbols.ps1' +} + +AfterAll { + # Clean up global variables used by mocks + Remove-Variable -Name 'restCalls' -Scope Global -ErrorAction SilentlyContinue + Remove-Variable -Name 'mockCallCount' -Scope Global -ErrorAction SilentlyContinue +} + +Describe 'publish-symbols.ps1 Parameter Validation' { + + It 'Should reject an empty PublishServer' { + { & $scriptPath -PublishServer '' -PublishTokenUri 'https://token' -PublishProjectName 'proj' -ArtifactName 'art' } | + Should -Throw + } + + It 'Should reject an empty PublishTokenUri' { + { & $scriptPath -PublishServer 'server' -PublishTokenUri '' -PublishProjectName 'proj' -ArtifactName 'art' } | + Should -Throw + } + + It 'Should reject an empty PublishProjectName' { + { & $scriptPath -PublishServer 'server' -PublishTokenUri 'https://token' -PublishProjectName '' -ArtifactName 'art' } | + Should -Throw + } + + It 'Should reject an empty ArtifactName' { + { & $scriptPath -PublishServer 'server' -PublishTokenUri 'https://token' -PublishProjectName 'proj' -ArtifactName '' } | + Should -Throw + } +} + +Describe 'publish-symbols.ps1 URL Construction' { + + BeforeAll { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token-12345' } + + $global:restCalls = @() + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:restCalls += @{ + Method = $Method + Uri = $Uri + Body = $Body + } + return @{ publishToInternalServerStatus = 0; publishToPublicServerStatus = 0; publishToInternalServerResult = 0; publishToPublicServerResult = 0 } + } + } + + BeforeEach { + $global:restCalls = @() + } + + It 'Should construct the correct base URL from PublishServer and PublishProjectName' { + & $scriptPath ` + -PublishServer 'myserver' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'My.Project' ` + -ArtifactName 'test_artifact' + + $global:restCalls.Count | Should -Be 3 + + # Registration call + $global:restCalls[0].Uri | Should -Be 'https://myserver.trafficmanager.net/projects/My.Project/requests' + $global:restCalls[0].Method | Should -Be 'POST' + + # Publish call + $global:restCalls[1].Uri | Should -Be 'https://myserver.trafficmanager.net/projects/My.Project/requests/test_artifact' + $global:restCalls[1].Method | Should -Be 'POST' + + # Status call + $global:restCalls[2].Uri | Should -Be 'https://myserver.trafficmanager.net/projects/My.Project/requests/test_artifact' + $global:restCalls[2].Method | Should -Be 'GET' + } + + It 'Should use ArtifactName directly as the request name' { + & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'myartifact_3' + + $global:restCalls[1].Uri | Should -BeLike '*myartifact_3' + $global:restCalls[2].Uri | Should -BeLike '*myartifact_3' + } +} + +Describe 'publish-symbols.ps1 Request Bodies' { + + BeforeAll { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token-12345' } + + $global:restCalls = @() + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:restCalls += @{ + Method = $Method + Uri = $Uri + Body = $Body + } + return @{ publishToInternalServerStatus = 0; publishToPublicServerStatus = 0; publishToInternalServerResult = 0; publishToPublicServerResult = 0 } + } + } + + BeforeEach { + $global:restCalls = @() + } + + It 'Should send the correct request name in the registration body' { + & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'my_artifact_1' + + $body = $global:restCalls[0].Body | ConvertFrom-Json + $body.requestName | Should -Be 'my_artifact_1' + } + + It 'Should default to publishing to both internal and public servers' { + & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' + + $body = $global:restCalls[1].Body | ConvertFrom-Json + $body.publishToInternalServer | Should -Be $true + $body.publishToPublicServer | Should -Be $true + } + + It 'Should respect PublishToInternal=false' { + & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' ` + -PublishToInternal $false + + $body = $global:restCalls[1].Body | ConvertFrom-Json + $body.publishToInternalServer | Should -Be $false + $body.publishToPublicServer | Should -Be $true + } + + It 'Should respect PublishToPublic=false' { + & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' ` + -PublishToPublic $false + + $body = $global:restCalls[1].Body | ConvertFrom-Json + $body.publishToInternalServer | Should -Be $true + $body.publishToPublicServer | Should -Be $false + } +} + +Describe 'publish-symbols.ps1 Error Handling' { + + It 'Should throw when token acquisition fails' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 1; return '' } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Throw '*token*' + } + + It 'Should throw when token is empty' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return ' ' } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Throw '*empty*' + } + + It 'Should throw with URI details when registration fails' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + Mock -CommandName 'Invoke-RestMethod' -MockWith { throw "Connection refused" } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Throw '*Failed to register*' + } + + It 'Should throw with URI details when publish fails' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -eq 1) { return @{} } + throw "Service unavailable" + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Throw '*Failed to publish*' + } + + It 'Should throw with URI details when status check fails' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -le 2) { return @{} } + throw "Timeout" + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Throw '*Failed to check*' + } +} + +Describe 'publish-symbols.ps1 Status Failure Detection' { + + It 'Should throw when internal server result is Failed (2)' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -le 2) { return @{} } + return @{ publishToInternalServerResult = 2; publishToPublicServerResult = 0 } + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Throw '*terminal failure*Internal server*Failed*' + } + + It 'Should throw when public server result is Cancelled (3)' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -le 2) { return @{} } + return @{ publishToInternalServerResult = 1; publishToPublicServerResult = 3 } + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Throw '*terminal failure*Public server*Cancelled*' + } + + It 'Should throw when both servers report failure' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -le 2) { return @{} } + return @{ publishToInternalServerResult = 2; publishToPublicServerResult = 3 } + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Throw '*terminal failure*Internal server*Public server*' + } + + It 'Should not throw when both servers report Succeeded (1)' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -le 2) { return @{} } + return @{ publishToInternalServerResult = 1; publishToPublicServerResult = 1 } + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Not -Throw + } + + It 'Should not throw when results are Pending (0)' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -le 2) { return @{} } + return @{ publishToInternalServerResult = 0; publishToPublicServerResult = 0 } + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' } | + Should -Not -Throw + } + + It 'Should not check internal result when PublishToInternal is false' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -le 2) { return @{} } + return @{ publishToInternalServerResult = 2; publishToPublicServerResult = 1 } + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' ` + -PublishToInternal $false } | + Should -Not -Throw + } + + It 'Should not check public result when PublishToPublic is false' { + Mock -CommandName 'az' -MockWith { $global:LASTEXITCODE = 0; return 'fake-token' } + $global:mockCallCount = 0 + Mock -CommandName 'Invoke-RestMethod' -MockWith { + $global:mockCallCount++ + if ($global:mockCallCount -le 2) { return @{} } + return @{ publishToInternalServerResult = 1; publishToPublicServerResult = 2 } + } + + { & $scriptPath ` + -PublishServer 'srv' ` + -PublishTokenUri 'https://token-uri' ` + -PublishProjectName 'proj' ` + -ArtifactName 'art' ` + -PublishToPublic $false } | + Should -Not -Throw + } +} diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index a908ae4e74..4ae301020f 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -174,7 +174,6 @@ extends: - template: /eng/pipelines/onebranch/stages/build-stages.yml@self parameters: isOfficial: false # This is a non-official pipeline. - publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} buildSqlClient: ${{ parameters.buildSqlClient }} buildAkvProvider: ${{ parameters.buildAkvProvider }} @@ -204,10 +203,32 @@ extends: signingAuthSignCertName: '$(SigningAuthSignCertName)' signingEsrpClientId: '$(SigningEsrpClientId)' signingEsrpConnectedServiceName: '$(SigningEsrpConnectedServiceName)' + + - template: /eng/pipelines/onebranch/stages/publish-symbols-stage.yml@self + parameters: + publishSymbols: ${{ parameters.publishSymbols }} + buildSqlServerServer: ${{ parameters.buildSqlServerServer }} + buildSqlClient: ${{ parameters.buildSqlClient }} + buildAkvProvider: ${{ parameters.buildAkvProvider }} + + abstractionsArtifactsName: '${{ variables.abstractionsArtifactsName }}' + abstractionsPackageVersion: '${{ variables.abstractionsPackageVersion }}' + akvProviderArtifactsName: '${{ variables.akvProviderArtifactsName }}' + akvProviderPackageVersion: '${{ variables.akvProviderPackageVersion }}' + azureArtifactsName: '${{ variables.azureArtifactsName }}' + azurePackageVersion: '${{ variables.azurePackageVersion }}' + loggingArtifactsName: '${{ variables.loggingArtifactsName }}' + loggingPackageVersion: '${{ variables.loggingPackageVersion }}' + sqlClientArtifactsName: '${{ variables.sqlClientArtifactsName }}' + sqlClientPackageVersion: '${{ variables.sqlClientPackageVersion }}' + sqlServerArtifactsName: '${{ variables.sqlServerArtifactsName }}' + sqlServerPackageVersion: '${{ variables.sqlServerPackageVersion }}' + symbolsAzureSubscription: '$(SymbolsAzureSubscription)' - symbolsPublishProjectName: '$(SymbolsPublishProjectName)' - symbolsPublishServer: '$(SymbolsPublishServer)' - symbolsPublishTokenUri: '$(SymbolsPublishTokenUri)' + symbolsPublishProjectName: '$(SymbolsPublishProjectNameSqlClient)' + # Non-Official pipelines must publish to the PPE symbol server. + symbolsPublishServer: '$(SymbolsPublishServerPPE)' + symbolsPublishTokenUri: '$(SymbolsPublishTokenUriPPE)' symbolsUploadAccount: '$(SymbolsUploadAccount)' - template: /eng/pipelines/onebranch/stages/release-stages.yml@self @@ -219,6 +240,8 @@ extends: isOfficial: false stageNameSuffix: test + publishSymbols: ${{ parameters.publishSymbols }} + abstractionsArtifactsName: '${{ variables.abstractionsArtifactsName }}' abstractionsPackageVersion: '${{ variables.abstractionsPackageVersion }}' akvProviderArtifactsName: '${{ variables.akvProviderArtifactsName }}' diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index 7f02ea0226..a2902f1e21 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -202,7 +202,6 @@ extends: - template: /eng/pipelines/onebranch/stages/build-stages.yml@self parameters: isOfficial: true # This is an official pipeline. - publishSymbols: ${{ parameters.publishSymbols }} buildSqlServerServer: ${{ parameters.buildSqlServerServer }} buildSqlClient: ${{ parameters.buildSqlClient }} buildAkvProvider: ${{ parameters.buildAkvProvider }} @@ -232,10 +231,32 @@ extends: signingAuthSignCertName: '$(SigningAuthSignCertName)' signingEsrpClientId: '$(SigningEsrpClientId)' signingEsrpConnectedServiceName: '$(SigningEsrpConnectedServiceName)' + + - template: /eng/pipelines/onebranch/stages/publish-symbols-stage.yml@self + parameters: + publishSymbols: ${{ parameters.publishSymbols }} + buildSqlServerServer: ${{ parameters.buildSqlServerServer }} + buildSqlClient: ${{ parameters.buildSqlClient }} + buildAkvProvider: ${{ parameters.buildAkvProvider }} + + abstractionsArtifactsName: '${{ variables.abstractionsArtifactsName }}' + abstractionsPackageVersion: '${{ variables.abstractionsPackageVersion }}' + akvProviderArtifactsName: '${{ variables.akvProviderArtifactsName }}' + akvProviderPackageVersion: '${{ variables.akvProviderPackageVersion }}' + azureArtifactsName: '${{ variables.azureArtifactsName }}' + azurePackageVersion: '${{ variables.azurePackageVersion }}' + loggingArtifactsName: '${{ variables.loggingArtifactsName }}' + loggingPackageVersion: '${{ variables.loggingPackageVersion }}' + sqlClientArtifactsName: '${{ variables.sqlClientArtifactsName }}' + sqlClientPackageVersion: '${{ variables.sqlClientPackageVersion }}' + sqlServerArtifactsName: '${{ variables.sqlServerArtifactsName }}' + sqlServerPackageVersion: '${{ variables.sqlServerPackageVersion }}' + symbolsAzureSubscription: '$(SymbolsAzureSubscription)' - symbolsPublishProjectName: '$(SymbolsPublishProjectName)' - symbolsPublishServer: '$(SymbolsPublishServer)' - symbolsPublishTokenUri: '$(SymbolsPublishTokenUri)' + symbolsPublishProjectName: '$(SymbolsPublishProjectNameSqlClient)' + # Official pipelines must publish to the Production symbol server. + symbolsPublishServer: '$(SymbolsPublishServerProd)' + symbolsPublishTokenUri: '$(SymbolsPublishTokenUriProd)' symbolsUploadAccount: '$(SymbolsUploadAccount)' - template: /eng/pipelines/onebranch/stages/release-stages.yml@self @@ -246,6 +267,8 @@ extends: isOfficial: true stageNameSuffix: production + publishSymbols: ${{ parameters.publishSymbols }} + abstractionsArtifactsName: '${{ variables.abstractionsArtifactsName }}' abstractionsPackageVersion: '${{ variables.abstractionsPackageVersion }}' akvProviderArtifactsName: '${{ variables.akvProviderArtifactsName }}' diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index ba24af6309..252f63fb0a 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -22,10 +22,6 @@ parameters: - name: isOfficial type: boolean - # True to publish symbols to public and private servers. - - name: publishSymbols - type: boolean - # ── Build parameters ─────────────────────────────────────────────────── - name: buildSqlServerServer @@ -113,23 +109,6 @@ parameters: - name: signingEsrpConnectedServiceName type: string - # Symbols Publishing Parameters ------------------------------------------ - - - name: symbolsAzureSubscription - type: string - - - name: symbolsPublishProjectName - type: string - - - name: symbolsPublishServer - type: string - - - name: symbolsPublishTokenUri - type: string - - - name: symbolsUploadAccount - type: string - stages: # ==================================================================== # Stage 1: Independent packages (no cross-package dependencies) @@ -148,21 +127,14 @@ stages: signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' - symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' - symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' - symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' isOfficial: ${{ parameters.isOfficial }} packageName: Logging packageFullName: Microsoft.Data.SqlClient.Internal.Logging - packageVersion: '${{ parameters.loggingPackageVersion }}' versionProperties: >- -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} -p:LoggingAssemblyFileVersion=${{ parameters.loggingFileVersion }} assemblyFileVersion: '${{ parameters.loggingFileVersion }}' - publishSymbols: ${{ parameters.publishSymbols }} - ${{ if eq(parameters.buildSqlServerServer, true) }}: @@ -174,21 +146,14 @@ stages: signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' - symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' - symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' - symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' isOfficial: ${{ parameters.isOfficial }} packageName: SqlServer packageFullName: Microsoft.SqlServer.Server - packageVersion: '${{ parameters.sqlServerPackageVersion }}' versionProperties: >- -p:SqlServerAssemblyFileVersion=${{ parameters.sqlServerFileVersion }} -p:SqlServerPackageVersion=${{ parameters.sqlServerPackageVersion }} assemblyFileVersion: '${{ parameters.sqlServerFileVersion }}' - publishSymbols: ${{ parameters.publishSymbols }} # ==================================================================== # Stage 2: Abstractions package (depends on Logging from Stage 1) @@ -209,22 +174,15 @@ stages: signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' - symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' - symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' - symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' isOfficial: ${{ parameters.isOfficial }} packageName: Abstractions packageFullName: Microsoft.Data.SqlClient.Extensions.Abstractions - packageVersion: '${{ parameters.abstractionsPackageVersion }}' versionProperties: >- -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:AbstractionsAssemblyFileVersion=${{ parameters.abstractionsFileVersion }} -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} assemblyFileVersion: '${{ parameters.abstractionsFileVersion }}' - publishSymbols: ${{ parameters.publishSymbols }} downloadArtifacts: - artifactName: '${{ parameters.loggingArtifactsName }}' displayName: 'Logging Package' @@ -242,21 +200,13 @@ stages: jobs: - template: /eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml@self parameters: - apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient/dlls' - apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient/pdbs' isOfficial: ${{ parameters.isOfficial }} - publishSymbols: ${{ parameters.publishSymbols }} signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' - symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' - symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' - symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' abstractionsArtifactName: '${{ parameters.abstractionsArtifactsName }}' abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' @@ -273,23 +223,16 @@ stages: signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' - symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' - symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' - symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' isOfficial: ${{ parameters.isOfficial }} packageName: Azure packageFullName: Microsoft.Data.SqlClient.Extensions.Azure - packageVersion: '${{ parameters.azurePackageVersion }}' versionProperties: >- -p:AzurePackageVersion=${{ parameters.azurePackageVersion }} -p:AzureAssemblyFileVersion=${{ parameters.azureFileVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} assemblyFileVersion: '${{ parameters.azureFileVersion }}' - publishSymbols: ${{ parameters.publishSymbols }} downloadArtifacts: - artifactName: '${{ parameters.abstractionsArtifactsName }}' displayName: Abstractions Package @@ -314,16 +257,10 @@ stages: signingAuthSignCertName: '${{ parameters.signingAuthSignCertName }}' signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' - symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' - symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' - symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' isOfficial: ${{ parameters.isOfficial }} packageName: AkvProvider packageFullName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider - packageVersion: '${{ parameters.akvProviderPackageVersion }}' versionProperties: >- -p:AkvPackageVersion=${{ parameters.akvProviderPackageVersion }} -p:AkvAssemblyFileVersion=${{ parameters.akvProviderFileVersion }} @@ -331,7 +268,6 @@ stages: -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} assemblyFileVersion: '${{ parameters.akvProviderFileVersion }}' - publishSymbols: ${{ parameters.publishSymbols }} downloadArtifacts: - artifactName: '${{ parameters.sqlClientArtifactsName }}' displayName: SqlClient Package diff --git a/eng/pipelines/onebranch/stages/publish-symbols-stage.yml b/eng/pipelines/onebranch/stages/publish-symbols-stage.yml new file mode 100644 index 0000000000..e716d2255d --- /dev/null +++ b/eng/pipelines/onebranch/stages/publish-symbols-stage.yml @@ -0,0 +1,222 @@ +################################################################################# +# 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. # +################################################################################# + +# Unified symbols publishing stage template for sqlclient OneBranch pipelines. +# Consumed by both the official and non-official pipeline definitions. +# +# This stage publishes PDB symbol files to the internal and public symbol servers. +# Each package's PDBs are published in a separate job to maintain unique naming and +# versioning information per package. +# +# PDBs are expected to be located under the 'symbols/' directory at the root of +# each build artifact, with target framework subdirectories preserved by the build +# job's CopyFiles step. The publish-symbols job consumes that 'symbols' folder directly. +# +# Note that none of the resource DLLs we build produce PDBs, so there are no patterns below that +# match them. +# +# The stage is excluded at compile time when publishSymbols is false. + +parameters: + # ── General parameters ───────────────────────────────────────────────── + + # True to publish symbols (controls whether this stage is emitted at all). + - name: publishSymbols + type: boolean + + # ── Build parameters (used to conditionally include per-package jobs) ─── + + - name: buildSqlServerServer + type: boolean + + - name: buildSqlClient + type: boolean + + - name: buildAkvProvider + type: boolean + + # ── Package Parameters ───────────────────────────────────────────────── + + - name: abstractionsArtifactsName + type: string + + - name: abstractionsPackageVersion + type: string + + - name: akvProviderArtifactsName + type: string + + - name: akvProviderPackageVersion + type: string + + - name: azureArtifactsName + type: string + + - name: azurePackageVersion + type: string + + - name: loggingArtifactsName + type: string + + - name: loggingPackageVersion + type: string + + - name: sqlClientArtifactsName + type: string + + - name: sqlClientPackageVersion + type: string + + - name: sqlServerArtifactsName + type: string + + - name: sqlServerPackageVersion + type: string + + # ── Symbols Publishing Parameters ────────────────────────────────────── + + - name: symbolsAzureSubscription + type: string + + - name: symbolsPublishProjectName + type: string + + - name: symbolsPublishServer + type: string + + - name: symbolsPublishTokenUri + type: string + + - name: symbolsUploadAccount + type: string + +stages: + # Stage is emitted whenever publishSymbols is true. If no packages were + # built, only the no-op placeholder job runs and the stage succeeds + # immediately — keeping the dependency graph simple for downstream stages. + - ${{ if eq(parameters.publishSymbols, true) }}: + - stage: publish_symbols + displayName: "Publish Symbols" + + # Depend on whichever build stages produced artifacts we need to publish. + dependsOn: + - ${{ if or(parameters.buildSqlServerServer, parameters.buildSqlClient, parameters.buildAkvProvider) }}: + - build_independent + - ${{ if parameters.buildSqlClient }}: + - build_abstractions + - build_dependent + - ${{ if and(parameters.buildAkvProvider, parameters.buildSqlClient) }}: + - build_addons + + jobs: + # ── No-op placeholder ────────────────────────────────────────── + # When no packages were built, this job keeps the stage valid + # (ADO requires at least one job per stage) and succeeds instantly. + - ${{ if not(or(parameters.buildSqlServerServer, parameters.buildSqlClient, parameters.buildAkvProvider)) }}: + - job: publish_symbols_noop + displayName: "No symbols to publish" + pool: + type: linux + steps: + - script: echo "No packages were built — nothing to publish." + displayName: Skip + + # ── Logging ──────────────────────────────────────────────────── + - ${{ if or(parameters.buildAkvProvider, parameters.buildSqlClient) }}: + - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self + parameters: + artifactName: '${{ parameters.loggingArtifactsName }}' + packageName: Logging + packageFullName: Microsoft.Data.SqlClient.Internal.Logging + packageVersion: '${{ parameters.loggingPackageVersion }}' + # Matches: Microsoft.Data.SqlClient.Internal.Logging.pdb across all TFM subdirs. + searchPattern: '**/Microsoft.Data.SqlClient.Internal.Logging.pdb' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + # ── SqlServer.Server ─────────────────────────────────────────── + - ${{ if eq(parameters.buildSqlServerServer, true) }}: + - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self + parameters: + artifactName: '${{ parameters.sqlServerArtifactsName }}' + packageName: SqlServer + packageFullName: Microsoft.SqlServer.Server + packageVersion: '${{ parameters.sqlServerPackageVersion }}' + # Matches: Microsoft.SqlServer.Server.pdb across all TFM subdirs. + searchPattern: '**/Microsoft.SqlServer.Server.pdb' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + # ── Abstractions ─────────────────────────────────────────────── + - ${{ if eq(parameters.buildSqlClient, true) }}: + - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self + parameters: + artifactName: '${{ parameters.abstractionsArtifactsName }}' + packageName: Abstractions + packageFullName: Microsoft.Data.SqlClient.Extensions.Abstractions + packageVersion: '${{ parameters.abstractionsPackageVersion }}' + # Matches: Microsoft.Data.SqlClient.Extensions.Abstractions.pdb across all TFM subdirs. + searchPattern: '**/Microsoft.Data.SqlClient.Extensions.Abstractions.pdb' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + # ── SqlClient ────────────────────────────────────────────────── + - ${{ if eq(parameters.buildSqlClient, true) }}: + - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self + parameters: + artifactName: '${{ parameters.sqlClientArtifactsName }}' + packageName: SqlClient + packageFullName: Microsoft.Data.SqlClient + packageVersion: '${{ parameters.sqlClientPackageVersion }}' + # Matches: Microsoft.Data.SqlClient.pdb across all OS/TFM subdirs. + # Excludes: Microsoft.Data.SqlClient.SNI*.pdb (native SNI — symbols published separately). + searchPattern: '**/Microsoft.Data.SqlClient.pdb' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + # ── Azure ────────────────────────────────────────────────────── + - ${{ if eq(parameters.buildSqlClient, true) }}: + - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self + parameters: + artifactName: '${{ parameters.azureArtifactsName }}' + packageName: Azure + packageFullName: Microsoft.Data.SqlClient.Extensions.Azure + packageVersion: '${{ parameters.azurePackageVersion }}' + # Matches: Microsoft.Data.SqlClient.Extensions.Azure.pdb across all TFM subdirs. + searchPattern: '**/Microsoft.Data.SqlClient.Extensions.Azure.pdb' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' + + # ── AKV Provider ─────────────────────────────────────────────── + - ${{ if and(eq(parameters.buildAkvProvider, true), eq(parameters.buildSqlClient, true)) }}: + - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self + parameters: + artifactName: '${{ parameters.akvProviderArtifactsName }}' + packageName: AkvProvider + packageFullName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + packageVersion: '${{ parameters.akvProviderPackageVersion }}' + # Matches: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb across all TFM subdirs. + searchPattern: '**/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb' + symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' + symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' + symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' + symbolsPublishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + symbolsUploadAccount: '${{ parameters.symbolsUploadAccount }}' diff --git a/eng/pipelines/onebranch/stages/release-stages.yml b/eng/pipelines/onebranch/stages/release-stages.yml index 917447a077..6ee3fbc88e 100644 --- a/eng/pipelines/onebranch/stages/release-stages.yml +++ b/eng/pipelines/onebranch/stages/release-stages.yml @@ -89,6 +89,14 @@ parameters: - name: sqlServerPackageVersion type: string + # ── Symbols publishing parameter ─────────────────────────────────────── + # When true, the release stage will depend on publish_symbols so that + # symbol publishing completes before packages are released. + + - name: publishSymbols + type: boolean + default: false + # ── Release parameters ───────────────────────────────────────────────── - name: releaseSqlServerServer @@ -139,6 +147,8 @@ stages: - sqlclient_package_validation - ${{ if parameters.releaseAkvProvider }}: - build_addons + - ${{ if parameters.publishSymbols }}: + - publish_symbols variables: - name: onebranchReleaseEnvironment @@ -183,7 +193,7 @@ stages: parameters: packageName: Microsoft.SqlServer.Server artifactName: '${{ parameters.sqlServerArtifactsName }}' - packagePath: 'Microsoft.SqlServer.Server.${{ parameters.sqlServerPackageVersion }}.nupkg' + packagePath: 'packages/Microsoft.SqlServer.Server.${{ parameters.sqlServerPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -193,7 +203,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Internal.Logging artifactName: '${{ parameters.loggingArtifactsName }}' - packagePath: 'Microsoft.Data.SqlClient.Internal.Logging.${{ parameters.loggingPackageVersion }}.nupkg' + packagePath: 'packages/Microsoft.Data.SqlClient.Internal.Logging.${{ parameters.loggingPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -203,7 +213,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Extensions.Abstractions artifactName: '${{ parameters.abstractionsArtifactsName }}' - packagePath: 'Microsoft.Data.SqlClient.Extensions.Abstractions.${{ parameters.abstractionsPackageVersion }}.nupkg' + packagePath: 'packages/Microsoft.Data.SqlClient.Extensions.Abstractions.${{ parameters.abstractionsPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -213,7 +223,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient artifactName: '${{ parameters.sqlClientArtifactsName }}' - packagePath: 'Microsoft.Data.SqlClient.${{ parameters.sqlClientPackageVersion }}.nupkg' + packagePath: 'packages/Microsoft.Data.SqlClient.${{ parameters.sqlClientPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -223,7 +233,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Extensions.Azure artifactName: '${{ parameters.azureArtifactsName }}' - packagePath: 'Microsoft.Data.SqlClient.Extensions.Azure.${{ parameters.azurePackageVersion }}.nupkg' + packagePath: 'packages/Microsoft.Data.SqlClient.Extensions.Azure.${{ parameters.azurePackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -233,7 +243,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider artifactName: '${{ parameters.akvProviderArtifactsName }}' - packagePath: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.${{ parameters.akvProviderPackageVersion }}.nupkg' + packagePath: 'packages/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.${{ parameters.akvProviderPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} diff --git a/eng/pipelines/onebranch/steps/copy-apiscan-files-sqlclient-step.yml b/eng/pipelines/onebranch/steps/copy-apiscan-files-sqlclient-step.yml deleted file mode 100644 index 653eb40f5d..0000000000 --- a/eng/pipelines/onebranch/steps/copy-apiscan-files-sqlclient-step.yml +++ /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. # -################################################################################# - -# @TODO: This can be made more generic - -parameters: - # Path where dll files should be copied to for APIScan - - name: dllPath - type: string - - # Path where pdb files should be copied to for APIScan - - name: pdbPath - type: string - - - name: referenceType - type: string - values: - - Package - - Project - -steps: - - task: CopyFiles@2 - displayName: 'Copy DLLs for APIScan' - inputs: - contents: '**/Microsoft.Data.SqlClient.dll' - flattenFolders: false # Disabled to keep different frameworks and OS builds separate in the target - sourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/${{ parameters.referenceType }}-Release/' - targetFolder: '${{ parameters.dllPath }}/' - - - task: CopyFiles@2 - displayName: 'Copy PDBs for APIScan' - inputs: - contents: '**/Microsoft.Data.SqlClient.pdb' - flattenFolders: false # Disabled to keep different frameworks and OS builds separate in the target - sourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/${{ parameters.referenceType }}-Release/' - targetFolder: '${{ parameters.pdbPath }}/' diff --git a/eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml b/eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml index 073911e5fe..db95ef4f24 100644 --- a/eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml +++ b/eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml @@ -30,8 +30,9 @@ parameters: - name: esrpClientId type: string - # Globbing pattern for the files to sign. All files in $(BUILD_OUTPUT) - # that match this pattern will be scanned and signed. This should end with ".dll". + # Minimatch pattern(s) for the files to sign. All files in $(BUILD_OUTPUT) + # that match will be scanned and signed. Supports multi-line patterns and + # negation (!pattern). See https://aka.ms/esrp.signtask for details. - name: pattern type: string @@ -47,6 +48,7 @@ steps: EsrpClientId: '${{ parameters.esrpClientId }}' FolderPath: '$(BUILD_OUTPUT)' Pattern: '${{ parameters.pattern }}' + UseMinimatch: true UseMSIAuthentication: true VerboseLogin: 1 @@ -62,6 +64,7 @@ steps: EsrpClientId: '${{ parameters.esrpClientId }}' FolderPath: '$(BUILD_OUTPUT)' Pattern: '${{ parameters.pattern }}' + UseMinimatch: true signConfigType: inlineSignParams UseMSIAuthentication: true inlineOperation: | diff --git a/eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml b/eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml index bddbac517c..6a6682d40f 100644 --- a/eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml +++ b/eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml @@ -34,7 +34,8 @@ parameters: - name: searchPath type: string - # Globbing pattern to use to search for NuGet packages. If not provided, defaults to '*.*nupkg' + # Minimatch pattern(s) to search for NuGet packages. Supports multi-line + # patterns and negation (!pattern). Defaults to '*.*nupkg'. - name: searchPattern type: string default: '*.*nupkg' @@ -51,6 +52,7 @@ steps: EsrpClientId: '${{ parameters.esrpClientId }}' FolderPath: '${{ parameters.searchPath }}' Pattern: '${{ parameters.searchPattern }}' + UseMinimatch: true UseMSIAuthentication: true VerboseLogin: 1 @@ -66,6 +68,7 @@ steps: AuthSignCertName: '${{ parameters.authSignCertName }}' FolderPath: '${{ parameters.searchPath }}' Pattern: '${{ parameters.searchPattern }}' + UseMinimatch: true signConfigType: 'inlineSignParams' UseMSIAuthentication: true inlineOperation: | diff --git a/eng/pipelines/onebranch/steps/pack-csproj-step.yml b/eng/pipelines/onebranch/steps/pack-csproj-step.yml index edbec72c00..535106a9d1 100644 --- a/eng/pipelines/onebranch/steps/pack-csproj-step.yml +++ b/eng/pipelines/onebranch/steps/pack-csproj-step.yml @@ -28,8 +28,8 @@ steps: msbuildArguments: >- -t:${{ parameters.packTarget }} -p:ReferenceType=Package - -p:PackagesDir=$(PACK_OUTPUT)/ + -p:PackagesDir=$(JOB_OUTPUT)/packages/ ${{ parameters.versionProperties }} - - script: tree /a /f $(PACK_OUTPUT) + - script: tree /a /f $(JOB_OUTPUT)/packages displayName: List Pack Output Tree After Pack diff --git a/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml b/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml index 933c37f6a6..73229fe834 100644 --- a/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml +++ b/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml @@ -34,9 +34,9 @@ steps: displayName: Output Build Output Tree - task: CopyFiles@2 - displayName: 'Copy NuGet Packages to PACK_OUTPUT' + displayName: 'Copy NuGet Packages to JOB_OUTPUT' inputs: contents: '**/Microsoft.Data.SqlClient*.*nupkg' flattenFolders: true sourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/' - targetFolder: '$(PACK_OUTPUT)' + targetFolder: '$(JOB_OUTPUT)/packages' diff --git a/eng/pipelines/onebranch/steps/publish-symbols-step.yml b/eng/pipelines/onebranch/steps/publish-symbols-step.yml index 48ee30b9c2..d06b3c73e5 100644 --- a/eng/pipelines/onebranch/steps/publish-symbols-step.yml +++ b/eng/pipelines/onebranch/steps/publish-symbols-step.yml @@ -4,7 +4,23 @@ # 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 +# Symbols Publishing Pipeline +# =========================== +# Canonical docs: https://www.osgwiki.com/wiki/Symbols_Publishing_Pipeline_to_SymWeb_and_MSDL +# +# Publishing symbols to SymWeb (internal) and MSDL (public) is a two-step process: +# +# Step 1 - Upload: The PublishSymbols@2 task uploads PDB files to the Azure DevOps +# symbol store under an artifact name (SymbolsArtifactName). This stores the +# symbols but does NOT make them available on SymWeb or MSDL. +# +# Step 2 - Publish: The publish-symbols.ps1 script calls the Symbols Publishing Pipeline +# REST API to request that the previously uploaded symbols be published to the +# internal (SymWeb) and/or public (MSDL) symbol servers. +# +# IMPORTANT: Step 2 depends on Step 1. The ArtifactName parameter passed to the publish +# script MUST match the SymbolsArtifactName used by PublishSymbols@2 so that both steps +# reference the same uploaded artifact. parameters: # Name of the symbols artifact that will be published @@ -43,6 +59,13 @@ parameters: - name: searchPattern type: string + # Root folder to search for PDB files. When called from a build job this is typically + # $(BUILD_OUTPUT); when called from the dedicated symbols stage it points at the + # downloaded artifact path containing the PDBs. + - name: symbolsFolder + type: string + default: '$(BUILD_OUTPUT)' + # Account/org where the symbols will be uploaded - name: uploadAccount type: string @@ -60,8 +83,46 @@ steps: - script: 'echo ##vso[task.setvariable variable=ArtifactServices.Symbol.AccountName;]${{ parameters.uploadAccount }}' displayName: 'Set ArtifactServices.Symbol.AccountName to ${{ parameters.uploadAccount }}' + # Log the PDB files that match the search pattern so we can verify no + # unexpected files are included in the upload. + - pwsh: | + $folder = '${{ parameters.symbolsFolder }}' + $glob = '${{ parameters.searchPattern }}' + Write-Host "Symbols folder : $folder" + Write-Host "Search pattern : $glob" + + # Convert the glob to a regex that can match against relative paths. + # ** → match any number of path segments (.*?) + # * → match within a single segment ([^/\\]*) + # ? → match a single non-separator char ([^/\\]) + # . → literal dot + $regex = [regex]::Escape($glob) + $regex = $regex -replace '\\\*\\\*[/\\]?', '.*?' # ** or **/ + $regex = $regex -replace '\\\*', '[^/\\]*' # single * + $regex = $regex -replace '\\\?', '[^/\\]' # single ? + $regex = '^' + $regex + '$' + Write-Host "Regex : $regex" + Write-Host "" + + $allFiles = Get-ChildItem -Path $folder -Recurse -File + $matched = $allFiles | Where-Object { + $rel = $_.FullName.Substring($folder.Length).TrimStart('/\') + $rel -match $regex + } + + if (-not $matched) { + Write-Host "##vso[task.logissue type=warning]No PDB files matched pattern '$glob' under '$folder'" + } else { + $count = @($matched).Count + Write-Host "Matched $count PDB file(s):" + $matched | ForEach-Object { Write-Host " $($_.FullName)" } + } + displayName: 'Log PDBs matching ${{ parameters.searchPattern }}' + + # Step 1 - Upload: Push PDB files to the Azure DevOps symbol store. + # The SymbolsArtifactName set here is the key that links this upload to Step 2. - task: PublishSymbols@2 - displayName: 'Upload symbols to ${{ parameters.uploadAccount }} org' + displayName: 'Step 1: Upload symbols to ${{ parameters.uploadAccount }} org' inputs: IndexSources: false Pat: '$(System.AccessToken)' @@ -69,85 +130,25 @@ steps: SymbolExpirationInDays: 1825 # 5 years SymbolServerType: 'TeamServices' SymbolsArtifactName: '${{ parameters.artifactName }}' - SymbolsFolder: '$(BUILD_OUTPUT)' + SymbolsFolder: '${{ parameters.symbolsFolder }}' SymbolsMaximumWaitTime: 60 SymbolsProduct: '${{ parameters.packageName }}' SymbolsVersion: '${{ parameters.version }}' + # Step 2 - Publish: Request the Symbols Publishing Pipeline to publish the uploaded + # symbols to SymWeb and/or MSDL. The -ArtifactName argument must match the + # SymbolsArtifactName from Step 1. - task: AzureCLI@2 - displayName: 'Publish Symbols' + displayName: 'Step 2: Publish symbols to SymWeb/MSDL' 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" + scriptType: pscore + scriptLocation: scriptPath + scriptPath: '$(Build.SourcesDirectory)/eng/pipelines/onebranch/scripts/publish-symbols.ps1' + arguments: >- + -PublishServer "${{ parameters.publishServer }}" + -PublishTokenUri "${{ parameters.publishTokenUri }}" + -PublishProjectName "${{ parameters.publishProjectName }}" + -ArtifactName "${{ parameters.artifactName }}" + -PublishToInternal $${{ parameters.publishToInternal }} + -PublishToPublic $${{ parameters.publishToPublic }} diff --git a/eng/pipelines/onebranch/variables/common-variables.yml b/eng/pipelines/onebranch/variables/common-variables.yml index a2a48ba184..3df1a6264f 100644 --- a/eng/pipelines/onebranch/variables/common-variables.yml +++ b/eng/pipelines/onebranch/variables/common-variables.yml @@ -23,13 +23,21 @@ variables: - name: BUILD_OUTPUT value: $(REPO_ROOT)/artifacts - # This is where downloaded artifacts should be placed. This will enable other build jobs to - # do local NuGet installations with packages built by earlier stages. - - name: PACK_INPUT + # Directory where downloaded pipeline artifacts (NuGet packages from earlier + # stages) are placed. Build jobs use this as a local NuGet package source so + # that downstream packages can resolve dependencies on packages built by + # upstream stages. + - name: JOB_INPUT value: $(REPO_ROOT)/packages - # This is where our C# projects place their NuGet package outputs. This is intentionally - # separated from packages/ (where downloaded pipeline artifacts go) so that ESRP signing and - # OneBranch artifact publishing only operate on packages built by the current job. - - name: PACK_OUTPUT + # Root directory for all job output artifacts (NuGet packages, symbols, etc.). + # OneBranch auto-publishes everything under this directory. It is intentionally + # separated from packages/ (where downloaded pipeline artifacts go) so that ESRP + # signing and OneBranch artifact publishing only operate on the current job's output. + # + # Sub-directory layout: + # assemblies/ - DLL assemblies for APIScan (preserving TFM folder structure) + # packages/ - NuGet packages (.nupkg, .snupkg) + # symbols/ - PDB symbol files (preserving TFM folder structure) + - name: JOB_OUTPUT value: $(REPO_ROOT)/output diff --git a/eng/pipelines/onebranch/variables/onebranch-variables.yml b/eng/pipelines/onebranch/variables/onebranch-variables.yml index 7b7c7597cb..b3bebb0a64 100644 --- a/eng/pipelines/onebranch/variables/onebranch-variables.yml +++ b/eng/pipelines/onebranch/variables/onebranch-variables.yml @@ -24,11 +24,11 @@ variables: # make it clear that these variables are used for symbols (as opposed to other msc tasks). # # SymbolsAzureSubscription - # SymbolsPublishProjectName - # SymbolsPublishServer - # SymbolsPublishTokenUri + # SymbolsPublishProjectNameSqlClient + # SymbolsPublishServerProd / SymbolsPublishServerPPE + # SymbolsPublishTokenUriProd / SymbolsPublishTokenUriPPE # SymbolsUploadAccount - - group: 'symbols-variables-v2' + - group: 'Symbols Publishing' # OneBranch Template Variables ########################################### From 8d2483b9c9431c38f8fa71ebdad1bc85037ec5cc Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 21 Apr 2026 07:36:29 -0300 Subject: [PATCH 053/298] Symbols Publishing Follow-up (#4214) --- .../onebranch-pipeline-design.instructions.md | 6 ++++- .../onebranch/jobs/publish-symbols-job.yml | 25 ++++++++++++++++--- .../onebranch/sqlclient-official.yml | 16 ++++++++---- .../onebranch/steps/publish-symbols-step.yml | 16 ++++++------ .../variables/onebranch-variables.yml | 10 ++++++-- 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/.github/instructions/onebranch-pipeline-design.instructions.md b/.github/instructions/onebranch-pipeline-design.instructions.md index 0860d5db52..f1520fa0e0 100644 --- a/.github/instructions/onebranch-pipeline-design.instructions.md +++ b/.github/instructions/onebranch-pipeline-design.instructions.md @@ -70,6 +70,8 @@ When adding a new csproj-based package: - The `publish-symbols-step.yml` accepts a `symbolsFolder` parameter to point at the downloaded PDB location - The publish step calls an extracted `publish-symbols.ps1` script with structured error handling and diagnostic logging - Symbols publishing credentials come from the `Symbols Publishing` variable group +- In the official pipeline, symbol server destination follows `releaseToProduction`: Production when true, PPE when false +- Non-official pipeline always targets the PPE symbol server ## Release Stage @@ -99,7 +101,9 @@ Release parameters (all boolean, default `false`): - `releaseSqlServerServer`, `releaseLogging`, `releaseAbstractions`, `releaseSqlClient`, `releaseAzure`, `releaseAKVProvider` Official-only parameter: -- `releaseToProduction` — push to NuGet Production feed (default `false`) +- `releaseToProduction` — controls both NuGet target feed and symbol server destination (default `false`): + - `true` → NuGet Production feed + Production symbol server + - `false` → NuGet Test feed + PPE symbol server When `isPreview` is true, pipeline resolves `effective*Version` variables to preview versions; otherwise GA versions. All versions defined in `variables/common-variables.yml`. diff --git a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml index 21b20476cd..de2d7e4634 100644 --- a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml +++ b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml @@ -54,11 +54,13 @@ jobs: - job: publish_symbols_${{ parameters.packageName }} displayName: 'Publish Symbols: ${{ parameters.packageFullName }}' pool: - type: windows + type: linux variables: - # OneBranch requires ob_outputDirectory to be set, even if this job produces no artifacts. - ob_outputDirectory: $(JOB_OUTPUT) + # OneBranch requires ob_outputDirectory to be set. Pipeline Artifacts are always on and + # cannot be disabled. To prevent this job from publishing artifacts, a .artifactignore + # that excludes all files is written into ob_outputDirectory before the auto-publish step. + ob_outputDirectory: $(Build.SourcesDirectory)/no_publish # Disable SDL scanning — this job only uploads/publishes PDBs and produces no # assemblies to scan. APIScan and BinSkim are handled by the build jobs. @@ -72,7 +74,24 @@ jobs: # Path to the PDB files within the downloaded artifact. symbolsPath: $(Pipeline.Workspace)/${{ parameters.artifactName }}/symbols + # PublishSymbols@2 runs on the OneBranch host agent (outside the build container) due to 1ES + # Pipeline Template credential isolation. On Linux, the host resolves to the Microsoft org by + # default. Setting this variable at job level ensures the task sees it and connects to the + # correct org's symbol store. + # + # Reference: + # https://www.osgwiki.com/wiki/Symbols_Publishing_Pipeline_to_SymWeb_and_MSDL#Option_B:_OneBranch + # + ArtifactServices.Symbol.AccountName: ${{ parameters.symbolsUploadAccount }} + steps: + # Create ob_outputDirectory with a .artifactignore that excludes everything, + # so OneBranch's auto-publish uploads an empty artifact. + - pwsh: | + New-Item -Path "$(ob_outputDirectory)" -ItemType Directory -Force + "**" | Out-File -FilePath "$(ob_outputDirectory)/.artifactignore" -Encoding ascii + displayName: 'Suppress artifact publishing' + - task: DownloadPipelineArtifact@2 displayName: 'Download ${{ parameters.packageFullName }} Artifact' inputs: diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index a2902f1e21..96fb0dc5f5 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -33,9 +33,10 @@ parameters: type: boolean default: false - # Push packages to NuGet Production (otherwise pushes to NuGet Test). + # When true, publish symbols and push NuGet packages to Production environments. When false, + # symbols use PPE and NuGet packages use QA/Test. - name: releaseToProduction - displayName: Release to NuGet Production + displayName: Publish Symbols and NuGet Packages to Production type: boolean default: false @@ -254,9 +255,14 @@ extends: symbolsAzureSubscription: '$(SymbolsAzureSubscription)' symbolsPublishProjectName: '$(SymbolsPublishProjectNameSqlClient)' - # Official pipelines must publish to the Production symbol server. - symbolsPublishServer: '$(SymbolsPublishServerProd)' - symbolsPublishTokenUri: '$(SymbolsPublishTokenUriProd)' + # Symbol server target follows releaseToProduction: Production for + # real releases, PPE for test/QA releases. + ${{ if eq(parameters.releaseToProduction, true) }}: + symbolsPublishServer: '$(SymbolsPublishServerProd)' + symbolsPublishTokenUri: '$(SymbolsPublishTokenUriProd)' + ${{ else }}: + symbolsPublishServer: '$(SymbolsPublishServerPPE)' + symbolsPublishTokenUri: '$(SymbolsPublishTokenUriPPE)' symbolsUploadAccount: '$(SymbolsUploadAccount)' - template: /eng/pipelines/onebranch/stages/release-stages.yml@self diff --git a/eng/pipelines/onebranch/steps/publish-symbols-step.yml b/eng/pipelines/onebranch/steps/publish-symbols-step.yml index d06b3c73e5..dc794f480d 100644 --- a/eng/pipelines/onebranch/steps/publish-symbols-step.yml +++ b/eng/pipelines/onebranch/steps/publish-symbols-step.yml @@ -75,16 +75,16 @@ parameters: type: string steps: - # Set variable for downstream tasks (allegedly). + # NOTE: ArtifactServices.Symbol.AccountName is set as a job-level variable in + # publish-symbols-job.yml. On OneBranch Linux agents, PublishSymbols@2 runs on the host (outside + # the build container) due to 1ES PT credential isolation. A ##vso[task.setvariable] inside the + # container is not visible to host-level tasks, so the variable must be declared at job scope. # - # 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 }}' + # Reference: + # https://www.osgwiki.com/wiki/Symbols_Publishing_Pipeline_to_SymWeb_and_MSDL#Option_B:_OneBranch - # Log the PDB files that match the search pattern so we can verify no - # unexpected files are included in the upload. + # Log the PDB files that match the search pattern so we can verify no unexpected files are + # included in the upload. - pwsh: | $folder = '${{ parameters.symbolsFolder }}' $glob = '${{ parameters.searchPattern }}' diff --git a/eng/pipelines/onebranch/variables/onebranch-variables.yml b/eng/pipelines/onebranch/variables/onebranch-variables.yml index b3bebb0a64..d5fa784885 100644 --- a/eng/pipelines/onebranch/variables/onebranch-variables.yml +++ b/eng/pipelines/onebranch/variables/onebranch-variables.yml @@ -39,6 +39,12 @@ variables: - name: Packaging.EnableSBOMSigning value: true - # Docker image which is used to build the project https://aka.ms/obpipelines/containers + # OneBranch supplies a variety of container images we must use for our jobs. + # + # Windows jobs use this image. - name: WindowsContainerImage - value: "onebranch.azurecr.io/windows/ltsc2022/vse2022:latest" + value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest + + # Linux jobs use this image. + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 From 3ae3274aa122c16e9057612400a1eee1f3ec4388 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:11:16 +0100 Subject: [PATCH 054/298] Merge | Port SqlBatch support to netfx (#3926) * Publicly expose SqlBatch* types on netfx and enable tests APIs not exposed: * SqlClientFactory.CanCreateBatch * SqlClientFactory.CreateBatch * SqlClientFactory.CreateBatchCommand * SqlConnection.CanCreateBatch * SqlConnection.CreateBatch * Post-merge reconciliation * Add missing members to ref assembly * Split conditional compilation blocks --- .../ref/Microsoft.Data.SqlClient.cs | 173 +++++++++++++++++- .../src/Microsoft/Data/SqlClient/SqlBatch.cs | 170 ++++++++++++++--- .../Data/SqlClient/SqlBatchCommand.cs | 44 +++-- .../Data/SqlClient/SqlBatchCommand.netcore.cs | 21 --- .../SqlClient/SqlBatchCommandCollection.cs | 116 ++++++++---- .../Data/SqlClient/SqlCommand.Scalar.cs | 2 - .../Microsoft/Data/SqlClient/SqlException.cs | 13 +- ...icrosoft.Data.SqlClient.ManualTests.csproj | 2 +- .../{BatchTests.netcore.cs => BatchTests.cs} | 26 ++- 9 files changed, 444 insertions(+), 123 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommand.netcore.cs rename src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/{BatchTests.netcore.cs => BatchTests.cs} (97%) diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs index ce6e481434..de581dbb0e 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs @@ -47,137 +47,287 @@ protected SqlAuthenticationInitializer() { } public abstract void Initialize(); } -#if NET /// +#if NET public class SqlBatch : System.Data.Common.DbBatch +#else +public class SqlBatch : System.IDisposable, System.IAsyncDisposable +#endif { /// public SqlBatch() { throw null; } /// public SqlBatch(Microsoft.Data.SqlClient.SqlConnection connection, Microsoft.Data.SqlClient.SqlTransaction transaction = null) { throw null; } /// + #if NET public override int Timeout { get => throw null; set { } } + #else + public int Timeout { get => throw null; set { } } + #endif /// public System.Collections.Generic.List Commands { get { throw null; } } /// + #if NET public new Microsoft.Data.SqlClient.SqlConnection Connection { get => throw null; set { } } + #else + public Microsoft.Data.SqlClient.SqlConnection Connection { get => throw null; set { } } + #endif /// + #if NET public new Microsoft.Data.SqlClient.SqlTransaction Transaction { get => throw null; set { } } + #else + public Microsoft.Data.SqlClient.SqlTransaction Transaction { get => throw null; set { } } + #endif /// + #if NET public new SqlBatchCommandCollection BatchCommands { get => throw null; } + #else + public SqlBatchCommandCollection BatchCommands { get => throw null; } + #endif + #if NET /// protected override System.Data.Common.DbBatchCommandCollection DbBatchCommands { get => throw null; } + #endif /// + #if NET protected override System.Data.Common.DbConnection DbConnection { get => throw null; set { } } + #else + protected virtual System.Data.Common.DbConnection DbConnection { get => throw null; set { } } + #endif /// + #if NET protected override System.Data.Common.DbTransaction DbTransaction { get => throw null; set { } } + #else + protected virtual System.Data.Common.DbTransaction DbTransaction { get => throw null; set { } } + #endif /// + #if NET public override void Cancel() => throw null; - /// + #else + public void Cancel() => throw null; + #endif + #if NET + /// protected override System.Data.Common.DbBatchCommand CreateDbBatchCommand() => throw null; + #endif /// + #if NET public override void Dispose() => throw null; + #else + public void Dispose() => throw null; + #endif + #if !NET + /// + public virtual System.Threading.Tasks.ValueTask DisposeAsync() => throw null; + #endif /// + #if NET protected override System.Data.Common.DbDataReader ExecuteDbDataReader(System.Data.CommandBehavior behavior) => throw null; + #else + protected virtual System.Data.Common.DbDataReader ExecuteDbDataReader(System.Data.CommandBehavior behavior) => throw null; + #endif /// + #if NET protected override System.Threading.Tasks.Task ExecuteDbDataReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken) => throw null; + #else + protected virtual System.Threading.Tasks.Task ExecuteDbDataReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken) => throw null; + #endif /// + #if NET public override int ExecuteNonQuery() => throw null; + #else + public int ExecuteNonQuery() => throw null; + #endif /// + #if NET public override System.Threading.Tasks.Task ExecuteNonQueryAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; + #else + public System.Threading.Tasks.Task ExecuteNonQueryAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; + #endif /// public Microsoft.Data.SqlClient.SqlDataReader ExecuteReader() => throw null; /// + #if NET public new System.Threading.Tasks.Task ExecuteReaderAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; + #else + public System.Threading.Tasks.Task ExecuteReaderAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; + #endif /// + #if NET public override object ExecuteScalar() => throw null; + #else + public object ExecuteScalar() => throw null; + #endif /// + #if NET public override System.Threading.Tasks.Task ExecuteScalarAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; + #else + public System.Threading.Tasks.Task ExecuteScalarAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; + #endif /// + #if NET public override void Prepare() => throw null; + #else + public void Prepare() => throw null; + #endif /// + #if NET public override System.Threading.Tasks.Task PrepareAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; + #else + public System.Threading.Tasks.Task PrepareAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; + #endif } -#endif -#if NET /// -public partial class SqlBatchCommand : System.Data.Common.DbBatchCommand +#if NET +public class SqlBatchCommand : System.Data.Common.DbBatchCommand +#else +public class SqlBatchCommand +#endif { /// public SqlBatchCommand() => throw null; /// public SqlBatchCommand(string commandText, System.Data.CommandType commandType = System.Data.CommandType.Text, System.Collections.Generic.IEnumerable parameters = null, Microsoft.Data.SqlClient.SqlCommandColumnEncryptionSetting columnEncryptionSetting = Microsoft.Data.SqlClient.SqlCommandColumnEncryptionSetting.UseConnectionSetting) { throw null; } /// + #if NET public new Microsoft.Data.SqlClient.SqlParameterCollection Parameters { get { throw null; } } + #else + public Microsoft.Data.SqlClient.SqlParameterCollection Parameters { get { throw null; } } + #endif + /// + #if NET + public override bool CanCreateParameter { get { throw null; } } + #else + public bool CanCreateParameter { get { throw null; } set { } } + #endif /// + #if NET public override string CommandText { get { throw null; } set { } } + #else + public string CommandText { get { throw null; } set { } } + #endif /// + #if NET public override System.Data.CommandType CommandType { get { throw null; } set { } } + #else + public System.Data.CommandType CommandType { get { throw null; } set { } } + #endif /// public System.Data.CommandBehavior CommandBehavior { get { throw null; } set { } } + /// + #if NET + public override System.Data.Common.DbParameter CreateParameter() => throw null; + #else + public System.Data.Common.DbParameter CreateParameter() => throw null; + #endif /// + #if NET public override int RecordsAffected { get { throw null; } } + #else + public int RecordsAffected { get { throw null; } } + #endif /// + #if NET protected override System.Data.Common.DbParameterCollection DbParameterCollection => throw null; + #else + protected virtual System.Data.Common.DbParameterCollection DbParameterCollection => throw null; + #endif - #if NET /// public Microsoft.Data.SqlClient.SqlCommandColumnEncryptionSetting ColumnEncryptionSetting { get { throw null; } set { } } - #endif } -#endif -#if NET /// +#if NET public class SqlBatchCommandCollection : System.Data.Common.DbBatchCommandCollection, System.Collections.Generic.IList +#else +public class SqlBatchCommandCollection : System.Collections.Generic.IList +#endif { /// + #if NET public override int Count => throw null; + #else + public int Count => throw null; + #endif /// + #if NET public override bool IsReadOnly => throw null; + #else + public bool IsReadOnly => throw null; + #endif System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() => throw null; + #if NET /// public override System.Collections.Generic.IEnumerator GetEnumerator() => throw null; + #endif /// public void Add(Microsoft.Data.SqlClient.SqlBatchCommand item) => throw null; + #if NET /// public override void Add(System.Data.Common.DbBatchCommand item) => throw null; + #endif /// + #if NET public override void Clear() => throw null; + #else + public void Clear() => throw null; + #endif /// public bool Contains(Microsoft.Data.SqlClient.SqlBatchCommand item) => throw null; + #if NET /// public override bool Contains(System.Data.Common.DbBatchCommand item) => throw null; + #endif /// public void CopyTo(Microsoft.Data.SqlClient.SqlBatchCommand[] array, int arrayIndex) => throw null; + #if NET /// public override void CopyTo(System.Data.Common.DbBatchCommand[] array, int arrayIndex) => throw null; + #endif /// public int IndexOf(Microsoft.Data.SqlClient.SqlBatchCommand item) => throw null; + #if NET /// public override int IndexOf(System.Data.Common.DbBatchCommand item) => throw null; + #endif /// public void Insert(int index, Microsoft.Data.SqlClient.SqlBatchCommand item) => throw null; + #if NET /// public override void Insert(int index, System.Data.Common.DbBatchCommand item) => throw null; + #endif /// public bool Remove(Microsoft.Data.SqlClient.SqlBatchCommand item) => throw null; + #if NET /// public override bool Remove(System.Data.Common.DbBatchCommand item) => throw null; + #endif /// + #if NET public override void RemoveAt(int index) => throw null; + #else + public void RemoveAt(int index) => throw null; + #endif /// Microsoft.Data.SqlClient.SqlBatchCommand System.Collections.Generic.IList.this[int index] { get => throw null; set { } } /// + #if NET public new Microsoft.Data.SqlClient.SqlBatchCommand this[int index] { get => throw null; set { } } + #else + public Microsoft.Data.SqlClient.SqlBatchCommand this[int index] { get => throw null; set { } } + #endif System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null; + #if NET /// protected override System.Data.Common.DbBatchCommand GetBatchCommand(int index) => throw null; + #endif + #if NET /// protected override void SetBatchCommand(int index, System.Data.Common.DbBatchCommand batchCommand) => throw null; + #endif } -#endif /// public sealed class SqlBulkCopy : System.IDisposable @@ -1653,6 +1803,9 @@ private SqlException(System.Runtime.Serialization.SerializationInfo info, System #if NET /// public new Microsoft.Data.SqlClient.SqlBatchCommand BatchCommand { get { throw null; } } + #else + /// + public Microsoft.Data.SqlClient.SqlBatchCommand BatchCommand { get { throw null; } } #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 7171bdbfe3..a1f79f6f15 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatch.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatch.cs @@ -1,9 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Generic; using System.Data; @@ -15,7 +13,12 @@ namespace Microsoft.Data.SqlClient { /// - public class SqlBatch : DbBatch + public class SqlBatch : + #if NET + DbBatch + #else + IDisposable, IAsyncDisposable + #endif { private SqlCommand _batchCommand; private List _commands; @@ -33,8 +36,30 @@ public SqlBatch(SqlConnection connection = null, SqlTransaction transaction = nu Connection = connection; Transaction = transaction; } + + #if NET + /// + protected override DbBatchCommandCollection DbBatchCommands => BatchCommands; + + /// + protected override DbBatchCommand CreateDbBatchCommand() => new SqlBatchCommand(); + #endif + + #if NETFRAMEWORK + /// + public virtual ValueTask DisposeAsync() + { + Dispose(); + return default; + } + #endif + /// - public override int Timeout + public + #if NET + override + #endif + int Timeout { get { @@ -47,12 +72,22 @@ public override int Timeout _batchCommand.CommandTimeout = value; } } - /// - protected override DbBatchCommandCollection DbBatchCommands => BatchCommands; + /// - public new SqlBatchCommandCollection BatchCommands => _providerCommands != null ? _providerCommands : _providerCommands = new SqlBatchCommandCollection(Commands); // Commands call will check disposed + public + #if NET + new + #endif + SqlBatchCommandCollection BatchCommands => _providerCommands != null ? _providerCommands : _providerCommands = new SqlBatchCommandCollection(Commands); // Commands call will check disposed + /// - protected override DbConnection DbConnection + protected + #if NET + override + #else + virtual + #endif + DbConnection DbConnection { get { @@ -65,8 +100,15 @@ protected override DbConnection DbConnection Connection = (SqlConnection)value; } } + /// - protected override DbTransaction DbTransaction + protected + #if NET + override + #else + virtual + #endif + DbTransaction DbTransaction { get { @@ -79,60 +121,103 @@ protected override DbTransaction DbTransaction Transaction = (SqlTransaction)value; } } + /// - public override void Cancel() + public + #if NET + override + #endif + void Cancel() { CheckDisposed(); _batchCommand.Cancel(); } + /// - public override int ExecuteNonQuery() + public + #if NET + override + #endif + int ExecuteNonQuery() { CheckDisposed(); SetupBatchCommandExecute(); return _batchCommand.ExecuteNonQuery(); } + /// - public override Task ExecuteNonQueryAsync(CancellationToken cancellationToken = default) + public + #if NET + override + #endif + Task ExecuteNonQueryAsync(CancellationToken cancellationToken = default) { CheckDisposed(); SetupBatchCommandExecute(); return _batchCommand.ExecuteNonQueryAsync(cancellationToken); } + /// - public override object ExecuteScalar() + public + #if NET + override + #endif + object ExecuteScalar() { CheckDisposed(); SetupBatchCommandExecute(); return _batchCommand.ExecuteScalar(); } + /// - public override Task ExecuteScalarAsync(CancellationToken cancellationToken = default) + public + #if NET + override + #endif + Task ExecuteScalarAsync(CancellationToken cancellationToken = default) { CheckDisposed(); SetupBatchCommandExecute(); return _batchCommand.ExecuteScalarBatchAsync(cancellationToken); } + /// - public override void Prepare() + public + #if NET + override + #endif + void Prepare() { CheckDisposed(); } + /// - public override Task PrepareAsync(CancellationToken cancellationToken = default) + public + #if NET + override + #endif + Task PrepareAsync(CancellationToken cancellationToken = default) { CheckDisposed(); return Task.CompletedTask; } + /// - public override void Dispose() + public + #if NET + override + #endif + void Dispose() { _batchCommand?.Dispose(); _batchCommand = null; _commands?.Clear(); _commands = null; + #if NET base.Dispose(); + #endif } + /// public List Commands { @@ -142,8 +227,13 @@ public List Commands return _commands != null ? _commands : _commands = new List(); } } + /// - public new SqlConnection Connection + public + #if NET + new + #endif + SqlConnection Connection { get { @@ -156,8 +246,13 @@ public List Commands _batchCommand.Connection = value; } } + /// - public new SqlTransaction Transaction + public + #if NET + new + #endif + SqlTransaction Transaction { get { @@ -170,6 +265,7 @@ public List Commands _batchCommand.Transaction = value; } } + /// public SqlDataReader ExecuteReader() { @@ -177,17 +273,36 @@ public SqlDataReader ExecuteReader() SetupBatchCommandExecute(); return _batchCommand.ExecuteReader(); } + /// - public new Task ExecuteReaderAsync(CancellationToken cancellationToken = default) + public + #if NET + new + #endif + Task ExecuteReaderAsync(CancellationToken cancellationToken = default) { CheckDisposed(); SetupBatchCommandExecute(); return _batchCommand.ExecuteReaderAsync(cancellationToken); } + /// - protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteReader(); + protected + #if NET + override + #else + virtual + #endif + DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteReader(); + /// - protected override Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) + protected + #if NET + override + #else + virtual + #endif + Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) { CheckDisposed(); SetupBatchCommandExecute(); @@ -204,12 +319,8 @@ protected override Task ExecuteDbDataReaderAsync(CommandBehavior b TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default ); - } - /// - protected override DbBatchCommand CreateDbBatchCommand() - { - return new SqlBatchCommand(); } + private void CheckDisposed() { if (_batchCommand is null) @@ -217,6 +328,7 @@ private void CheckDisposed() throw ADP.ObjectDisposed(this); } } + private void SetupBatchCommandExecute() { SqlConnection connection = Connection; @@ -240,5 +352,3 @@ private void SetupBatchCommandExecute() } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommand.cs index 3ac8af5a0e..460197a1bc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommand.cs @@ -10,10 +10,10 @@ namespace Microsoft.Data.SqlClient { /// - public partial class SqlBatchCommand -#if NET + public class SqlBatchCommand + #if NET : DbBatchCommand -#endif + #endif { private string _text; private CommandType _type; @@ -68,18 +68,25 @@ internal SqlBatchCommand(string commandText, SqlParameterCollection parameterCol _parameters = parameterCollection; } + /// + public + #if NET + override + #endif + bool CanCreateParameter => true; + /// public -#if NET + #if NET override -#endif + #endif string CommandText { get => _text; set => _text = value; } /// public -#if NET + #if NET override -#endif + #endif CommandType CommandType { get => _type; set => SetCommandType(value); } /// @@ -87,23 +94,25 @@ internal SqlBatchCommand(string commandText, SqlParameterCollection parameterCol /// public -#if NET + #if NET override -#endif + #endif int RecordsAffected { get => _recordsAffected; } /// protected -#if NET - override -#endif + #if NET + override + #else + virtual + #endif DbParameterCollection DbParameterCollection => Parameters; /// public -#if NET + #if NET new -#endif + #endif SqlParameterCollection Parameters { get @@ -123,6 +132,13 @@ internal set /// public SqlCommandColumnEncryptionSetting ColumnEncryptionSetting { get => _encryptionSetting; set => _encryptionSetting = value; } + /// + public + #if NET + override + #endif + DbParameter CreateParameter() => new SqlParameter(); + private void SetCommandType(CommandType value) { if (value != _type) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommand.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommand.netcore.cs deleted file mode 100644 index e55677b96b..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommand.netcore.cs +++ /dev/null @@ -1,21 +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. - -#if NET - -using System.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - public partial class SqlBatchCommand - { - /// - public override DbParameter CreateParameter() => new SqlParameter(); - - /// - public override bool CanCreateParameter => true; - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommandCollection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommandCollection.cs index e4f68aea5d..0546fdfd80 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommandCollection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatchCommandCollection.cs @@ -1,15 +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 NET +using System.Collections; using System.Collections.Generic; using System.Data.Common; namespace Microsoft.Data.SqlClient { /// - public class SqlBatchCommandCollection : DbBatchCommandCollection, IList + public class SqlBatchCommandCollection : + #if NET + DbBatchCommandCollection, + #endif + IList { readonly List _list; @@ -17,63 +21,107 @@ internal SqlBatchCommandCollection(List batchCommands) { _list = batchCommands; } + /// - public override int Count => _list.Count; + public + #if NET + override + #endif + int Count => _list.Count; + /// - public override bool IsReadOnly => false; - IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + public + #if NET + override + #endif + bool IsReadOnly => false; + + /// + public + #if NET + override + #endif + void Clear() => _list.Clear(); + + /// + public + #if NET + override + #endif + void RemoveAt(int index) => _list.RemoveAt(index); + + /// + public + #if NET + new + #endif + SqlBatchCommand this[int index] + { + get => _list[index]; + set => _list[index] = value; + } + + #if NET /// public override IEnumerator GetEnumerator() => _list.GetEnumerator(); - /// - public void Add(SqlBatchCommand item) => _list.Add(item); + /// public override void Add(DbBatchCommand item) => Add((SqlBatchCommand)item); - /// - public override void Clear() => _list.Clear(); - /// - public bool Contains(SqlBatchCommand item) => _list.Contains(item); + /// public override bool Contains(DbBatchCommand item) => Contains((SqlBatchCommand)item); - /// - public void CopyTo(SqlBatchCommand[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); + /// public override void CopyTo(DbBatchCommand[] array, int arrayIndex) { SqlBatchCommand[] target = (SqlBatchCommand[])array; CopyTo(target, arrayIndex); } - /// - public int IndexOf(SqlBatchCommand item) => _list.IndexOf(item); + /// public override int IndexOf(DbBatchCommand item) => IndexOf((SqlBatchCommand)item); - /// - public void Insert(int index, SqlBatchCommand item) => _list.Insert(index, item); + /// public override void Insert(int index, DbBatchCommand item) => Insert(index, (SqlBatchCommand)item); - /// - public bool Remove(SqlBatchCommand item) => _list.Remove(item); + /// public override bool Remove(DbBatchCommand item) => Remove((SqlBatchCommand)item); - /// - public override void RemoveAt(int index) => _list.RemoveAt(index); + + /// + protected override DbBatchCommand GetBatchCommand(int index) => _list[index]; + + /// + protected override void SetBatchCommand(int index, DbBatchCommand batchCommand) + => _list[index] = (SqlBatchCommand)batchCommand; + #else + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + #endif + + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + + /// + public void Add(SqlBatchCommand item) => _list.Add(item); + + /// + public bool Contains(SqlBatchCommand item) => _list.Contains(item); + + /// + public void CopyTo(SqlBatchCommand[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); + + /// + public int IndexOf(SqlBatchCommand item) => _list.IndexOf(item); + + /// + public void Insert(int index, SqlBatchCommand item) => _list.Insert(index, item); + + /// + public bool Remove(SqlBatchCommand item) => _list.Remove(item); + /// SqlBatchCommand IList.this[int index] { get => _list[index]; set => _list[index] = value; } - /// - public new SqlBatchCommand this[int index] - { - get => _list[index]; - set => _list[index] = value; - } - /// - protected override DbBatchCommand GetBatchCommand(int index) => _list[index]; - /// - protected override void SetBatchCommand(int index, DbBatchCommand batchCommand) - => _list[index] = (SqlBatchCommand)batchCommand; } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Scalar.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Scalar.cs index 2a47e1466f..0582d23d98 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Scalar.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Scalar.cs @@ -82,7 +82,6 @@ public override Task ExecuteScalarAsync(CancellationToken cancellationTo return ExecuteScalarAsyncInternal(cancellationToken); } - #if NET internal Task ExecuteScalarBatchAsync(CancellationToken cancellationToken) { Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); @@ -167,7 +166,6 @@ internal Task ExecuteScalarBatchAsync(CancellationToken cancellationToke }, TaskScheduler.Default).Unwrap(); } - #endif #endregion diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs index 1a606242c2..a1735fabe7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs @@ -117,7 +117,7 @@ public override void GetObjectData(SerializationInfo si, StreamingContext contex override public string Source => DbConnectionStringDefaults.ApplicationName; -#if NET + #if NET /// protected override DbBatchCommand DbBatchCommand => BatchCommand; @@ -127,14 +127,15 @@ public override void GetObjectData(SerializationInfo si, StreamingContext contex get => _batchCommand; internal set => _batchCommand = value; } -#else - internal SqlBatchCommand BatchCommand + #else + /// + public SqlBatchCommand BatchCommand { get => _batchCommand; - set => _batchCommand = value; + internal set => _batchCommand = value; } -#endif - + #endif + /// public override string ToString() { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index c28f1cfeb7..110547a5de 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -191,7 +191,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.netcore.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.netcore.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs index 175e81521b..991d55cdeb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.netcore.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs @@ -1,9 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Generic; using System.Data; @@ -52,8 +50,12 @@ public static void ConnectionCanCreateBatch() { using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) { + #if NET Assert.True(connection.CanCreateBatch); using (var batch = connection.CreateBatch()) + #else + using (var batch = new SqlBatch(connection)) + #endif { Assert.NotNull(batch); Assert.Equal(connection, batch.Connection); @@ -70,7 +72,11 @@ public static void SqlBatchCanCreateParameter() { using var connection = new SqlConnection(DataTestUtility.TCPConnectionString); connection.Open(); +#if NET using DbBatch batch = connection.CreateBatch(); +#else + using SqlBatch batch = new SqlBatch(connection); +#endif SqlBatchCommand batchCommand = new SqlBatchCommand("SELECT @p"); Assert.True(batchCommand.CanCreateParameter); @@ -154,7 +160,11 @@ public static void ProviderApi() int resultCount = 0; int rowCount = 0; var dbProviderFactory = SqlClientFactory.Instance; + #if NET DbBatch batch = dbProviderFactory.CreateBatch(); + #else + SqlBatch batch = new SqlBatch(); + #endif using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) { connection.Open(); @@ -167,7 +177,11 @@ public static void ProviderApi() p2.ParameterName = "@p2"; p1.Value = 50.0f; p2.Value = 10248; + #if NET DbBatchCommand command = dbProviderFactory.CreateBatchCommand(); + #else + SqlBatchCommand command = new SqlBatchCommand(); + #endif command.CommandText = "UPDATE Orders SET Freight=@p1 WHERE OrderID=@p2"; command.Parameters.Add(p1); command.Parameters.Add(p2); @@ -178,7 +192,11 @@ public static void ProviderApi() DbParameter parameter = dbProviderFactory.CreateParameter(); parameter.ParameterName = "@p4"; parameter.Value = 10248; + #if NET DbBatchCommand command = dbProviderFactory.CreateBatchCommand(); + #else + SqlBatchCommand command = new SqlBatchCommand(); + #endif command.CommandText = $"SELECT Freight FROM Orders WHERE OrderID={parameter.ParameterName}"; command.Parameters.Add(parameter); batch.BatchCommands.Add(command); @@ -663,5 +681,3 @@ private static bool TryExecuteNonQueryCommand(string command) } } } - -#endif From 3e2f7ea184de5e14d8cb1aa5b174ec6fac3dae07 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Wed, 22 Apr 2026 03:36:00 +1000 Subject: [PATCH 055/298] avoid redundant exception type checks (#4100) --- .../ManagedSni/SniTcpHandle.netcore.cs | 6 +++-- .../SqlAuthenticationProviderManager.cs | 22 +++++++++---------- .../Data/SqlClient/SqlQueryMetadataCache.cs | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs index 9ce9684235..c7d562750a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs @@ -221,8 +221,10 @@ public SniTcpHandle( { throw; } - if (exRetry is SocketException || exRetry is ArgumentNullException - || exRetry is ArgumentException || exRetry is ArgumentOutOfRangeException || exRetry is AggregateException) + if (exRetry is + SocketException or + ArgumentException or + AggregateException) { SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Retrying exception {1}", args0: _connectionId, args1: exRetry?.Message); if (parallel) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index 54468782bd..653063b68e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -188,17 +188,17 @@ static SqlAuthenticationProviderManager() // simply have no default and the app must provide one if they // attempt to use Active Directory authentication. catch (Exception ex) - when (ex is ArgumentNullException || - ex is ArgumentException || - ex is BadImageFormatException || - ex is FileLoadException || - ex is FileNotFoundException || - ex is MemberAccessException || - ex is MethodAccessException || - ex is MissingMethodException || - ex is NotSupportedException || - ex is TargetInvocationException || - ex is TypeLoadException) + when (ex is + ArgumentException or + BadImageFormatException or + FileLoadException or + FileNotFoundException or + MemberAccessException or + MethodAccessException or + MissingMethodException or + NotSupportedException or + TargetInvocationException or + TypeLoadException) { SqlClientEventSource.Log.TryTraceEvent( nameof(SqlAuthenticationProviderManager) + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs index a4a35840ac..51f0abc7cf 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs @@ -129,7 +129,7 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand) InvalidateCacheEntry(sqlCommand); // If we get one of the expected exceptions, just fail the cache lookup, otherwise throw. - if (ex is SqlException || ex is ArgumentException || ex is ArgumentNullException) + if (ex is SqlException or ArgumentException) { foreach (SqlParameter paramToCleanup in sqlCommand.Parameters) { From 8cd5dda272bef5e7a7198cb8f1285d7becf64b0b Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:57:54 -0700 Subject: [PATCH 056/298] Update .NET SDK to v10.0.106 (#4218) --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 3d74096930..b7b9908783 100644 --- a/global.json +++ b/global.json @@ -7,7 +7,7 @@ // .NET 10 SDK versions in the 10.0.2xx series require MSBuild 18.x, so we specify the most // recent 10.0.1xx series release which is compatible with MSBuild 17.x. // - "version": "10.0.105", + "version": "10.0.106", // We cannot allow any roll forward due to the above MSBuild compatibility issues. "rollForward": "disable", From 8964a1786ce53c9e6228511bdadd8403e90aad17 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Wed, 22 Apr 2026 21:26:48 -0700 Subject: [PATCH 057/298] Update .NET 10 sdk to v10.0.107 (#4226) --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index b7b9908783..48cbbf1a80 100644 --- a/global.json +++ b/global.json @@ -7,7 +7,7 @@ // .NET 10 SDK versions in the 10.0.2xx series require MSBuild 18.x, so we specify the most // recent 10.0.1xx series release which is compatible with MSBuild 17.x. // - "version": "10.0.106", + "version": "10.0.107", // We cannot allow any roll forward due to the above MSBuild compatibility issues. "rollForward": "disable", From 876d2192000259318b054022c14e79c57f702b3b Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Thu, 23 Apr 2026 12:40:23 +0530 Subject: [PATCH 058/298] Add new conn-string synonyms for cross-driver alignment (#4192) * Add new conn-string synonyms for cross-driver alignment * Fix FailoverPartner testcases --- .../DbConnectionStringSynonyms.cs | 5 ++ .../Data/SqlClient/SqlConnectionString.cs | 33 +++++----- .../SqlClient/SqlConnectionStringBuilder.cs | 9 ++- .../SqlConnectionStringBuilderTest.cs | 63 +++++++++++++++++++ 4 files changed, 94 insertions(+), 16 deletions(-) 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 index 8d4060e1b4..15c32abe75 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringSynonyms.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringSynonyms.cs @@ -10,12 +10,15 @@ internal static class DbConnectionStringSynonyms internal const string Address = "address"; internal const string App = "app"; internal const string ApplicationIntent = "applicationintent"; + internal const string ColumnEncryption = "columnEncryption"; internal const string ConnectionLifetime = "connection lifetime"; internal const string ConnectionTimeout = "connection timeout"; internal const string ConnectRetryCount = "connectretrycount"; internal const string ConnectRetryInterval = "connectretryinterval"; + internal const string ConnectTimeout = "connecttimeout"; internal const string Database = "database"; internal const string ExtendedProperties = "extended properties"; + internal const string FailoverPartner = "failoverpartner"; internal const string FailoverPartnerSpn = "FailoverPartnerSPN"; internal const string HostNameInCertificate = "hostnameincertificate"; internal const string InitialFileName = "initial file name"; @@ -26,6 +29,7 @@ internal static class DbConnectionStringSynonyms internal const string Net = "net"; internal const string Network = "network"; internal const string NetworkAddress = "network address"; + internal const string PacketSize = "packetsize"; internal const string PersistSecurityInfo = "persistsecurityinfo"; internal const string PoolBlockingPeriod = "poolblockingperiod"; internal const string Pwd = "pwd"; @@ -38,6 +42,7 @@ internal static class DbConnectionStringSynonyms internal const string TrustServerCertificate = "trustservercertificate"; internal const string Uid = "uid"; internal const string User = "user"; + internal const string WorkstationId = "workstationid"; internal const string WsId = "wsid"; } } 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 d36863da95..ad05f67830 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -52,7 +52,7 @@ internal static class TRANSACTIONBINDING internal const string ImplicitUnbind = "Implicit Unbind"; internal const string ExplicitUnbind = "Explicit Unbind"; } - + private static readonly Dictionary s_keywordMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase); @@ -109,9 +109,9 @@ internal static class TRANSACTIONBINDING private static readonly Version s_constTypeSystemAsmVersion11 = new("11.0.0.0"); private readonly string _expandedAttachDBFilename; // expanded during construction so that CreatePermissionSet & Expand are consistent - + #region Constructors - + /// /// Static constructor to do things that we can't do in a single line initialization. /// @@ -125,13 +125,14 @@ static SqlConnectionString() AddKeywordToMap(DbConnectionStringKeywords.ApplicationIntent, DbConnectionStringSynonyms.ApplicationIntent); AddKeywordToMap(DbConnectionStringKeywords.ApplicationName, - DbConnectionStringSynonyms.App); + DbConnectionStringSynonyms.App); AddKeywordToMap(DbConnectionStringKeywords.AttachDbFilename, DbConnectionStringSynonyms.ExtendedProperties, DbConnectionStringSynonyms.InitialFileName); AddKeywordToMap(DbConnectionStringKeywords.AttestationProtocol); AddKeywordToMap(DbConnectionStringKeywords.Authentication); - AddKeywordToMap(DbConnectionStringKeywords.ColumnEncryptionSetting); + AddKeywordToMap(DbConnectionStringKeywords.ColumnEncryptionSetting, + DbConnectionStringSynonyms.ColumnEncryption); AddKeywordToMap(DbConnectionStringKeywords.CommandTimeout); AddKeywordToMap(DbConnectionStringKeywords.ConnectRetryCount, DbConnectionStringSynonyms.ConnectRetryCount); @@ -139,6 +140,7 @@ static SqlConnectionString() DbConnectionStringSynonyms.ConnectRetryInterval); AddKeywordToMap(DbConnectionStringKeywords.ConnectTimeout, DbConnectionStringSynonyms.ConnectionTimeout, + DbConnectionStringSynonyms.ConnectTimeout, DbConnectionStringSynonyms.Timeout); AddKeywordToMap(DbConnectionStringKeywords.ContextConnection); AddKeywordToMap(DbConnectionStringKeywords.CurrentLanguage, @@ -151,7 +153,8 @@ static SqlConnectionString() AddKeywordToMap(DbConnectionStringKeywords.EnclaveAttestationUrl); AddKeywordToMap(DbConnectionStringKeywords.Encrypt); AddKeywordToMap(DbConnectionStringKeywords.Enlist); - AddKeywordToMap(DbConnectionStringKeywords.FailoverPartner); + AddKeywordToMap(DbConnectionStringKeywords.FailoverPartner, + DbConnectionStringSynonyms.FailoverPartner); AddKeywordToMap(DbConnectionStringKeywords.FailoverPartnerSpn, DbConnectionStringSynonyms.FailoverPartnerSpn); AddKeywordToMap(DbConnectionStringKeywords.HostNameInCertificate, @@ -170,7 +173,8 @@ static SqlConnectionString() AddKeywordToMap(DbConnectionStringKeywords.MinPoolSize); AddKeywordToMap(DbConnectionStringKeywords.MultiSubnetFailover, DbConnectionStringSynonyms.MultiSubnetFailover); - AddKeywordToMap(DbConnectionStringKeywords.PacketSize); + AddKeywordToMap(DbConnectionStringKeywords.PacketSize, + DbConnectionStringSynonyms.PacketSize); AddKeywordToMap(DbConnectionStringKeywords.Password, DbConnectionStringSynonyms.Pwd); AddKeywordToMap(DbConnectionStringKeywords.PersistSecurityInfo, @@ -192,6 +196,7 @@ static SqlConnectionString() DbConnectionStringSynonyms.User); AddKeywordToMap(DbConnectionStringKeywords.UserInstance); AddKeywordToMap(DbConnectionStringKeywords.WorkstationId, + DbConnectionStringSynonyms.WorkstationId, DbConnectionStringSynonyms.WsId); #if NETFRAMEWORK @@ -203,7 +208,7 @@ static SqlConnectionString() DbConnectionStringSynonyms.TransparentNetworkIpResolution); #endif } - + internal SqlConnectionString(string connectionString): base(connectionString, s_keywordMap) { #if !NETFRAMEWORK @@ -568,12 +573,12 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS } #endregion - + internal bool IntegratedSecurity => _integratedSecurity; // @TODO: This is temporary until we can remove DbConnectionString (see SqlClientPermission) internal static IReadOnlyDictionary KeywordMap => s_keywordMap; - + // We always initialize in Async mode so that both synchronous and asynchronous methods // will work. In the future we can deprecate the keyword entirely. internal bool Asynchronous => true; @@ -983,21 +988,21 @@ protected internal override PermissionSet CreatePermissionSet() internal string NetworkLibrary => _networkLibrary; #endif // NETFRAMEWORK - + #region Private Methods - + private static void AddKeywordToMap(string keyword, params string[] synonyms) { // Add mapping of keyword to keyword s_keywordMap.Add(keyword, keyword); - + // Add mapping of synonyms to keyword foreach (string synonym in synonyms) { s_keywordMap.Add(synonym, keyword); } } - + #endregion } } 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 046228d0c6..33d78c57c7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -273,12 +273,17 @@ private static Dictionary CreateKeywordsDictionary() { DbConnectionStringSynonyms.Uid, Keywords.UserID }, { DbConnectionStringSynonyms.User, Keywords.UserID }, { DbConnectionStringSynonyms.WsId, Keywords.WorkstationID }, + { DbConnectionStringSynonyms.WorkstationId, Keywords.WorkstationID }, { DbConnectionStringSynonyms.ServerSpn, Keywords.ServerSPN }, { DbConnectionStringSynonyms.FailoverPartnerSpn, Keywords.FailoverPartnerSPN }, + { DbConnectionStringSynonyms.ColumnEncryption, Keywords.ColumnEncryptionSetting }, + { DbConnectionStringSynonyms.ConnectTimeout, Keywords.ConnectTimeout }, + { DbConnectionStringSynonyms.FailoverPartner, Keywords.FailoverPartner }, + { DbConnectionStringSynonyms.PacketSize, Keywords.PacketSize }, }; return pairs; } - + // @TODO These methods are completely unnecessary. private static bool ConvertToBoolean(object value) => DbConnectionStringUtilities.ConvertToBoolean(value); @@ -565,7 +570,7 @@ 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); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index 30e232e88c..a24c330181 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -93,7 +93,12 @@ public partial class SqlConnectionStringBuilderTest [InlineData("Type System Version = Latest")] [InlineData("User Instance = true")] [InlineData("Workstation ID = myworkstation")] + [InlineData("WorkstationID = myworkstation")] [InlineData("WSID = myworkstation")] + [InlineData("ConnectTimeout = 30")] + [InlineData("Initial Catalog = Northwind; FailoverPartner = randomserver.sys.local")] + [InlineData("PacketSize = 8192")] + [InlineData("columnEncryption = Enabled")] [InlineData("Host Name In Certificate = tds.test.com")] [InlineData("HostNameInCertificate = tds.test.com")] [InlineData("Server Certificate = c:\\test.cer")] @@ -937,5 +942,63 @@ internal static void CheckEncryptType(SqlConnectionStringBuilder builder, SqlCon Assert.Equal(expectedValue, builder.Encrypt); } + [Theory] + [InlineData("ConnectTimeout = 45", 45)] + [InlineData("connecttimeout = 45", 45)] + [InlineData("CONNECTTIMEOUT = 45", 45)] + [InlineData("Connect Timeout = 45", 45)] + [InlineData("Connection Timeout = 45", 45)] + [InlineData("Timeout = 45", 45)] + public void ConnectTimeoutSynonymsResolveCorrectly(string connectionString, int expected) + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); + Assert.Equal(expected, builder.ConnectTimeout); + } + + [Theory] + [InlineData("Initial Catalog = Northwind; FailoverPartner = partner.sys.local")] + [InlineData("Initial Catalog = Northwind; failoverpartner = partner.sys.local")] + [InlineData("Initial Catalog = Northwind; FAILOVERPARTNER = partner.sys.local")] + [InlineData("Initial Catalog = Northwind; Failover Partner = partner.sys.local")] + public void FailoverPartnerSynonymsResolveCorrectly(string connectionString) + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); + Assert.Equal("partner.sys.local", builder.FailoverPartner); + } + + [Theory] + [InlineData("PacketSize = 16000", 16000)] + [InlineData("packetsize = 16000", 16000)] + [InlineData("PACKETSIZE = 16000", 16000)] + [InlineData("Packet Size = 16000", 16000)] + public void PacketSizeSynonymsResolveCorrectly(string connectionString, int expected) + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); + Assert.Equal(expected, builder.PacketSize); + } + + [Theory] + [InlineData("WorkstationID = myws")] + [InlineData("workstationid = myws")] + [InlineData("WORKSTATIONID = myws")] + [InlineData("Workstation ID = myws")] + [InlineData("WSID = myws")] + public void WorkstationIdSynonymsResolveCorrectly(string connectionString) + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); + Assert.Equal("myws", builder.WorkstationID); + } + + [Theory] + [InlineData("columnEncryption = Enabled")] + [InlineData("columnencryption = Enabled")] + [InlineData("COLUMNENCRYPTION = Enabled")] + [InlineData("Column Encryption Setting = Enabled")] + public void ColumnEncryptionSynonymsResolveCorrectly(string connectionString) + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString); + Assert.Equal(SqlConnectionColumnEncryptionSetting.Enabled, builder.ColumnEncryptionSetting); + } + } } From 2a6280ad44fc7422774e0cf44f4e71e0c326453b Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:24:17 -0300 Subject: [PATCH 059/298] Fix IsDBNull + stream read bug that skips column data (#4082) --- BUILDGUIDE.md | 47 ++ .../Microsoft/Data/SqlClient/SqlDataReader.cs | 12 +- ...Microsoft.Data.SqlClient.TestCommon.csproj | 10 +- .../AlwaysEncrypted/CspProviderExt.cs | 18 +- .../DataReaderTest/DataReaderStreamsTest.cs | 584 ++++++++++++++++-- .../SQL/DataReaderTest/DataReaderTest.cs | 47 +- .../Config.cs | 22 +- 7 files changed, 659 insertions(+), 81 deletions(-) diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 8f13730f68..5da2e2ffd6 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -57,6 +57,53 @@ Manual Tests require the below setup to run: |IsManagedInstance | (Optional) When set to `true` **TVP** related tests will use non-Azure bsl files to compare test results. This is needed when testing against Azure Managed Instances; otherwise TVP Tests will fail on TestSet 3. The default value is `false`. | |PowerShellPath | The full path to PowerShell.exe. This is not required if the path is present in the PATH environment variable. | `D:\\escaped\\absolute\\path\\to\\PowerShell.exe` | +## Test Configuration Environment Variables + +Environment variables can be used to override test configuration without modifying `config.json` +files directly. This is useful when running tests via the command line, in CI/CD pipelines, +containerized environments, or when switching between configurations frequently. + +### Main Test Suite (`Microsoft.Data.SqlClient.TestUtilities`) + +These variables apply to unit, functional, and manual tests. The configuration is defined in +[`Config.cs`](src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs). + +| Variable | Description | Default | +|----------|-------------|---------| +| `TEST_MDS_CONFIG` | Override the path to the `config.json` file used by the test runner. | `config.json` (in the working directory) | +| `MDS_TCPConnectionString` | Override the TCP connection string. Takes precedence over the value in `config.json`. | Value from `config.json` | + +Example: + +```bash +export TEST_MDS_CONFIG=~/my-test-config.json +export MDS_TCPConnectionString="Server=myserver;Database=mydb;Trusted_Connection=True;" +dotnet test src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +``` + +### Performance Tests + +These variables apply to performance benchmarks. The configuration is defined in +[`Config.cs`](src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs). + +| Variable | Description | Default | +|----------|-------------|---------| +| `RUNNER_CONFIG` | Override the path to the `runnerconfig.json` file used by performance benchmarks. | `runnerconfig.json` (in the DLL output directory) | + +See [Run Performance Tests](#run-performance-tests) for more details. + +### Azure Extension Tests + +These variables apply to the Azure extension test suite. The configuration is defined in +[`Config.cs`](src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs). + +| Variable | Description | Default | +|----------|-------------|---------| +| `TEST_MDS_CONFIG` | Override the path to the `config.json` file used by Azure extension tests. | `config.json` | +| `ADO_POOL` | Set to any value to indicate tests are running in an Azure DevOps CI pool. | _(unset)_ | +| `SYSTEM_ACCESSTOKEN` | Azure Pipelines system access token, used for workload identity federation tests. | _(unset)_ | +| `TEST_DEBUG_EMIT` | Set to any value to enable debug output of configuration values during test startup. Sensitive values are Base64-encoded. | _(unset)_ | + ## MSBuild Reference ### Targets diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index d0bd8dc247..f01fe4506d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -3942,8 +3942,16 @@ internal TdsOperationStatus TryReadColumnInternal(int i, bool readHeaderOnly = f // 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)) + // The column header has been read (e.g. by IsDBNull) but data hasn't + // been consumed yet. Consume it now unless: + // - readHeaderOnly: caller only wants the header (e.g. IsDBNull itself). + // - forStreaming + SequentialAccess: a streaming type (Stream, TextReader, + // XmlReader) was requested in sequential mode. The data must stay on the + // wire so that SqlSequentialStream / SqlSequentialTextReader can read it + // incrementally. In non-sequential mode we still need to materialize the + // data into _data[i] for the MemoryStream / StringReader path. + if ((i == _sharedState._nextColumnDataToRead) && (!readHeaderOnly) && + !(forStreaming && IsCommandBehavior(CommandBehavior.SequentialAccess))) { return TryReadColumnData(); } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj index 562c60b3af..3f014f9fbe 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj +++ b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj @@ -3,6 +3,12 @@ Microsoft.Data.SqlClient.TestCommon Microsoft.Data.SqlClient.TestCommon enable + + + false @@ -26,16 +32,12 @@ - - - - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index e57a734e0e..acb2e9278c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.Data.SqlClient.Tests.Common.Fixtures; @@ -59,7 +60,7 @@ public void TestEncryptDecryptWithCsp(string connectionString, string providerNa @$"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; @@ -71,6 +72,13 @@ public static IEnumerable TestEncryptDecryptWithCsp_Data { get { + // xUnit evaluates MemberData during discovery, before [PlatformSpecific] + // can skip execution. Guard here to prevent Registry access on non-Windows. + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + yield break; + } + const string providerRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; using RegistryKey defaultProviderRegistryKey = Registry.LocalMachine.OpenSubKey(providerRegistryKeyPath); if (defaultProviderRegistryKey is null) @@ -78,7 +86,7 @@ public static IEnumerable TestEncryptDecryptWithCsp_Data // No test cases can be generated if the registry key doesn't exist. yield break; } - + foreach (string subKeyName in defaultProviderRegistryKey.GetSubKeyNames()) { // Skip inappropriate providers @@ -93,17 +101,17 @@ public static IEnumerable TestEncryptDecryptWithCsp_Data { 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) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs index e5ff3c7559..380978f049 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs @@ -434,71 +434,63 @@ public static void NullStreamProperties(CommandBehavior behavior, AccessorType a using (SqlDataReader reader = command.ExecuteReader(behavior)) { - if (reader.Read()) - { - Assert.True(reader.IsDBNull(0)); - Assert.True(reader.IsDBNull(1)); - Assert.True(reader.IsDBNull(2)); - } + Assert.True(reader.Read()); + Assert.True(reader.IsDBNull(0)); + Assert.True(reader.IsDBNull(1)); + Assert.True(reader.IsDBNull(2)); } using (SqlDataReader reader = command.ExecuteReader(behavior)) { - if (reader.Read()) - { - Assert.True(reader.IsDBNullAsync(0).GetAwaiter().GetResult()); - Assert.True(reader.IsDBNullAsync(1).GetAwaiter().GetResult()); - Assert.True(reader.IsDBNullAsync(2).GetAwaiter().GetResult()); - } + Assert.True(reader.Read()); + Assert.True(reader.IsDBNullAsync(0).GetAwaiter().GetResult()); + Assert.True(reader.IsDBNullAsync(1).GetAwaiter().GetResult()); + Assert.True(reader.IsDBNullAsync(2).GetAwaiter().GetResult()); } using (SqlDataReader reader = command.ExecuteReader(behavior)) { - if (reader.Read()) + Assert.True(reader.Read()); + using (XmlReader xmlReader = GetValue(reader, 0, accessorType)) { - using (XmlReader xmlReader = GetValue(reader, 0, accessorType)) - { - Assert.NotNull(xmlReader); - Assert.Equal(accessorType == AccessorType.GetFieldValueAsync, xmlReader.Settings.Async); - Assert.Equal(xmlReader.Value, string.Empty); - Assert.False(xmlReader.Read()); - Assert.True(xmlReader.EOF); - } + Assert.NotNull(xmlReader); + Assert.Equal(accessorType == AccessorType.GetFieldValueAsync, xmlReader.Settings.Async); + Assert.Equal(xmlReader.Value, string.Empty); + Assert.False(xmlReader.Read()); + Assert.True(xmlReader.EOF); + } - using (TextReader textReader = GetValue(reader, 1, accessorType)) - { - Assert.NotNull(textReader); - Assert.True(behavior != CommandBehavior.SequentialAccess || textReader.GetType().Name.Contains("Sequential")); - Assert.Equal(textReader.ReadToEnd(), string.Empty); - } + using (TextReader textReader = GetValue(reader, 1, accessorType)) + { + Assert.NotNull(textReader); + Assert.True(behavior != CommandBehavior.SequentialAccess || textReader.GetType().Name.Contains("Sequential")); + Assert.Equal(textReader.ReadToEnd(), string.Empty); + } - using (Stream stream = GetValue(reader, 2, accessorType)) - { - Assert.NotNull(stream); - Assert.True(behavior != CommandBehavior.SequentialAccess || stream.GetType().Name.Contains("Sequential")); - } + using (Stream stream = GetValue(reader, 2, accessorType)) + { + Assert.NotNull(stream); + Assert.True(behavior != CommandBehavior.SequentialAccess || stream.GetType().Name.Contains("Sequential")); } } using (SqlDataReader reader = command.ExecuteReader(behavior)) { - if (reader.Read()) + Assert.True(reader.Read()); + // get a clean reader over the same field and check that the value is empty + using (XmlReader xmlReader = GetValue(reader, 0, accessorType)) { - // get a clean reader over the same field and check that the value is empty - using (XmlReader xmlReader = GetValue(reader, 0, accessorType)) - { - Assert.Equal(GetXmlDocumentContents(xmlReader), string.Empty); - } + Assert.Equal(GetXmlDocumentContents(xmlReader), string.Empty); + } - using (TextReader textReader = GetValue(reader, 1, accessorType)) - { - Assert.Equal(textReader.ReadToEnd(), string.Empty); - } + using (TextReader textReader = GetValue(reader, 1, accessorType)) + { + Assert.Equal(textReader.ReadToEnd(), string.Empty); + } - using (Stream stream = GetValue(reader, 2, accessorType)) - { - Assert.Equal(GetStreamContents(stream), Array.Empty()); - } + using (Stream stream = GetValue(reader, 2, accessorType)) + { + Assert.Equal(GetStreamContents(stream), Array.Empty()); } } } @@ -567,6 +559,506 @@ public static async Task ReadAsyncContentsCompletes() } #endif +#nullable enable + // Test that calling IsDBNullAsync() doesn't prevent + // GetFieldValueAsync() from successfully reading the column value. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("FOO", false, false)] + [InlineData("FOO", false, true)] + [InlineData(null, true, false)] + [InlineData(null, true, true)] + public static async Task IsDBNullAsync_GetFieldValueAsync_Compatibility( + string? value, + bool expectNull, + bool checkNull) + { + using SqlConnection connection = new (DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using SqlCommand command = new( + "select 'foo', 7, convert(varbinary, " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + + while (await reader.ReadAsync()) + { + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + // Check if the 3rd column (index 2) is null, if desired. + if (checkNull) + { + Assert.Equal(expectNull, await reader.IsDBNullAsync(2)); + } + + // Read the value of the 3rd column (index 2). + using MemoryStream buffer = new(); + using (Stream stream = await reader.GetFieldValueAsync(2)) + { + await stream.CopyToAsync(buffer); + Assert.Equal( + // Trying to read a null column via Stream yields an empty stream, thus an + // empty string. + expectNull ? "" : value, + Encoding.UTF8.GetString(buffer.ToArray())); + } + } + } + + // Test that calling IsDBNullAsync() doesn't prevent + // GetFieldValueAsync() from successfully reading the column value. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("FOO", false, false)] + [InlineData("FOO", false, true)] + [InlineData(null, true, false)] + [InlineData(null, true, true)] + public static async Task IsDBNullAsync_GetFieldValueAsyncTextReader_Compatibility( + string? value, + bool expectNull, + bool checkNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using SqlCommand command = new( + "select 'foo', 7, convert(nvarchar(100), " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + + while (await reader.ReadAsync()) + { + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + if (checkNull) + { + Assert.Equal(expectNull, await reader.IsDBNullAsync(2)); + } + + using TextReader textReader = await reader.GetFieldValueAsync(2); + string result = await textReader.ReadToEndAsync(); + Assert.Equal(expectNull ? "" : value, result); + } + } + + // Test that calling IsDBNullAsync() doesn't prevent + // GetFieldValueAsync() from successfully reading the column value. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("", false, false)] + [InlineData("", false, true)] + [InlineData(null, true, false)] + [InlineData(null, true, true)] + public static async Task IsDBNullAsync_GetFieldValueAsyncXmlReader_Compatibility( + string? value, + bool expectNull, + bool checkNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using SqlCommand command = new( + "select 'foo', 7, convert(xml, " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + + while (await reader.ReadAsync()) + { + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + if (checkNull) + { + Assert.Equal(expectNull, await reader.IsDBNullAsync(2)); + } + + if (expectNull) + { + using XmlReader xmlReader = await reader.GetFieldValueAsync(2); + Assert.False(xmlReader.Read()); + } + else + { + using XmlReader xmlReader = await reader.GetFieldValueAsync(2); + xmlReader.Read(); + string result = xmlReader.ReadOuterXml(); + Assert.Equal(value, result); + } + } + } + + // Test that calling IsDBNull (sync) doesn't prevent + // GetFieldValue (sync) from successfully reading the column value. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("FOO", false, false)] + [InlineData("FOO", false, true)] + [InlineData(null, true, false)] + [InlineData(null, true, true)] + public static async Task IsDBNull_GetFieldValueStream_Compatibility( + string? value, + bool expectNull, + bool checkNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using SqlCommand command = new( + "select 'foo', 7, convert(varbinary, " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + command.ExecuteReader(CommandBehavior.SequentialAccess); + + while (reader.Read()) + { + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + if (checkNull) + { + Assert.Equal(expectNull, reader.IsDBNull(2)); + } + + using MemoryStream buffer = new(); + using (Stream stream = reader.GetFieldValue(2)) + { + stream.CopyTo(buffer); + Assert.Equal( + expectNull ? "" : value, + Encoding.UTF8.GetString(buffer.ToArray())); + } + } + } + + // Test that calling IsDBNull (sync) doesn't prevent + // GetFieldValue (sync) from successfully reading the column value. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("FOO", false, false)] + [InlineData("FOO", false, true)] + [InlineData(null, true, false)] + [InlineData(null, true, true)] + public static async Task IsDBNull_GetFieldValueTextReader_Compatibility( + string? value, + bool expectNull, + bool checkNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using SqlCommand command = new( + "select 'foo', 7, convert(nvarchar(100), " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + command.ExecuteReader(CommandBehavior.SequentialAccess); + + while (reader.Read()) + { + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + if (checkNull) + { + Assert.Equal(expectNull, reader.IsDBNull(2)); + } + + using TextReader textReader = reader.GetFieldValue(2); + string result = textReader.ReadToEnd(); + Assert.Equal(expectNull ? "" : value, result); + } + } + + // Test that calling IsDBNull (sync) doesn't prevent + // GetFieldValue (sync) from successfully reading the column value. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("", false, false)] + [InlineData("", false, true)] + [InlineData(null, true, false)] + [InlineData(null, true, true)] + public static async Task IsDBNull_GetFieldValueXmlReader_Compatibility( + string? value, + bool expectNull, + bool checkNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using SqlCommand command = new( + "select 'foo', 7, convert(xml, " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + command.ExecuteReader(CommandBehavior.SequentialAccess); + + while (reader.Read()) + { + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + if (checkNull) + { + Assert.Equal(expectNull, reader.IsDBNull(2)); + } + + if (expectNull) + { + using XmlReader xmlReader = reader.GetFieldValue(2); + Assert.False(xmlReader.Read()); + } + else + { + using XmlReader xmlReader = reader.GetFieldValue(2); + xmlReader.Read(); + string result = xmlReader.ReadOuterXml(); + Assert.Equal(value, result); + } + } + } + + // Test that calling IsDBNull (sync) doesn't prevent + // GetFieldValueAsync (async) from successfully reading the column value. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("FOO", false, false)] + [InlineData("FOO", false, true)] + [InlineData(null, true, false)] + [InlineData(null, true, true)] + public static async Task IsDBNull_GetFieldValueAsyncStream_Compatibility( + string? value, + bool expectNull, + bool checkNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using SqlCommand command = new( + "select 'foo', 7, convert(varbinary, " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + command.ExecuteReader(CommandBehavior.SequentialAccess); + + while (reader.Read()) + { + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + if (checkNull) + { + Assert.Equal(expectNull, reader.IsDBNull(2)); + } + + using MemoryStream buffer = new(); + using (Stream stream = await reader.GetFieldValueAsync(2)) + { + await stream.CopyToAsync(buffer); + Assert.Equal( + expectNull ? "" : value, + Encoding.UTF8.GetString(buffer.ToArray())); + } + } + } + + // Test that calling IsDBNullAsync doesn't prevent + // GetFieldValue (sync) from successfully reading the column value. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("FOO", false, false)] + [InlineData("FOO", false, true)] + [InlineData(null, true, false)] + [InlineData(null, true, true)] + public static async Task IsDBNullAsync_GetFieldValueStream_Compatibility( + string? value, + bool expectNull, + bool checkNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + await connection.OpenAsync(); + + using SqlCommand command = new( + "select 'foo', 7, convert(varbinary, " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + + while (await reader.ReadAsync()) + { + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + if (checkNull) + { + Assert.Equal(expectNull, await reader.IsDBNullAsync(2)); + } + + using MemoryStream buffer = new(); + using (Stream stream = reader.GetFieldValue(2)) + { + stream.CopyTo(buffer); + Assert.Equal( + expectNull ? "" : value, + Encoding.UTF8.GetString(buffer.ToArray())); + } + } + } + + // Test that calling IsDBNull doesn't prevent + // GetFieldValue from reading the column in non-sequential mode. + // Regression test for Copilot review comment on PR #4082: the forStreaming + // guard must not suppress TryReadColumnData in non-sequential access. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("FOO", false)] + [InlineData(null, true)] + public static void IsDBNull_GetFieldValueStream_NonSequential( + string? value, + bool expectNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); + + using SqlCommand command = new( + "select 'foo', 7, convert(varbinary, " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + command.ExecuteReader(CommandBehavior.Default); + + Assert.True(reader.Read()); + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + // Pre-read the column header via IsDBNull + Assert.Equal(expectNull, reader.IsDBNull(2)); + + // Now read via streaming type — should still work + using MemoryStream buffer = new(); + using (Stream stream = reader.GetFieldValue(2)) + { + stream.CopyTo(buffer); + Assert.Equal( + expectNull ? "" : value, + Encoding.UTF8.GetString(buffer.ToArray())); + } + } + + // Test that calling IsDBNull doesn't prevent + // GetFieldValue from reading the column in non-sequential mode. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("FOO", false)] + [InlineData(null, true)] + public static void IsDBNull_GetFieldValueTextReader_NonSequential( + string? value, + bool expectNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); + + using SqlCommand command = new( + "select 'foo', 7, convert(nvarchar(100), " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + command.ExecuteReader(CommandBehavior.Default); + + Assert.True(reader.Read()); + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + Assert.Equal(expectNull, reader.IsDBNull(2)); + + using TextReader textReader = reader.GetFieldValue(2); + string result = textReader.ReadToEnd(); + Assert.Equal(expectNull ? "" : value, result); + } + + // Test that calling IsDBNull doesn't prevent + // GetFieldValue from reading the column in non-sequential mode. + [ConditionalTheory( + typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData("", false)] + [InlineData(null, true)] + public static void IsDBNull_GetFieldValueXmlReader_NonSequential( + string? value, + bool expectNull) + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); + + using SqlCommand command = new( + "select 'foo', 7, convert(xml, " + + (value is null ? "null" : $"'{value}'") + ")", + connection); + + using SqlDataReader reader = + command.ExecuteReader(CommandBehavior.Default); + + Assert.True(reader.Read()); + Assert.Equal("foo", reader.GetString(0)); + Assert.Equal(7, reader.GetInt32(1)); + + Assert.Equal(expectNull, reader.IsDBNull(2)); + + if (expectNull) + { + using XmlReader xmlReader = reader.GetFieldValue(2); + Assert.False(xmlReader.Read()); + } + else + { + using XmlReader xmlReader = reader.GetFieldValue(2); + xmlReader.Read(); + string result = xmlReader.ReadOuterXml(); + Assert.Equal(value, result); + } + } +#nullable disable + private static async Task ExecuteReader(SqlCommand command, CommandBehavior behavior, bool isExecuteAsync) => isExecuteAsync ? await command.ExecuteReaderAsync(behavior) : command.ExecuteReader(behavior); 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 2fbf2714f9..aad117b336 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -440,7 +440,7 @@ public static async Task CanReadXmlData() Computer 44.95 2000-10-01 - An in-depth look at creating applications + An in-depth look at creating applications with XML. @@ -449,8 +449,8 @@ with XML. Fantasy 5.95 2000-12-16 - A former architect battles corporate zombies, - an evil sorceress, and her own childhood to become queen + A former architect battles corporate zombies, + an evil sorceress, and her own childhood to become queen of the world. @@ -459,8 +459,8 @@ of the world. Fantasy 5.95 2000-11-17 - After the collapse of a nanotechnology - society in England, the young survivors lay the + After the collapse of a nanotechnology + society in England, the young survivors lay the foundation for a new society. @@ -469,9 +469,9 @@ of the world. 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 + 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. @@ -480,8 +480,8 @@ of the world. Fantasy 5.95 2001-09-10 - The two daughters of Maeve, half-sisters, - battle one another for control of England. Sequel to + The two daughters of Maeve, half-sisters, + battle one another for control of England. Sequel to Oberon's Legacy. @@ -490,7 +490,7 @@ of the world. Romance 4.95 2000-09-02 - When Carla meets Paul at an ornithology + When Carla meets Paul at an ornithology conference, tempers fly as feathers get ruffled. @@ -499,7 +499,7 @@ of the world. Romance 4.95 2000-11-02 - A deep sea diver finds true love twenty + A deep sea diver finds true love twenty thousand leagues beneath the sea. @@ -518,7 +518,7 @@ thousand leagues beneath the sea. 6.95 2000-11-02 After an inadvertant trip through a Heisenberg - Uncertainty Device, James Salway discovers the problems + Uncertainty Device, James Salway discovers the problems of being quantum. @@ -527,7 +527,7 @@ of being quantum. Computer 36.95 2000-12-09 - Microsoft's .NET initiative is explored in + Microsoft's .NET initiative is explored in detail in this deep programmer's reference. @@ -536,8 +536,8 @@ detail in this deep programmer's reference. Computer 36.95 2000-12-01 - The Microsoft MSXML3 parser is covered in - detail, with attention to XML DOM interfaces, XSLT processing, + The Microsoft MSXML3 parser is covered in + detail, with attention to XML DOM interfaces, XSLT processing, SAX and more. @@ -547,8 +547,8 @@ SAX and more. 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 + looking at how Visual Basic, Visual C++, C#, and ASP+ are + integrated into a comprehensive development environment. "; @@ -886,13 +886,13 @@ StringName varchar(255) not null, cmd.CommandText = $"""" SELECT [gs].[List], - [gs].[UseProtoSerializer] + [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(); @@ -960,6 +960,11 @@ public _Row(int id, Guid documentIdentificationId, string name, string value) } } + // NOTE: This test is very slow (can exceed 10 minutes on macOS). It runs 512 iterations + // (packet sizes 512..2045 stepping by 3), each opening a new non-pooled TCP connection and + // reading 21 rows that include several nvarchar(max) values tens-of-thousands of characters + // long. With small packet sizes the large strings require many network round-trips, and on + // non-Windows platforms the managed SNI layer is used which adds further overhead. [Trait("Category", "flaky")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static async Task CanReadAwkwardDataLengths() @@ -1094,7 +1099,7 @@ [Value] [nvarchar](max) NULL SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); builder.PersistSecurityInfo = true; builder.Pooling = false; - + for (int packetSize = 512; packetSize<2048; packetSize+=3) { builder.PacketSize = packetSize; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs index 72ed2e63a0..6e82dc5076 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs @@ -42,23 +42,39 @@ public class Config public string KerberosDomainUser = null; public bool IsManagedInstance = false; public string AliasName = null; - + public static Config Load(string configPath = @"config.json") { // Allow an override of the config path via an environment variable. - configPath = Environment.GetEnvironmentVariable("MDS_TEST_CONFIG") ?? configPath; + configPath = Environment.GetEnvironmentVariable("TEST_MDS_CONFIG") ?? configPath; + Config config; try { using (StreamReader r = new StreamReader(configPath)) { - return JsonConvert.DeserializeObject(r.ReadToEnd()); + config = JsonConvert.DeserializeObject(r.ReadToEnd()) + ?? throw new InvalidOperationException($"Failed to deserialize config from '{configPath}'."); } } catch { throw; } + + static void SetFromEnv(string envVar, ref string configValue) + { + string envValue = Environment.GetEnvironmentVariable(envVar); + if (!string.IsNullOrEmpty(envValue)) + { + configValue = envValue; + } + } + + // Allow environment variables to override individual config values. + SetFromEnv("MDS_TCPConnectionString", ref config.TCPConnectionString); + + return config; } public static void UpdateConfig(Config updatedConfig, string configPath = @"config.json") From 74a8b0fd8dc22b7729e532bc67ad0d9752cca9dc Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 23 Apr 2026 13:49:23 -0700 Subject: [PATCH 060/298] Implement ChannelDbConnectionPool.Clear (#4194) * Implement clear * Hook up clearallpools testing * Rename generation property. * Expose IdleCount. Wrap idle channel in helper class to safely control IdleCount calculation. * Make basic lifecycle methods no-ops to enable manual tests. * Address copilot comments. * Review changes * Review changes 1 Co-authored-by: Copilot * Rename test class. Add concurrency test. Co-authored-by: Copilot * Fix using statement. --------- Co-authored-by: Copilot --- .../SqlConnection.xml | 20 +- specs/001-pool-clear/spec.md | 100 +++++++ .../Data/ProviderBase/DbConnectionInternal.cs | 11 + .../ConnectionPool/ChannelDbConnectionPool.cs | 112 ++++++-- .../ConnectionPool/DbConnectionPoolGroup.cs | 2 +- .../ConnectionPool/IDbConnectionPool.cs | 12 + .../ConnectionPool/IdleConnectionChannel.cs | 92 +++++++ .../WaitHandleDbConnectionPool.cs | 8 +- .../Data/SqlClient/SqlConnectionFactory.cs | 6 +- .../ConnectionPoolHelper.cs | 22 +- .../ConnectionPoolTest/ConnectionPoolTest.cs | 39 ++- .../ChannelDbConnectionPoolTest.cs | 233 ++++++++++++++++- .../IdleConnectionChannelTest.cs | 244 ++++++++++++++++++ .../TransactedConnectionPoolTest.cs | 1 + 14 files changed, 841 insertions(+), 61 deletions(-) create mode 100644 specs/001-pool-clear/spec.md create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index f0eddd56fa..f2e03ebf26 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -827,10 +827,17 @@ The following example creates a an - Empties the connection pool. + Empties all connection pools. - resets (or empties) the connection pool. If there are connections in use at the time of the call, they are marked appropriately and will be discarded (instead of being returned to the pool) when is called on them. + resets (or empties) all connection pools. If there are connections in use at the time of the call, they are marked appropriately and will be discarded (instead of being returned to a pool) when is called on them. + +> [!CAUTION] +> Clearing the pool is an expensive operation and should only be used if required. This operation may negatively interfere with pool warmup and generate high connection churn as the warmup operation continually opens new connections to attempt to reach min pool size. This situation is especially likely if clear is called in a tight loop. + +]]> @@ -841,7 +848,14 @@ The following example creates a an 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 `connection`. If additional connections associated with `connection` 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. + +> [!CAUTION] +> Clearing the pool is an expensive operation and should only be used if required. This operation may negatively interfere with pool warmup and generate high connection churn as the warmup operation continually opens new connections to attempt to reach min pool size. This situation is especially likely if clear is called in a tight loop. + +]]> diff --git a/specs/001-pool-clear/spec.md b/specs/001-pool-clear/spec.md new file mode 100644 index 0000000000..3ff9488410 --- /dev/null +++ b/specs/001-pool-clear/spec.md @@ -0,0 +1,100 @@ +# Feature Specification: Pool Clear + +**Feature Branch**: `dev/mdaigle/connection-pool-designs` +**Created**: 2026-04-10 +**Status**: Draft +**Input**: User description: "Implement Clear() in ChannelDbConnectionPool so that SqlConnection.ClearPool() and SqlConnection.ClearAllPools() work with pool v2, using a generation counter approach to lazily invalidate connections." + +## User Scenarios & Testing + +### User Story 1 - Clear a Specific Connection Pool + +As an application developer, I want to call `SqlConnection.ClearPool(connection)` to invalidate all connections in a specific pool, so that the next connection request creates a fresh physical connection (e.g., after a failover, credential rotation, or configuration change on the server). + +**Independent Test**: Can be tested by opening connections, calling `ClearPool`, then verifying that subsequent connection requests do not reuse pre-clear connections. + +**Acceptance Scenarios**: + +1. **Given** a pool with 10 idle connections, **When** `ClearPool` is called, **Then** all idle connections are closed and new connection requests create fresh physical connections. +2. **Given** a pool with 5 busy (in-use) connections and 5 idle connections, **When** `ClearPool` is called, **Then** idle connections are closed immediately. + +--- + +### User Story 2 - Clear All Connection Pools + +As an application developer, I want to call `SqlConnection.ClearAllPools()` to invalidate connections across all pools in the application, so that I can recover from broad infrastructure changes (e.g., DNS migration, certificate rollover). + +**Independent Test**: Can be tested by creating connections to multiple connection strings, calling `ClearAllPools`, and verifying all pools produce fresh connections. + +**Acceptance Scenarios**: + +1. **Given** multiple pools each with idle connections, **When** `ClearAllPools` is called, **Then** all idle connections across all pools are closed. + +--- + +### User Story 3 - Lazy Invalidation of Busy Connections + +As the pool infrastructure, I need busy connections that were opened before a clear to be destroyed when they are returned to the pool, so that clearing does not interrupt active operations but still ensures all pre-clear connections are eventually removed. + +**Independent Test**: Can be tested by opening a connection, calling `ClearPool`, executing a query on the open connection (should succeed), closing the connection, then verifying the pool destroyed it rather than reusing it. + +**Acceptance Scenarios**: + +1. **Given** a connection opened and in use before a clear, **When** `ClearPool` is called, **Then** the connection continues to work normally. +2. **Given** a connection opened and in use before a pool clear, **When** the connection is returned to the pool, **Then** the pool detects that it predates the clear and destroys it. +3. **Given** a connection opened after a pool clear, **When** the connection is returned to the pool, **Then** it is returned to the idle channel normally. + +--- + +### User Story 4 - Multiple Consecutive Clears + +As an application developer, I want multiple/concurrent calls to `ClearPool` to behave correctly, so that pool state is not corrupted. + +**Independent Test**: Can be tested by calling `ClearPool` multiple times in rapid succession and verifying no exceptions, no connection leaks, and correct pool behavior afterward. + +**Acceptance Scenarios**: + +1. **Given** a pool with connections, **When** `ClearPool` is called twice rapidly, **Then** both calls complete without error, and connections predating either clear are invalidated. + +--- + +### Edge Cases + +- What happens if `ClearPool` is called on an empty pool? The generation counter increments but no connections are closed. Subsequent connections are fresh. +- What happens if `ClearPool` is called during pool shutdown? The clear should be a no-op or complete harmlessly — shutdown already destroys all connections. +- What happens with many clears causing generation counter overflow? With `int` counter, overflow at 2^31. Even at 1 clear/second, this is 68 years. Overflow is not a practical concern. If it wraps, the worst case is one stale connection survives a single retrieval cycle. +- What happens if `ClearPool` is called during pool startup? `ClearPool` can be called at any time after the pool is instantiated. If connections are being added to the pool while clearing, they are closed subject to the conditions of the clear operation. +- What happens if ClearPool is called while a SqlConnection is waiting to receive a connection from the pool? If a SqlConnection is waiting for a connection, then there are no idle connections in the pool, so `ClearPool` will not have any effect. + +## Requirements + +### Functional Requirements + +- **FR-002**: System MUST stamp each new connection with the current pool generation at creation time. +- **FR-003**: System MUST reject connections whose generation does not match the current pool generation when they are retrieved from the idle channel or returned to the pool. +- **FR-004**: System MUST drain all idle connections from the channel on `Clear()`, closing each one. +- **FR-005**: System MUST NOT interrupt busy (in-use) connections during a clear — busy connections are destroyed lazily when returned. +- **FR-006**: System MUST allow connections opened after a clear to be pooled normally. +- **FR-007**: System MUST support concurrent calls to `Clear()` without corrupting pool state. +- **FR-008**: System MUST integrate with the existing `SqlConnection.ClearPool()` and `SqlConnection.ClearAllPools()` call paths. + +### Key Entities + +- **Pool Generation Counter (`_clearGeneration`)**: A pool-level `volatile int` incremented atomically via `Interlocked.Increment` on each `Clear()` call. Represents the current "epoch" of the pool. +- **Connection Generation (`ClearGeneration`)**: A property on `DbConnectionInternal` stamped when the connection is created or added to the pool. Used to compare against the pool's current generation. +- **Stale Connection**: A connection whose `ClearGeneration` does not match the pool's `_clearGeneration`. Stale connections are destroyed rather than returned to the idle channel. + +## Success Criteria + +### Measurable Outcomes + +- **SC-001**: After `ClearPool` is called, 100% of subsequent connection acquisitions produce fresh physical connections (no pre-clear connections reused). +- **SC-002**: Busy connections continue to operate normally during and after a pool clear — no active queries are interrupted. +- **SC-003**: `ClearPool` and `ClearAllPools` work identically whether using pool v1 (WaitHandle) or pool v2 (Channel), from the caller's perspective. + +## Assumptions + +- The generation counter approach is preferred over the WaitHandle pool's `DoNotPoolThisConnection()` mark-all pattern because `ConnectionPoolSlots` is not iterable by design (CAS-based slot array). +- `DbConnectionInternal` can accommodate a new `ClearGeneration` property without breaking existing functionality or requiring changes to the WaitHandle pool. +- The existing `SqlConnection.ClearPool()` → `SqlConnectionFactory.ClearPool()` → `IDbConnectionPool.Clear()` call chain is already wired up and only requires the `ChannelDbConnectionPool.Clear()` implementation. +- Transacted connections with stale generations are handled separately as part of the transactions feature and are out of scope for Phase 1 of pool clear. 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 b4426bc4d6..5d3143783f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -92,6 +92,17 @@ internal DbConnectionInternal(ConnectionState state, bool hidePassword, bool all /// internal DateTime CreateTime { get; } + /// + /// The pool generation at the time this connection was created or added to the pool. + /// Used by to detect stale connections after a pool clear. + /// + /// + /// Not safe, should only be set by the connection pool. + /// + // TODO: Ideally this would be readonly and set in the constructor. Piping the value all the way through the connection factory is too complicated to be worth it. + // If we can expose the constructor to the connection pool in the future, it can be set in the constructor. + internal int ClearGeneration { get; set; } + internal bool AllowSetConnectionString { get; } internal bool CanBePooled => !IsConnectionDoomed && !_cannotBePooled && !_owningObject.TryGetTarget(out _); 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 index 553e6cd395..d6db51e5bb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -73,11 +73,26 @@ internal sealed class ChannelDbConnectionPool : IDbConnectionPool private readonly ConnectionPoolSlots _connectionSlots; /// - /// Reader side for the idle connection channel. Contains nulls in order to release waiting attempts after - /// a connection has been physically closed/broken. + /// The idle connection channel. Contains nulls in order to release waiting attempts after + /// a connection has been physically closed/broken. Also tracks the count of non-null idle connections. /// - private readonly ChannelReader _idleConnectionReader; - private readonly ChannelWriter _idleConnectionWriter; + private readonly IdleConnectionChannel _idleChannel; + + /// + /// The current generation of the pool. Incremented atomically on each call. + /// Connections stamped with a generation that does not match are considered stale and are destroyed + /// rather than returned to the idle channel. + /// Must be updated using operations to ensure thread safety. + /// + private volatile int _clearGeneration; + + /// + /// Guard to prevent concurrent operations from draining the idle channel + /// simultaneously. The generation counter is still incremented by every caller so stale connections + /// are always caught lazily, but only one thread performs the actual drain. + /// Must be updated using operations to ensure thread safety. + /// + private volatile int _isClearing; #endregion /// @@ -99,13 +114,7 @@ internal ChannelDbConnectionPool( TransactedConnectionPool = new(this); _connectionSlots = new(MaxPoolSize); - - // We enforce Max Pool Size, so no need to create a bounded channel (which is less efficient) - // On the consuming side, we have the multiplexing write loop but also non-multiplexing Rents - // On the producing side, we have connections being released back into the pool (both multiplexing and not) - var idleChannel = Channel.CreateUnbounded(); - _idleConnectionReader = idleChannel.Reader; - _idleConnectionWriter = idleChannel.Writer; + _idleChannel = new(); State = Running; } @@ -123,7 +132,11 @@ public ConcurrentDictionary< public int Count => _connectionSlots.ReservationCount; /// - public bool ErrorOccurred => throw new NotImplementedException(); + public int IdleCount => _idleChannel.Count; + + /// + /// This will be implemented later when we add support for the pool blocking period after errors. For now, it always returns false. + public bool ErrorOccurred => false; /// public int Id => _instanceId; @@ -162,7 +175,45 @@ public ConcurrentDictionary< /// public void Clear() { - throw new NotImplementedException(); + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Clearing.", Id); + + Interlocked.Increment(ref _clearGeneration); + + // If another thread is already draining, skip the drain. The generation counter has + // already been incremented, so stale connections will still be caught lazily by + // IsLiveConnection on their next retrieval or return. + if (Interlocked.CompareExchange(ref _isClearing, 1, 0) == 1) + { + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Skip drain, already clearing.", Id); + return; + } + + try + { + // Drain idle connections from the channel and destroy them. Limit iterations to + // the current idle count to prevent an unbounded loop if connections are + // concurrently returned to the channel during the drain. + // Any connections from a previous generation that are returned to the pool + // after we start draining will fail the _clearCounter comparison and will be closed. + int numToDrain = IdleCount; + while (numToDrain > 0 && _idleChannel.TryRead(out DbConnectionInternal? connection)) + { + if (connection is not null) + { + RemoveConnection(connection); + numToDrain--; + } + } + } + finally + { + Interlocked.Exchange(ref _isClearing, 0); + } + + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Cleared.", Id); } /// @@ -205,7 +256,7 @@ public void ReturnInternalConnection(DbConnectionInternal connection, DbConnecti } else { - var written = _idleConnectionWriter.TryWrite(connection); + var written = _idleChannel.TryWrite(connection); Debug.Assert(written, "Failed to write returning connection to the idle channel."); } } @@ -213,13 +264,13 @@ public void ReturnInternalConnection(DbConnectionInternal connection, DbConnecti /// public void Shutdown() { - throw new NotImplementedException(); + // No-op for now, warmup will be implemented later. } /// public void Startup() { - throw new NotImplementedException(); + // No-op for now, warmup will be implemented later. } /// @@ -353,18 +404,23 @@ public bool TryGetConnection( // DbConnectionInternal doesn't support an async open. It's better to block this thread and keep // throughput high than to queue all of our opens onto a single worker thread. Add an async path // when this support is added to DbConnectionInternal. - return ConnectionFactory.CreatePooledConnection( + var connection = ConnectionFactory.CreatePooledConnection( owningConnection, this, - PoolGroup.PoolKey, - PoolGroup.ConnectionOptions, userOptions); + + if (connection is not null) + { + connection.ClearGeneration = _clearGeneration; + } + + return connection; }, cleanupCallback: (newConnection) => { // If we fail to open a connection, we need to write a null to the idle channel to // wake up any waiters - _idleConnectionWriter?.TryWrite(null); + _idleChannel?.TryWrite(null); newConnection?.Dispose(); }); } @@ -376,16 +432,24 @@ public bool TryGetConnection( /// Returns true if the connection is live and unexpired, otherwise returns false. private bool IsLiveConnection(DbConnectionInternal connection) { + // Broken physical connection if (!connection.IsConnectionAlive()) { return false; } + // Connection has been alive longer than the load balance timeout if (LoadBalanceTimeout != TimeSpan.Zero && DateTime.UtcNow > connection.CreateTime + LoadBalanceTimeout) { return false; } + // Connection was created before the last Clear, so it's stale. + if (connection.ClearGeneration != _clearGeneration) + { + return false; + } + return true; } @@ -400,7 +464,7 @@ private void RemoveConnection(DbConnectionInternal connection) // Removing a connection from the pool opens a free slot. // Write a null to the idle connection channel to wake up a waiter, who can now open a new // connection. Statement order is important since we have synchronous completions on the channel. - _idleConnectionWriter.TryWrite(null); + _idleChannel.TryWrite(null); connection.Dispose(); } @@ -412,7 +476,7 @@ private void RemoveConnection(DbConnectionInternal connection) private DbConnectionInternal? GetIdleConnection() { // The channel may contain nulls. Read until we find a non-null connection or exhaust the channel. - while (_idleConnectionReader.TryRead(out DbConnectionInternal? connection)) + while (_idleChannel.TryRead(out DbConnectionInternal? connection)) { if (connection is null) { @@ -478,7 +542,7 @@ private async Task GetInternalConnection( // (first-come, first-served), which is crucial to us. if (async) { - connection ??= await _idleConnectionReader.ReadAsync(cancellationToken).ConfigureAwait(false); + connection ??= await _idleChannel.ReadAsync(cancellationToken).ConfigureAwait(false); } else { @@ -525,7 +589,7 @@ private async Task GetInternalConnection( try { ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter = - _idleConnectionReader.ReadAsync(cancellationToken).ConfigureAwait(false).GetAwaiter(); + _idleChannel.ReadAsync(cancellationToken).ConfigureAwait(false).GetAwaiter(); using ManualResetEventSlim mres = new ManualResetEventSlim(false, 0); // Cancellation happens through the ReadAsync call, which will complete the task. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 57597ae761..fc14e62d58 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -172,7 +172,7 @@ internal IDbConnectionPool GetConnectionPool(SqlConnectionFactory connectionFact IDbConnectionPool newPool; if (LocalAppContextSwitches.UseConnectionPoolV2) { - throw new NotImplementedException(); + newPool = new ChannelDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); } else { 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 index bfc5789d3f..8a6ae397ed 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -38,6 +38,11 @@ internal interface IDbConnectionPool /// int Count { get; } + /// + /// The number of connections currently sitting idle in the pool. + /// + int IdleCount { get; } + /// /// Indicates whether an error has occurred in the pool. /// Primarily used to support the pool blocking period feature. @@ -100,6 +105,13 @@ internal interface IDbConnectionPool /// /// Clears the connection pool, releasing all connections and resetting the state. /// + /// + /// Clearing the pool is an expensive operation and should only be used if required. + /// This operation may negatively interfere with pool warmup and generate high connection + /// churn as the warmup operation continually opens new connections to attempt + /// to reach min pool size. This situation is especially likely if clear is called in a + /// tight loop. + /// void Clear(); /// diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs new file mode 100644 index 0000000000..00748ab184 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Channels; +using Microsoft.Data.ProviderBase; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + /// + /// Wraps an unbounded of idle connections and tracks the number of + /// non-null connections it contains. Unbounded channels do not support + /// , so this class maintains the count via + /// operations on every read and write of a non-null value. + /// + internal sealed class IdleConnectionChannel + { + private readonly ChannelReader _reader; + private readonly ChannelWriter _writer; + private volatile int _count; + + internal IdleConnectionChannel() + { + var channel = Channel.CreateUnbounded(); + _reader = channel.Reader; + //TODO: the channel should be completed on pool shutdown + _writer = channel.Writer; + } + + /// + /// The number of non-null connections currently in the channel. + /// + internal int Count => _count; + + /// + /// Writes a connection (or null wake-up signal) to the channel. + /// Increments the idle count when is not null. + /// + /// if the value was written; otherwise . + internal bool TryWrite(DbConnectionInternal? connection) + { + if (_writer.TryWrite(connection)) + { + if (connection is not null) + { + Interlocked.Increment(ref _count); + } + return true; + } + + return false; + } + + /// + /// Tries to read a value from the channel without blocking. + /// Decrements the idle count when a non-null connection is read. + /// + internal bool TryRead(out DbConnectionInternal? connection) + { + if (_reader.TryRead(out connection)) + { + if (connection is not null) + { + Interlocked.Decrement(ref _count); + } + + return true; + } + + return false; + } + + /// + /// Asynchronously reads a value from the channel. + /// Decrements the idle count when a non-null connection is read. + /// + internal async ValueTask ReadAsync(CancellationToken cancellationToken) + { + var connection = await _reader.ReadAsync(cancellationToken).ConfigureAwait(false); + + if (connection is not null) + { + Interlocked.Decrement(ref _count); + } + + return connection; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 8d2311b5cd..b7fa972e85 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -259,8 +259,12 @@ private int CreationTimeout get { return PoolGroupOptions.CreationTimeout; } } + /// public int Count => _totalObjects; + /// + public int IdleCount => _stackNew.Count + _stackOld.Count; + public SqlConnectionFactory ConnectionFactory => _connectionFactory; public bool ErrorOccurred => _errorOccurred; @@ -290,7 +294,7 @@ private bool NeedToReplenish return true; } - int freeObjects = _stackNew.Count + _stackOld.Count; + int freeObjects = IdleCount; int waitingRequests = _waitCount; bool needToReplenish = (freeObjects < waitingRequests) || ((freeObjects == waitingRequests) && (totalObjects > 1)); @@ -528,8 +532,6 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio newObj = _connectionFactory.CreatePooledConnection( owningObject, this, - _connectionPoolGroup.PoolKey, - _connectionPoolGroup.ConnectionOptions, userOptions); lock (_objectList) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 89be9767d8..8d49f4dc30 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -141,15 +141,13 @@ internal DbConnectionInternal CreateNonPooledConnection( internal DbConnectionInternal CreatePooledConnection( DbConnection owningConnection, IDbConnectionPool pool, - DbConnectionPoolKey poolKey, - DbConnectionOptions options, DbConnectionOptions userOptions) { Debug.Assert(pool != null, "null pool?"); DbConnectionInternal newConnection = CreateConnection( - options, - poolKey, // @TODO: is pool.PoolGroup.Key the same thing? + pool.PoolGroup.ConnectionOptions, + pool.PoolGroup.PoolKey, pool.PoolGroup.ProviderInfo, pool, owningConnection, 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 d447720a1a..acaf348a52 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 @@ -16,31 +16,28 @@ 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.SqlClient.ConnectionPool.IDbConnectionPool"); private static Type s_waitHandleDbConnectionPool = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool"); + private static Type s_channelDbConnectionPool = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.ChannelDbConnectionPool"); 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_sqlConnectionFactory = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.SqlConnectionFactory"); 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_waitHandleDbConnectionPool.GetProperty("Count", BindingFlags.Instance | BindingFlags.Public); + // Resolve Count from the interface so it works with both pool implementations + private static PropertyInfo s_dbConnectionPoolCount = s_dbConnectionPool.GetProperty("Count", BindingFlags.Instance | BindingFlags.Public); + private static PropertyInfo s_dbConnectionPoolIdleCount = s_dbConnectionPool.GetProperty("IdleCount", BindingFlags.Instance | BindingFlags.Public); private static PropertyInfo s_dictStringPoolGroupGetKeys = s_dictStringPoolGroup.GetProperty("Keys"); private static PropertyInfo s_dictPoolIdentityPoolValues = s_dictPoolIdentityPool.GetProperty("Values"); private static PropertyInfo s_sqlConnectionFactorySingleton = s_sqlConnectionFactory.GetProperty("Instance", BindingFlags.Static | BindingFlags.NonPublic); private static FieldInfo s_dbConnectionFactoryPoolGroupList = s_sqlConnectionFactory.GetField("_connectionPoolGroups", BindingFlags.Instance | BindingFlags.NonPublic); private static FieldInfo s_dbConnectionPoolGroupPoolCollection = s_dbConnectionPoolGroup.GetField("_poolCollection", 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) { VerifyObjectIsPool(pool); - - ICollection oldStack = (ICollection)s_dbConnectionPoolStackOld.GetValue(pool); - ICollection newStack = (ICollection)s_dbConnectionPoolStackNew.GetValue(pool); - - return (oldStack.Count + newStack.Count); + return (int)s_dbConnectionPoolIdleCount.GetValue(pool, null); } /// @@ -107,10 +104,17 @@ public static object ConnectionPoolFromString(string connectionString) /// /// Causes the cleanup timer code in the connection pool to be invoked /// - /// A connection pool object + /// A connection pool object internal static void CleanConnectionPool(object pool) { VerifyObjectIsPool(pool); + + if (s_channelDbConnectionPool.IsInstanceOfType(pool)) + { + // ChannelDbConnectionPool does not have a cleanup timer callback. + return; + } + s_dbConnectionPoolCleanup.Invoke(pool, new object[] { null }); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs index 10c3939774..0d9b076dd2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs @@ -7,14 +7,19 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.Tests.Common; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class ConnectionPoolConnectionStringProvider : IEnumerable { - private static readonly string _TCPConnectionString = (new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = false, Pooling = true }).ConnectionString; - private static readonly string _tcpMarsConnStr = (new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = true, Pooling = true }).ConnectionString; + private static readonly string _TCPConnectionString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { + MultipleActiveResultSets = false, + Pooling = true}.ConnectionString; + private static readonly string _tcpMarsConnStr = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { + MultipleActiveResultSets = true, + Pooling = true }.ConnectionString; public IEnumerator GetEnumerator() { @@ -28,6 +33,29 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } + public class ConnectionPoolConnectionStringAndPoolVersionProvider : IEnumerable + { + private static readonly string _TCPConnectionString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { + MultipleActiveResultSets = false, + Pooling = true }.ConnectionString; + private static readonly string _tcpMarsConnStr = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { + MultipleActiveResultSets = true, + Pooling = true }.ConnectionString; + + public IEnumerator GetEnumerator() + { + yield return new object[] { _TCPConnectionString, false }; + yield return new object[] { _TCPConnectionString, true }; + if (DataTestUtility.IsNotAzureSynapse()) + { + yield return new object[] { _tcpMarsConnStr, false }; + yield return new object[] { _tcpMarsConnStr, true }; + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + // TODO Synapse: Fix these tests for Azure Synapse. public static class ConnectionPoolTest { @@ -118,9 +146,12 @@ public static void AccessTokenConnectionPoolingTest() /// Tests if clearing all of the pools does actually remove the pools /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [ClassData(typeof(ConnectionPoolConnectionStringProvider))] - public static void ClearAllPoolsTest(string connectionString) + [ClassData(typeof(ConnectionPoolConnectionStringAndPoolVersionProvider))] + public static void ClearAllPoolsTest(string connectionString, bool usePoolV2) { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseConnectionPoolV2 = usePoolV2; + SqlConnection.ClearAllPools(); Assert.True(0 == ConnectionPoolWrapper.AllConnectionPools().Length, "Pools exist after clearing all pools"); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index 219bb1fdf6..c7df9dc2c6 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -570,7 +570,7 @@ public void TestCount() public void TestErrorOccurred() { var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => _ = pool.ErrorOccurred); + Assert.False(pool.ErrorOccurred); } [Fact] @@ -682,46 +682,253 @@ public void TestUseLoadBalancing() #region Not Implemented Method Tests [Fact] - public void TestClear() + public void TestPutObjectFromTransactedPool() { var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => pool.Clear()); + Assert.Throws(() => pool.PutObjectFromTransactedPool(null!)); } [Fact] - public void TestPutObjectFromTransactedPool() + public void TestReplaceConnection() { var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => pool.PutObjectFromTransactedPool(null!)); + Assert.Throws(() => pool.ReplaceConnection(null!, null!, null!)); } [Fact] - public void TestReplaceConnection() + public void TestTransactionEnded() { var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => pool.ReplaceConnection(null!, null!, null!)); + Assert.Throws(() => pool.TransactionEnded(null!, null!)); } + #endregion + + #region Pool Clear Tests [Fact] - public void TestShutdown() + public void Clear_EmptyPool_DoesNotThrow() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => pool.Shutdown()); + + // Act & Assert - Should complete without error + pool.Clear(); + Assert.Equal(0, pool.Count); } [Fact] - public void TestStartup() + public void Clear_MultipleIdleConnections_AllAreDestroyed() { + // Arrange + int numConnections = 5; var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => pool.Startup()); + var owningConnections = new SqlConnection[numConnections]; + var internalConnections = new DbConnectionInternal?[numConnections]; + + for (int i = 0; i < numConnections; i++) + { + owningConnections[i] = new SqlConnection(); + pool.TryGetConnection( + owningConnections[i], + taskCompletionSource: null, + new DbConnectionOptions("", null), + out internalConnections[i] + ); + Assert.Equal(0, internalConnections[i]!.ClearGeneration); + } + + // Return all connections to the pool + for (int i = 0; i < numConnections; i++) + { + pool.ReturnInternalConnection(internalConnections[i]!, owningConnections[i]); + } + + // Act + pool.Clear(); + + // Assert + Assert.Equal(0, pool.Count); } [Fact] - public void TestTransactionEnded() + public void Clear_BusyConnection_NotDestroyedImmediately() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => pool.TransactionEnded(null!, null!)); + SqlConnection owningConnection = new(); + + pool.TryGetConnection( + owningConnection, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? busyConnection + ); + Assert.NotNull(busyConnection); + Assert.Equal(0, busyConnection.ClearGeneration); + + // Act - Clear while connection is still busy + pool.Clear(); + + // Assert - Busy connection is still tracked in the pool and retains its old generation + Assert.Equal(1, pool.Count); + Assert.Equal(0, busyConnection.ClearGeneration); } + + [Fact] + public void Clear_BusyConnectionReturned_IsDestroyed() + { + // Arrange + var pool = ConstructPool(SuccessfulConnectionFactory); + SqlConnection owningConnection = new(); + + pool.TryGetConnection( + owningConnection, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? busyConnection + ); + Assert.NotNull(busyConnection); + Assert.Equal(0, busyConnection.ClearGeneration); + + // Act - Clear, then return the busy connection + pool.Clear(); + + // Assert - Busy connection is still tracked but has stale generation + Assert.Equal(1, pool.Count); + + // Act - Return the busy connection + pool.ReturnInternalConnection(busyConnection, owningConnection); + + // Assert - The connection should have been destroyed on return (generation mismatch) + Assert.Equal(0, pool.Count); + } + + [Fact] + public void Clear_MixedBusyAndIdle_OnlyIdleDestroyedImmediately() + { + // Arrange + var pool = ConstructPool(SuccessfulConnectionFactory); + SqlConnection busyOwner = new(); + SqlConnection idleOwner = new(); + + pool.TryGetConnection( + busyOwner, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? busyConnection + ); + pool.TryGetConnection( + idleOwner, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? idleConnection + ); + Assert.NotNull(busyConnection); + Assert.NotNull(idleConnection); + Assert.Equal(0, busyConnection.ClearGeneration); + Assert.Equal(0, idleConnection.ClearGeneration); + + // Return only the idle connection + pool.ReturnInternalConnection(idleConnection, idleOwner); + + // Act + pool.Clear(); + + // Assert - Only the busy connection remains with stale generation + Assert.Equal(1, pool.Count); + Assert.Equal(0, busyConnection.ClearGeneration); + + // Now return the busy connection - it should be destroyed (generation 0 != pool generation 1) + pool.ReturnInternalConnection(busyConnection, busyOwner); + Assert.Equal(0, pool.Count); + } + + [Fact] + public void Clear_NewConnectionsAfterClear_ArePooledNormally() + { + // Arrange + var pool = ConstructPool(SuccessfulConnectionFactory); + SqlConnection owningConnection = new(); + + pool.TryGetConnection( + owningConnection, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? oldConnection + ); + Assert.Equal(0, oldConnection!.ClearGeneration); + pool.ReturnInternalConnection(oldConnection, owningConnection); + + // Act + pool.Clear(); + + // Get a new connection after clear + SqlConnection newOwner = new(); + pool.TryGetConnection( + newOwner, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? newConnection + ); + Assert.NotNull(newConnection); + + // The new connection should be different from the old one and have generation 1 + Assert.NotSame(oldConnection, newConnection); + Assert.Equal(1, newConnection.ClearGeneration); + + // Return the new connection - it should be pooled normally + pool.ReturnInternalConnection(newConnection, newOwner); + Assert.Equal(1, pool.Count); + + // Get another connection - it should reuse the post-clear connection (same generation) + SqlConnection reuseOwner = new(); + pool.TryGetConnection( + reuseOwner, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? reusedConnection + ); + Assert.Same(newConnection, reusedConnection); + Assert.Equal(1, reusedConnection!.ClearGeneration); + } + + [Fact] + public void Clear_MultipleClearCalls_DoNotCorruptState() + { + // Arrange + var pool = ConstructPool(SuccessfulConnectionFactory); + SqlConnection owningConnection = new(); + + pool.TryGetConnection( + owningConnection, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? connection + ); + Assert.Equal(0, connection!.ClearGeneration); + pool.ReturnInternalConnection(connection, owningConnection); + + // Act - Call clear multiple times rapidly + pool.Clear(); + pool.Clear(); + pool.Clear(); + + // Assert - Pool state is still valid + Assert.Equal(0, pool.Count); + + // New connections should have generation 3 (incremented three times) + SqlConnection newOwner = new(); + pool.TryGetConnection( + newOwner, + taskCompletionSource: null, + new DbConnectionOptions("", null), + out DbConnectionInternal? newConnection + ); + Assert.NotNull(newConnection); + Assert.Equal(1, pool.Count); + Assert.Equal(3, newConnection.ClearGeneration); + } + #endregion #region Test classes diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs new file mode 100644 index 0000000000..56aa9c234a --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs @@ -0,0 +1,244 @@ +// 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; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; + +#nullable enable + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool +{ + public class IdleConnectionChannelTest + { + #region TryWrite + + [Fact] + public void TryWrite_NonNullConnection_IncrementsCount() + { + var channel = new IdleConnectionChannel(); + + Assert.True(channel.TryWrite(new StubDbConnectionInternal())); + Assert.Equal(1, channel.Count); + } + + [Fact] + public void TryWrite_NullConnection_DoesNotIncrementCount() + { + var channel = new IdleConnectionChannel(); + + Assert.True(channel.TryWrite(null)); + Assert.Equal(0, channel.Count); + } + + [Fact] + public void TryWrite_MultipleConnections_TracksCountCorrectly() + { + var channel = new IdleConnectionChannel(); + + channel.TryWrite(new StubDbConnectionInternal()); + channel.TryWrite(new StubDbConnectionInternal()); + channel.TryWrite(null); + channel.TryWrite(new StubDbConnectionInternal()); + + Assert.Equal(3, channel.Count); + } + + #endregion + + #region TryRead + + [Fact] + public void TryRead_NonNullConnection_DecrementsCount() + { + var channel = new IdleConnectionChannel(); + channel.TryWrite(new StubDbConnectionInternal()); + Assert.Equal(1, channel.Count); + + Assert.True(channel.TryRead(out var connection)); + Assert.NotNull(connection); + Assert.Equal(0, channel.Count); + } + + [Fact] + public void TryRead_NullConnection_DoesNotDecrementCount() + { + var channel = new IdleConnectionChannel(); + channel.TryWrite(new StubDbConnectionInternal()); + channel.TryWrite(null); + Assert.Equal(1, channel.Count); + + // Read the non-null connection first (FIFO) + Assert.True(channel.TryRead(out var first)); + Assert.NotNull(first); + Assert.Equal(0, channel.Count); + + // Read the null + Assert.True(channel.TryRead(out var second)); + Assert.Null(second); + Assert.Equal(0, channel.Count); + } + + [Fact] + public void TryRead_EmptyChannel_ReturnsFalse() + { + var channel = new IdleConnectionChannel(); + + Assert.False(channel.TryRead(out var connection)); + Assert.Null(connection); + Assert.Equal(0, channel.Count); + } + + #endregion + + #region ReadAsync + + [Fact] + public async Task ReadAsync_NonNullConnection_DecrementsCount() + { + var channel = new IdleConnectionChannel(); + channel.TryWrite(new StubDbConnectionInternal()); + Assert.Equal(1, channel.Count); + + var connection = await channel.ReadAsync(CancellationToken.None); + + Assert.NotNull(connection); + Assert.Equal(0, channel.Count); + } + + [Fact] + public async Task ReadAsync_NullConnection_DoesNotDecrementCount() + { + var channel = new IdleConnectionChannel(); + channel.TryWrite(new StubDbConnectionInternal()); + channel.TryWrite(null); + Assert.Equal(1, channel.Count); + + // First read returns the non-null connection (FIFO) + var first = await channel.ReadAsync(CancellationToken.None); + Assert.NotNull(first); + Assert.Equal(0, channel.Count); + + // Second read returns null + var second = await channel.ReadAsync(CancellationToken.None); + Assert.Null(second); + Assert.Equal(0, channel.Count); + } + + [Fact] + public async Task ReadAsync_WaitsForWrite() + { + var channel = new IdleConnectionChannel(); + var expected = new StubDbConnectionInternal(); + + var readTask = channel.ReadAsync(CancellationToken.None); + Assert.False(readTask.IsCompleted); + + channel.TryWrite(expected); + + var connection = await readTask; + Assert.Same(expected, connection); + Assert.Equal(0, channel.Count); + } + + [Fact] + public async Task ReadAsync_Cancelled_ThrowsOperationCanceledException() + { + var channel = new IdleConnectionChannel(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + await Assert.ThrowsAnyAsync( + () => channel.ReadAsync(cts.Token).AsTask()); + } + + #endregion + + #region Mixed operations + + [Fact] + public void WriteAndReadSequence_CountStaysConsistent() + { + var channel = new IdleConnectionChannel(); + + // Write 3 + channel.TryWrite(new StubDbConnectionInternal()); + channel.TryWrite(new StubDbConnectionInternal()); + channel.TryWrite(new StubDbConnectionInternal()); + Assert.Equal(3, channel.Count); + + // Read 2 + channel.TryRead(out _); + channel.TryRead(out _); + Assert.Equal(1, channel.Count); + + // Write 1 more + channel.TryWrite(new StubDbConnectionInternal()); + Assert.Equal(2, channel.Count); + + // Read remaining 2 + channel.TryRead(out _); + channel.TryRead(out _); + Assert.Equal(0, channel.Count); + + // Channel is empty + Assert.False(channel.TryRead(out _)); + Assert.Equal(0, channel.Count); + } + + #endregion + + #region Multi-threaded Tests + + [Fact] + public async Task ConcurrentWriteAndRead_CountReturnsToZero() + { + var channel = new IdleConnectionChannel(); + var barrier = new Barrier(3); + const int iterations = 1000; + + async Task WriteAndRead() + { + barrier.SignalAndWait(); + + for (int i = 0; i < iterations; i++) + { + channel.TryWrite(new StubDbConnectionInternal()); + await channel.ReadAsync(CancellationToken.None); + } + } + + await Task.WhenAll( + Task.Run(WriteAndRead), + Task.Run(WriteAndRead), + Task.Run(WriteAndRead)); + + Assert.Equal(0, channel.Count); + } + + #endregion + + #region Helpers + + private class StubDbConnectionInternal : DbConnectionInternal + { + public override string ServerVersion => throw new NotImplementedException(); + + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) + => throw new NotImplementedException(); + + public override void EnlistTransaction(Transaction transaction) { } + protected override void Activate(Transaction transaction) { } + protected override void Deactivate() { } + internal override void ResetConnection() { } + } + + #endregion + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs index 18bd9c5ea3..9f284be22a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs @@ -661,6 +661,7 @@ internal class MockDbConnectionPool : IDbConnectionPool public int Count => throw new NotImplementedException(); public bool ErrorOccurred => throw new NotImplementedException(); public int Id { get; } = 1; + public int IdleCount => throw new NotImplementedException(); public DbConnectionPoolIdentity Identity => throw new NotImplementedException(); public bool IsRunning => throw new NotImplementedException(); public TimeSpan LoadBalanceTimeout => throw new NotImplementedException(); From c5da95dbebf897c895485bc67012f2b3e9c6db6b Mon Sep 17 00:00:00 2001 From: Erik Ejlskov Jensen Date: Fri, 24 Apr 2026 09:35:25 +0200 Subject: [PATCH 061/298] Update to latest Azure.Core package with Azure.Identity types (#4200) --- Directory.Packages.props | 8 +++---- doc/Directory.Packages.props | 8 ++++--- .../Directory.Packages.props | 12 +++++----- doc/apps/AzureAuthentication/README.md | 22 +++++++++---------- .../Microsoft.Data.SqlClient.Samples.csproj | 1 - .../Azure/src/Azure.csproj | 1 - ...Microsoft.Data.SqlClient.TestCommon.csproj | 4 ++-- ...soft.Data.SqlClient.FunctionalTests.csproj | 4 ++-- ...icrosoft.Data.SqlClient.ManualTests.csproj | 4 ++-- 9 files changed, 31 insertions(+), 33 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b1a858bf72..d18da0127a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -50,8 +50,8 @@ - - + + @@ -81,9 +81,7 @@ - - - + diff --git a/doc/Directory.Packages.props b/doc/Directory.Packages.props index 2ead6c1757..3f34943e76 100644 --- a/doc/Directory.Packages.props +++ b/doc/Directory.Packages.props @@ -2,9 +2,11 @@ + + - - - + + + diff --git a/doc/apps/AzureAuthentication/Directory.Packages.props b/doc/apps/AzureAuthentication/Directory.Packages.props index b512683765..1456264d7a 100644 --- a/doc/apps/AzureAuthentication/Directory.Packages.props +++ b/doc/apps/AzureAuthentication/Directory.Packages.props @@ -4,11 +4,11 @@ - - 7.0.0-preview4.26064.3 + + 7.0.0 - - 7.0.0-preview1.26064.3 + + 7.0.0 @@ -25,8 +25,8 @@ - - + + diff --git a/doc/apps/AzureAuthentication/README.md b/doc/apps/AzureAuthentication/README.md index a0d9d8b7ba..2a3bf16b86 100644 --- a/doc/apps/AzureAuthentication/README.md +++ b/doc/apps/AzureAuthentication/README.md @@ -33,8 +33,8 @@ Package versions are controlled through MSBuild properties. Pass them on the com | Property | Default | Description | | --- | --- | --- | -| `SqlClientVersion` | `7.0.0-preview4.26064.3` | Version of `Microsoft.Data.SqlClient` to reference. | -| `AkvProviderVersion` | `7.0.0-preview1.26064.3` | Version of `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` to reference. | +| `SqlClientVersion` | `7.0.0` | Version of `Microsoft.Data.SqlClient` to reference. | +| `AkvProviderVersion` | `7.0.0` | Version of `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` to reference. | | `AzureVersion` | None | Version of `Microsoft.Data.SqlClient.Extensions.Azure` to reference. When omitted, the `Azure` package will not be referenced. | ## Local Package Source @@ -68,9 +68,9 @@ Description: Supply specific package versions when building to test different versions of the SqlClient suite, for example: - -p:SqlClientVersion=7.0.0.preview4 - -p:AkvProviderVersion=7.0.1-preview2 - -p:AzureVersion=1.0.0-preview1 + -p:SqlClientVersion=7.1.0.preview1 + -p:AkvProviderVersion=7.0.0 + -p:AzureVersion=1.1.0-preview1 Usage: AzureAuthentication [options] @@ -104,9 +104,9 @@ Azure Authentication Tester --------------------------- Packages used: - SqlClient: 7.0.0-preview4.26055.1 - AKV Provider: 6.1.2 - Azure: 1.0.0-preview1.26055.1 + SqlClient: 7.0.0 + AKV Provider: 7.0.0 + Azure: 1.0.0 Connection details: Data Source: adotest.database.windows.net @@ -142,19 +142,19 @@ authentication provider. Run against locally-built packages (drop `.nupkg` files into the `packages/` folder first): ```bash -dotnet run -p:SqlClientVersion=7.0.0-preview4 -- -c "" +dotnet run -p:SqlClientVersion=7.1.0-preview1 -- -c "" ``` Run including the `Azure` extensions package: ```bash -dotnet run -p:AzureVersion=1.0.0-preview1 -- -c "" +dotnet run -p:AzureVersion=1.0.0 -- -c "" ``` Override all three versions at once: ```bash -dotnet run -p:SqlClientVersion=7.0.0-preview1 -p:AkvProviderVersion=7.0.0-preview1 -p:AzureVersion=1.0.0-preview1 -- -c "" +dotnet run -p:SqlClientVersion=7.1.0-preview1 -p:AkvProviderVersion=7.1.0-preview1 -p:AzureVersion=1.0.0 -- -c "" ``` ## Prerequisites diff --git a/doc/samples/Microsoft.Data.SqlClient.Samples.csproj b/doc/samples/Microsoft.Data.SqlClient.Samples.csproj index c194fac4d0..db1d5bd997 100644 --- a/doc/samples/Microsoft.Data.SqlClient.Samples.csproj +++ b/doc/samples/Microsoft.Data.SqlClient.Samples.csproj @@ -12,7 +12,6 @@ - diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj index b244dd467e..07caed5129 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj @@ -129,7 +129,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj index 3f014f9fbe..82d5b2513d 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj +++ b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj @@ -30,14 +30,14 @@ - + - + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 7045eecc65..0e9cb7b785 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -66,7 +66,7 @@ - + @@ -97,7 +97,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 110547a5de..c4f0f2bf5b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -355,7 +355,7 @@ - + @@ -391,7 +391,7 @@ - + From f9d1b4fd083490afa782456a2eb5ee0cd239cc9a Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 24 Apr 2026 17:50:14 +1000 Subject: [PATCH 062/298] Use _value instead of ToString in SqlConnectionEncryptOption (#4013) --- .../Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs index 833132c944..8ad98507bf 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionEncryptOption.cs @@ -105,7 +105,7 @@ public override bool Equals(object obj) if (obj != null && obj is SqlConnectionEncryptOption option) { - return ToString().Equals(option.ToString()); + return _value.Equals(option._value); } return false; @@ -114,7 +114,7 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - return ToString().GetHashCode(); + return _value.GetHashCode(); } } From 91f486dc48cd112e469c9326816735b7a1a3b295 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:51:14 +0100 Subject: [PATCH 063/298] Cleanup | Remove unused resource strings (#4101) * Rebuilt Strings.Designer.cs from Strings.resx No functional changes - just saved Strings.resx to avoid polluting the next few diffs. * Remove unused resource strings, pt1 * Remove unused resource strings, pt2 * Remove unused resource strings, pt3 * Remove unused resource strings, pt4 * Remove unused resource strings, pt5 * Remove unused resource strings, pt6 * Remove unused resource strings, pt7 * Remove unused resource strings, pt8 * Remove unused resource strings, pt9 * Remove unused resource strings, pt10 / mopup * Remove references to SqlMisc_NullValueMessage This assertion was testing the third-party SqlNullValueException class, and relied upon a resource which was never used in library code. --- .../Data/Common/AdapterUtil.netfx.cs | 4 - .../SqlClient/LocalDb/LocalDbApi.windows.cs | 6 +- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 20 - .../src/Resources/Strings.Designer.cs | 11774 +++------------- .../src/Resources/Strings.resx | 2785 +--- .../SQL/DataStreamTest/DataStreamTest.cs | 11 +- 6 files changed, 2195 insertions(+), 12405 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs index eb125e5b71..99f627d603 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs @@ -63,10 +63,6 @@ internal static ArgumentException InvalidValue(string parameterName) => #endregion - // DbDataAdapter - internal static InvalidOperationException ComputerNameEx(int lastError) => - InvalidOperation(StringsHelper.GetString(Strings.ADP_ComputerNameEx, lastError)); - // DBDataPermissionAttribute.KeyRestrictionBehavior internal static ArgumentOutOfRangeException InvalidKeyRestrictionBehavior(KeyRestrictionBehavior value) { 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 index 787b3b72e4..c0674ca6db 100644 --- 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 @@ -90,7 +90,7 @@ private static LocalDbCreateInstanceDelegate LocalDbCreateInstance { int hResult = Marshal.GetLastWin32Error(); SqlClientEventSource.Log.TryTraceEvent(" GetProcAddress for LocalDBCreateInstance error 0x{0}", hResult); - throw CreateLocalDbException(errorMessage: StringsHelper.GetString("LocalDB_MethodNotFound")); + throw CreateLocalDbException(errorMessage: StringsHelper.GetString(Strings.LocalDB_MethodNotFound)); } s_localDbCreateInstance = (LocalDbCreateInstanceDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDbCreateInstanceDelegate)); @@ -154,7 +154,7 @@ private static IntPtr UserInstanceDllHandle { SniNativeWrapper.SniGetLastError(out SniError sniError); throw CreateLocalDbException( - errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), + errorMessage: StringsHelper.GetString(Strings.LocalDB_FailedGetDLLHandle), sniError: sniError.sniError); } } @@ -237,7 +237,7 @@ internal static void CreateLocalDbInstance(string instance) if (hr < 0) { - throw CreateLocalDbException(errorMessage: StringsHelper.GetString("LocalDB_CreateFailed"), instance: instance, localDbError: hr); + throw CreateLocalDbException(errorMessage: StringsHelper.GetString(Strings.LocalDB_CreateFailed), instance: instance, localDbError: hr); } // Mark instance as created 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 d10df22966..96312a491d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -533,26 +533,11 @@ SqlAuthenticationMethod.ActiveDirectoryDefault or }; } - internal static Exception ParameterCannotBeEmpty(string paramName) - { - return ADP.ArgumentNull(StringsHelper.GetString(Strings.SQL_ParameterCannotBeEmpty, paramName)); - } - internal static Exception ParameterDirectionInvalidForOptimizedBinding(string paramName) { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ParameterDirectionInvalidForOptimizedBinding, paramName)); } - internal static Exception ActiveDirectoryInteractiveTimeout() - { - return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_Interactive_Authentication); - } - - internal static Exception ActiveDirectoryDeviceFlowTimeout() - { - return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_DeviceFlow_Authentication); - } - internal static Exception ActiveDirectoryTokenRetrievingTimeout(string authenticaton, string errorCode, Exception exception) { return ADP.TimeoutException(StringsHelper.GetString(Strings.AAD_Token_Retrieving_Timeout, authenticaton, errorCode, exception?.Message), exception); @@ -2155,11 +2140,6 @@ internal static Exception SocketDidNotThrow() { return new Exception(StringsHelper.GetString(Strings.SQL_SocketDidNotThrow, nameof(SocketException), nameof(SocketError.WouldBlock))); } -#else - static internal Exception SnapshotNotSupported(System.Data.IsolationLevel level) - { - return ADP.Argument(StringsHelper.GetString(Strings.SQL_SnapshotNotSupported, typeof(System.Data.IsolationLevel), level.ToString())); - } #endif } diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs index 2cae12cb6c..041b9aa8aa 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -19,7 +19,7 @@ namespace System { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { @@ -69,24 +69,6 @@ internal static string AAD_Token_Retrieving_Timeout { } } - /// - /// Looks up a localized string similar to Data adapter mapping error.. - /// - internal static string ADP_AdapterMappingExceptionMessage { - get { - return ResourceManager.GetString("ADP_AdapterMappingExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ascending. - /// - internal static string ADP_Ascending { - get { - return ResourceManager.GetString("ADP_Ascending", resourceCulture); - } - } - /// /// Looks up a localized string similar to Specified parameter name '{0}' is not valid.. /// @@ -150,15 +132,6 @@ internal static string ADP_CollectionIsNotParent { } } - /// - /// Looks up a localized string similar to The {0} with is already contained by this {1}.. - /// - internal static string ADP_CollectionIsParent { - get { - return ResourceManager.GetString("ADP_CollectionIsParent", resourceCulture); - } - } - /// /// Looks up a localized string similar to The {0} only accepts non-null {1} type objects.. /// @@ -178,13800 +151,6185 @@ internal static string ADP_CollectionRemoveInvalidObject { } /// - /// Looks up a localized string similar to The {0}.{1} is required to be unique, '{2}' already exists in the collection.. + /// Looks up a localized string similar to {0}: CommandText property has not been initialized. /// - internal static string ADP_CollectionUniqueValue { + internal static string ADP_CommandTextRequired { get { - return ResourceManager.GetString("ADP_CollectionUniqueValue", resourceCulture); + return ResourceManager.GetString("ADP_CommandTextRequired", resourceCulture); } } /// - /// Looks up a localized string similar to The column mapping from SourceColumn '{0}' failed because the DataColumn '{1}' is a computed column.. + /// Looks up a localized string similar to The connection was not closed. {0}. /// - internal static string ADP_ColumnSchemaExpression { + internal static string ADP_ConnectionAlreadyOpen { get { - return ResourceManager.GetString("ADP_ColumnSchemaExpression", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionAlreadyOpen", resourceCulture); } } /// - /// Looks up a localized string similar to Inconvertible type mismatch between SourceColumn '{0}' of {1} and the DataColumn '{2}' of {3}.. + /// Looks up a localized string similar to {0}: Connection property has not been initialized.. /// - internal static string ADP_ColumnSchemaMismatch { + internal static string ADP_ConnectionRequired { get { - return ResourceManager.GetString("ADP_ColumnSchemaMismatch", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionRequired", resourceCulture); } } /// - /// Looks up a localized string similar to Missing the DataColumn '{0}' for the SourceColumn '{2}'.. + /// Looks up a localized string similar to The connection's current state: {0}.. /// - internal static string ADP_ColumnSchemaMissing1 { + internal static string ADP_ConnectionStateMsg { get { - return ResourceManager.GetString("ADP_ColumnSchemaMissing1", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionStateMsg", resourceCulture); } } /// - /// Looks up a localized string similar to Missing the DataColumn '{0}' in the DataTable '{1}' for the SourceColumn '{2}'.. + /// Looks up a localized string similar to The connection's current state is closed.. /// - internal static string ADP_ColumnSchemaMissing2 { + internal static string ADP_ConnectionStateMsg_Closed { get { - return ResourceManager.GetString("ADP_ColumnSchemaMissing2", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionStateMsg_Closed", resourceCulture); } } /// - /// Looks up a localized string similar to {0}: CommandText property has not been initialized. + /// Looks up a localized string similar to The connection's current state is connecting.. /// - internal static string ADP_CommandTextRequired { + internal static string ADP_ConnectionStateMsg_Connecting { get { - return ResourceManager.GetString("ADP_CommandTextRequired", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionStateMsg_Connecting", resourceCulture); } } /// - /// Looks up a localized string similar to Unable to retrieve the ComputerNameDnsFullyQualified, {0}.. + /// Looks up a localized string similar to The connection's current state is open.. /// - internal static string ADP_ComputerNameEx { + internal static string ADP_ConnectionStateMsg_Open { get { - return ResourceManager.GetString("ADP_ComputerNameEx", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionStateMsg_Open", resourceCulture); } } /// - /// Looks up a localized string similar to The connection was not closed. {0}. + /// Looks up a localized string similar to The connection's current state is executing.. /// - internal static string ADP_ConnectionAlreadyOpen { + internal static string ADP_ConnectionStateMsg_OpenExecuting { get { - return ResourceManager.GetString("ADP_ConnectionAlreadyOpen", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionStateMsg_OpenExecuting", resourceCulture); } } /// - /// Looks up a localized string similar to The connection has been disabled.. + /// Looks up a localized string similar to The connection's current state is fetching.. /// - internal static string ADP_ConnectionIsDisabled { + internal static string ADP_ConnectionStateMsg_OpenFetching { get { - return ResourceManager.GetString("ADP_ConnectionIsDisabled", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionStateMsg_OpenFetching", resourceCulture); } } /// - /// Looks up a localized string similar to {0}: Connection property has not been initialized.. + /// Looks up a localized string similar to Format of the initialization string does not conform to specification starting at index {0}.. /// - internal static string ADP_ConnectionRequired { + internal static string ADP_ConnectionStringSyntax { get { - return ResourceManager.GetString("ADP_ConnectionRequired", resourceCulture); + return ResourceManager.GetString("ADP_ConnectionStringSyntax", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires a connection object. The Connection property has not been initialized.. + /// Looks up a localized string similar to Invalid attempt to call {0} when reader is closed.. /// - internal static string ADP_ConnectionRequired_Batch { + internal static string ADP_DataReaderClosed { get { - return ResourceManager.GetString("ADP_ConnectionRequired_Batch", resourceCulture); + return ResourceManager.GetString("ADP_DataReaderClosed", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the command clone to have a connection object. The Connection property of the command clone has not been initialized.. + /// Looks up a localized string similar to No mapping exists from DbType {0} to a known {1}.. /// - internal static string ADP_ConnectionRequired_Clone { + internal static string ADP_DbTypeNotSupported { get { - return ResourceManager.GetString("ADP_ConnectionRequired_Clone", resourceCulture); + return ResourceManager.GetString("ADP_DbTypeNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the DeleteCommand to have a connection object. The Connection property of the DeleteCommand has not been initialized.. + /// Looks up a localized string similar to {0} DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{1}.. /// - internal static string ADP_ConnectionRequired_Delete { + internal static string ADP_DeriveParametersNotSupported { get { - return ResourceManager.GetString("ADP_ConnectionRequired_Delete", resourceCulture); + return ResourceManager.GetString("ADP_DeriveParametersNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Fill: SelectCommand.Connection property has not been initialized.. + /// Looks up a localized string similar to The acceptable values for the property '{0}' are '{1}' or '{2}'.. /// - internal static string ADP_ConnectionRequired_Fill { + internal static string ADP_DoubleValuedProperty { get { - return ResourceManager.GetString("ADP_ConnectionRequired_Fill", resourceCulture); + return ResourceManager.GetString("ADP_DoubleValuedProperty", resourceCulture); } } /// - /// Looks up a localized string similar to FillPage: SelectCommand.Connection property has not been initialized.. + /// Looks up a localized string similar to Database cannot be null, the empty string, or string of only whitespace.. /// - internal static string ADP_ConnectionRequired_FillPage { + internal static string ADP_EmptyDatabaseName { get { - return ResourceManager.GetString("ADP_ConnectionRequired_FillPage", resourceCulture); + return ResourceManager.GetString("ADP_EmptyDatabaseName", resourceCulture); } } /// - /// Looks up a localized string similar to FillSchema: SelectCommand.Connection property has not been initialized.. + /// Looks up a localized string similar to Expecting non-empty string for '{0}' parameter.. /// - internal static string ADP_ConnectionRequired_FillSchema { + internal static string ADP_EmptyString { get { - return ResourceManager.GetString("ADP_ConnectionRequired_FillSchema", resourceCulture); + return ResourceManager.GetString("ADP_EmptyString", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the InsertCommand to have a connection object. The Connection property of the InsertCommand has not been initialized.. + /// Looks up a localized string similar to Internal DbConnection Error: {0}. /// - internal static string ADP_ConnectionRequired_Insert { + internal static string ADP_InternalConnectionError { get { - return ResourceManager.GetString("ADP_ConnectionRequired_Insert", resourceCulture); + return ResourceManager.GetString("ADP_InternalConnectionError", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the UpdateCommand to have a connection object. The Connection property of the UpdateCommand has not been initialized.. + /// Looks up a localized string similar to Internal .NET Framework Data Provider error {0}.. /// - internal static string ADP_ConnectionRequired_Update { + internal static string ADP_InternalProviderError { get { - return ResourceManager.GetString("ADP_ConnectionRequired_Update", resourceCulture); + return ResourceManager.GetString("ADP_InternalProviderError", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires a connection.. + /// Looks up a localized string similar to The length of argument '{0}' exceeds its limit of '{1}'.. /// - internal static string ADP_ConnectionRequired_UpdateRows { + internal static string ADP_InvalidArgumentLength { get { - return ResourceManager.GetString("ADP_ConnectionRequired_UpdateRows", resourceCulture); + return ResourceManager.GetString("ADP_InvalidArgumentLength", resourceCulture); } } /// - /// Looks up a localized string similar to The connection's current state: {0}.. + /// Looks up a localized string similar to Invalid CommandTimeout value {0}; the value must be >= 0.. /// - internal static string ADP_ConnectionStateMsg { + internal static string ADP_InvalidCommandTimeout { get { - return ResourceManager.GetString("ADP_ConnectionStateMsg", resourceCulture); + return ResourceManager.GetString("ADP_InvalidCommandTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to The connection's current state is closed.. + /// Looks up a localized string similar to Invalid value for key '{0}'.. /// - internal static string ADP_ConnectionStateMsg_Closed { + internal static string ADP_InvalidConnectionOptionValue { get { - return ResourceManager.GetString("ADP_ConnectionStateMsg_Closed", resourceCulture); + return ResourceManager.GetString("ADP_InvalidConnectionOptionValue", resourceCulture); } } /// - /// Looks up a localized string similar to The connection's current state is connecting.. + /// Looks up a localized string similar to The value's length for key '{0}' exceeds its limit of '{1}'.. /// - internal static string ADP_ConnectionStateMsg_Connecting { + internal static string ADP_InvalidConnectionOptionValueLength { get { - return ResourceManager.GetString("ADP_ConnectionStateMsg_Connecting", resourceCulture); + return ResourceManager.GetString("ADP_InvalidConnectionOptionValueLength", resourceCulture); } } /// - /// Looks up a localized string similar to The connection's current state is open.. + /// Looks up a localized string similar to The DataDirectory substitute is not a string.. /// - internal static string ADP_ConnectionStateMsg_Open { + internal static string ADP_InvalidDataDirectory { get { - return ResourceManager.GetString("ADP_ConnectionStateMsg_Open", resourceCulture); + return ResourceManager.GetString("ADP_InvalidDataDirectory", resourceCulture); } } /// - /// Looks up a localized string similar to The connection's current state is executing.. + /// Looks up a localized string similar to Specified length '{0}' is out of range.. /// - internal static string ADP_ConnectionStateMsg_OpenExecuting { + internal static string ADP_InvalidDataLength2 { get { - return ResourceManager.GetString("ADP_ConnectionStateMsg_OpenExecuting", resourceCulture); + return ResourceManager.GetString("ADP_InvalidDataLength2", resourceCulture); } } /// - /// Looks up a localized string similar to The connection's current state is fetching.. + /// Looks up a localized string similar to The parameter data type of {0} is invalid.. /// - internal static string ADP_ConnectionStateMsg_OpenFetching { + internal static string ADP_InvalidDataType { get { - return ResourceManager.GetString("ADP_ConnectionStateMsg_OpenFetching", resourceCulture); + return ResourceManager.GetString("ADP_InvalidDataType", resourceCulture); } } /// - /// Looks up a localized string similar to Format of the initialization string does not conform to specification starting at index {0}.. + /// Looks up a localized string similar to Invalid destination buffer (size of {0}) offset: {1}. /// - internal static string ADP_ConnectionStringSyntax { + internal static string ADP_InvalidDestinationBufferIndex { get { - return ResourceManager.GetString("ADP_ConnectionStringSyntax", resourceCulture); + return ResourceManager.GetString("ADP_InvalidDestinationBufferIndex", resourceCulture); } } /// - /// Looks up a localized string similar to Data adapter error.. + /// Looks up a localized string similar to The {0} enumeration value, {1}, is invalid.. /// - internal static string ADP_DataAdapterExceptionMessage { + internal static string ADP_InvalidEnumerationValue { get { - return ResourceManager.GetString("ADP_DataAdapterExceptionMessage", resourceCulture); + return ResourceManager.GetString("ADP_InvalidEnumerationValue", resourceCulture); } } /// - /// Looks up a localized string similar to The argument is too long.. + /// Looks up a localized string similar to {0} Invalid JSON string for vector.. /// - internal static string ADP_DatabaseNameTooLong { + internal static string ADP_InvalidJsonStringForVector { get { - return ResourceManager.GetString("ADP_DatabaseNameTooLong", resourceCulture); + return ResourceManager.GetString("ADP_InvalidJsonStringForVector", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid attempt to call {0} when reader is closed.. + /// Looks up a localized string similar to Invalid keyword, contain one or more of 'no characters', 'control characters', 'leading or trailing whitespace' or 'leading semicolons'.. /// - internal static string ADP_DataReaderClosed { + internal static string ADP_InvalidKey { get { - return ResourceManager.GetString("ADP_DataReaderClosed", resourceCulture); + return ResourceManager.GetString("ADP_InvalidKey", resourceCulture); } } /// - /// Looks up a localized string similar to DB concurrency violation.. + /// Looks up a localized string similar to Invalid value for this metadata.. /// - internal static string ADP_DBConcurrencyExceptionMessage { + internal static string ADP_InvalidMetaDataValue { get { - return ResourceManager.GetString("ADP_DBConcurrencyExceptionMessage", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMetaDataValue", resourceCulture); } } /// - /// Looks up a localized string similar to '{0}' cannot be called when the DbDataRecord is read only.. + /// Looks up a localized string similar to Invalid min or max pool size values, min pool size cannot be greater than the max pool size.. /// - internal static string ADP_DbDataUpdatableRecordReadOnly { + internal static string ADP_InvalidMinMaxPoolSizeValues { get { - return ResourceManager.GetString("ADP_DbDataUpdatableRecordReadOnly", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMinMaxPoolSizeValues", resourceCulture); } } /// - /// Looks up a localized string similar to '{0}' cannot be called when the record is read only.. + /// Looks up a localized string similar to Cannot set the AccessToken property if 'Authentication' has been specified in the connection string.. /// - internal static string ADP_DbRecordReadOnly { + internal static string ADP_InvalidMixedUsageOfAccessTokenAndAuthentication { get { - return ResourceManager.GetString("ADP_DbRecordReadOnly", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndAuthentication", resourceCulture); } } /// - /// Looks up a localized string similar to No mapping exists from DbType {0} to a known {1}.. + /// Looks up a localized string similar to Cannot set the AccessToken property if the Credential property is already set.. /// - internal static string ADP_DbTypeNotSupported { + internal static string ADP_InvalidMixedUsageOfAccessTokenAndCredential { get { - return ResourceManager.GetString("ADP_DbTypeNotSupported", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndCredential", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot enlist in the transaction because the connection is the primary connection for a delegated or promoted transaction.. + /// Looks up a localized string similar to Cannot set the AccessToken property if the 'Integrated Security' connection string keyword has been set to 'true' or 'SSPI'.. /// - internal static string ADP_DelegatedTransactionPresent { + internal static string ADP_InvalidMixedUsageOfAccessTokenAndIntegratedSecurity { get { - return ResourceManager.GetString("ADP_DelegatedTransactionPresent", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndIntegratedSecurity", resourceCulture); } } /// - /// Looks up a localized string similar to {0} DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{1}.. + /// Looks up a localized string similar to Cannot set the AccessToken property if the AccessTokenCallback has been set.. /// - internal static string ADP_DeriveParametersNotSupported { + internal static string ADP_InvalidMixedUsageOfAccessTokenAndTokenCallback { get { - return ResourceManager.GetString("ADP_DeriveParametersNotSupported", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndTokenCallback", resourceCulture); } } /// - /// Looks up a localized string similar to Descending. + /// Looks up a localized string similar to Cannot set the AccessToken property if 'UserID', 'UID', 'Password', or 'PWD' has been specified in connection string.. /// - internal static string ADP_Descending { + internal static string ADP_InvalidMixedUsageOfAccessTokenAndUserIDPassword { get { - return ResourceManager.GetString("ADP_Descending", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndUserIDPassword", resourceCulture); } } /// - /// Looks up a localized string similar to The acceptable values for the property '{0}' are '{1}' or '{2}'.. + /// Looks up a localized string similar to Cannot set the AccessTokenCallback property if the 'Integrated Security' connection string keyword has been set to 'true' or 'SSPI'.. /// - internal static string ADP_DoubleValuedProperty { + internal static string ADP_InvalidMixedUsageOfAccessTokenCallbackAndIntegratedSecurity { get { - return ResourceManager.GetString("ADP_DoubleValuedProperty", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenCallbackAndIntegratedSecurity", resourceCulture); } } /// - /// Looks up a localized string similar to Dynamic SQL generation is not supported against multiple base tables.. + /// Looks up a localized string similar to Cannot set the AccessTokenCallback property if 'Authentication=Active Directory Default' has been specified in the connection string.. /// - internal static string ADP_DynamicSQLJoinUnsupported { + internal static string ADP_InvalidMixedUsageOfAuthenticationAndTokenCallback { get { - return ResourceManager.GetString("ADP_DynamicSQLJoinUnsupported", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfAuthenticationAndTokenCallback", resourceCulture); } } /// - /// Looks up a localized string similar to Dynamic SQL generation not supported against table names '{0}' that contain the QuotePrefix or QuoteSuffix character '{1}'.. + /// Looks up a localized string similar to Cannot set the Credential property if the AccessToken property is already set.. /// - internal static string ADP_DynamicSQLNestedQuote { + internal static string ADP_InvalidMixedUsageOfCredentialAndAccessToken { get { - return ResourceManager.GetString("ADP_DynamicSQLNestedQuote", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfCredentialAndAccessToken", resourceCulture); } } /// - /// Looks up a localized string similar to Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.. + /// Looks up a localized string similar to Cannot use Credential with UserID, UID, Password, or PWD connection string keywords.. /// - internal static string ADP_DynamicSQLNoKeyInfoDelete { + internal static string ADP_InvalidMixedUsageOfSecureAndClearCredential { get { - return ResourceManager.GetString("ADP_DynamicSQLNoKeyInfoDelete", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfSecureAndClearCredential", resourceCulture); } } /// - /// Looks up a localized string similar to Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not contain a row version column.. + /// Looks up a localized string similar to Cannot use Credential with Integrated Security connection string keyword.. /// - internal static string ADP_DynamicSQLNoKeyInfoRowVersionDelete { + internal static string ADP_InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity { get { - return ResourceManager.GetString("ADP_DynamicSQLNoKeyInfoRowVersionDelete", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity", resourceCulture); } } /// - /// Looks up a localized string similar to Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not contain a row version column.. + /// Looks up a localized string similar to {0} "{1}".. /// - internal static string ADP_DynamicSQLNoKeyInfoRowVersionUpdate { + internal static string ADP_InvalidMultipartName { get { - return ResourceManager.GetString("ADP_DynamicSQLNoKeyInfoRowVersionUpdate", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMultipartName", resourceCulture); } } /// - /// Looks up a localized string similar to Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.. + /// Looks up a localized string similar to {0} "{1}", incorrect usage of quotes.. /// - internal static string ADP_DynamicSQLNoKeyInfoUpdate { + internal static string ADP_InvalidMultipartNameQuoteUsage { get { - return ResourceManager.GetString("ADP_DynamicSQLNoKeyInfoUpdate", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMultipartNameQuoteUsage", resourceCulture); } } /// - /// Looks up a localized string similar to Dynamic SQL generation is not supported against a SelectCommand that does not return any base table information.. + /// Looks up a localized string similar to {0} "{1}", the current limit of "{2}" is insufficient.. /// - internal static string ADP_DynamicSQLNoTableInfo { + internal static string ADP_InvalidMultipartNameToManyParts { get { - return ResourceManager.GetString("ADP_DynamicSQLNoTableInfo", resourceCulture); + return ResourceManager.GetString("ADP_InvalidMultipartNameToManyParts", resourceCulture); } } /// - /// Looks up a localized string similar to Expecting non-empty array for '{0}' parameter.. + /// Looks up a localized string similar to Invalid parameter Offset value '{0}'. The value must be greater than or equal to 0.. /// - internal static string ADP_EmptyArray { + internal static string ADP_InvalidOffsetValue { get { - return ResourceManager.GetString("ADP_EmptyArray", resourceCulture); + return ResourceManager.GetString("ADP_InvalidOffsetValue", resourceCulture); } } /// - /// Looks up a localized string similar to Database cannot be null, the empty string, or string of only whitespace.. + /// Looks up a localized string similar to Specified QuotePrefix and QuoteSuffix values do not match.. /// - internal static string ADP_EmptyDatabaseName { + internal static string ADP_InvalidPrefixSuffix { get { - return ResourceManager.GetString("ADP_EmptyDatabaseName", resourceCulture); + return ResourceManager.GetString("ADP_InvalidPrefixSuffix", resourceCulture); } } /// - /// Looks up a localized string similar to Expecting non-empty string for '{0}' parameter.. + /// Looks up a localized string similar to Specified SeekOrigin value is invalid.. /// - internal static string ADP_EmptyString { + internal static string ADP_InvalidSeekOrigin { get { - return ResourceManager.GetString("ADP_EmptyString", resourceCulture); + return ResourceManager.GetString("ADP_InvalidSeekOrigin", resourceCulture); } } /// - /// Looks up a localized string similar to '{0}':The length of the literal value must be even.. + /// Looks up a localized string similar to Invalid parameter Size value '{0}'. The value must be greater than or equal to 0.. /// - internal static string ADP_EvenLengthLiteralValue { + internal static string ADP_InvalidSizeValue { get { - return ResourceManager.GetString("ADP_EvenLengthLiteralValue", resourceCulture); + return ResourceManager.GetString("ADP_InvalidSizeValue", resourceCulture); } } /// - /// Looks up a localized string similar to Hierarchical chapter columns must map to an AutoIncrement DataColumn.. + /// Looks up a localized string similar to Invalid source buffer (size of {0}) offset: {1}. /// - internal static string ADP_FillChapterAutoIncrement { + internal static string ADP_InvalidSourceBufferIndex { get { - return ResourceManager.GetString("ADP_FillChapterAutoIncrement", resourceCulture); + return ResourceManager.GetString("ADP_InvalidSourceBufferIndex", resourceCulture); } } /// - /// Looks up a localized string similar to Fill: expected a non-empty string for the SourceTable name.. + /// Looks up a localized string similar to The value contains embedded nulls (\u0000).. /// - internal static string ADP_FillRequiresSourceTableName { + internal static string ADP_InvalidValue { get { - return ResourceManager.GetString("ADP_FillRequiresSourceTableName", resourceCulture); + return ResourceManager.GetString("ADP_InvalidValue", resourceCulture); } } /// - /// Looks up a localized string similar to FillSchema: expected a non-empty string for the SourceTable name.. + /// Looks up a localized string similar to Invalid vector header received.. /// - internal static string ADP_FillSchemaRequiresSourceTableName { + internal static string ADP_InvalidVectorHeader { get { - return ResourceManager.GetString("ADP_FillSchemaRequiresSourceTableName", resourceCulture); + return ResourceManager.GetString("ADP_InvalidVectorHeader", resourceCulture); } } /// - /// Looks up a localized string similar to '{0}':The literal value must be a string with hexadecimal digits. + /// Looks up a localized string similar to Invalid Xml; can only parse elements of version one.. /// - internal static string ADP_HexDigitLiteralValue { + internal static string ADP_InvalidXMLBadVersion { get { - return ResourceManager.GetString("ADP_HexDigitLiteralValue", resourceCulture); + return ResourceManager.GetString("ADP_InvalidXMLBadVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Incorrect async result.. + /// Looks up a localized string similar to Keyword not supported: '{0}'.. /// - internal static string ADP_IncorrectAsyncResult { + internal static string ADP_KeywordNotSupported { get { - return ResourceManager.GetString("ADP_IncorrectAsyncResult", resourceCulture); + return ResourceManager.GetString("ADP_KeywordNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Internal DbConnection Error: {0}. + /// Looks up a localized string similar to Cannot enlist in the transaction because a local transaction is in progress on the connection. Finish local transaction and retry.. /// - internal static string ADP_InternalConnectionError { + internal static string ADP_LocalTransactionPresent { get { - return ResourceManager.GetString("ADP_InternalConnectionError", resourceCulture); + return ResourceManager.GetString("ADP_LocalTransactionPresent", resourceCulture); } } /// - /// Looks up a localized string similar to Internal .NET Framework Data Provider error {0}.. + /// Looks up a localized string similar to Mismatched end method call for asyncResult. Expected call to {0} but {1} was called instead.. /// - internal static string ADP_InternalProviderError { + internal static string ADP_MismatchedAsyncResult { get { - return ResourceManager.GetString("ADP_InternalProviderError", resourceCulture); + return ResourceManager.GetString("ADP_MismatchedAsyncResult", resourceCulture); } } /// - /// Looks up a localized string similar to The length of argument '{0}' exceeds its limit of '{1}'.. + /// Looks up a localized string similar to Use of key '{0}' requires the key '{1}' to be present.. /// - internal static string ADP_InvalidArgumentLength { + internal static string ADP_MissingConnectionOptionValue { get { - return ResourceManager.GetString("ADP_InvalidArgumentLength", resourceCulture); + return ResourceManager.GetString("ADP_MissingConnectionOptionValue", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid argument value for method '{0}'.. + /// Looks up a localized string similar to {0} must be marked as read only.. /// - internal static string ADP_InvalidArgumentValue { + internal static string ADP_MustBeReadOnly { get { - return ResourceManager.GetString("ADP_InvalidArgumentValue", resourceCulture); + return ResourceManager.GetString("ADP_MustBeReadOnly", resourceCulture); } } /// - /// Looks up a localized string similar to Buffer offset '{1}' plus the bytes available '{0}' is greater than the length of the passed in buffer.. + /// Looks up a localized string similar to Invalid value for argument '{0}'. The value must be greater than or equal to 0.. /// - internal static string ADP_InvalidBufferSizeOrIndex { + internal static string ADP_NegativeParameter { get { - return ResourceManager.GetString("ADP_InvalidBufferSizeOrIndex", resourceCulture); + return ResourceManager.GetString("ADP_NegativeParameter", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid CommandTimeout value {0}; the value must be >= 0.. + /// Looks up a localized string similar to The ConnectionString property has not been initialized.. /// - internal static string ADP_InvalidCommandTimeout { + internal static string ADP_NoConnectionString { get { - return ResourceManager.GetString("ADP_InvalidCommandTimeout", resourceCulture); + return ResourceManager.GetString("ADP_NoConnectionString", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid value for key '{0}'.. + /// Looks up a localized string similar to Timeout attempting to open the connection. The time period elapsed prior to attempting to open the connection has been exceeded. This may have occurred because of too many simultaneous non-pooled connection attempts.. /// - internal static string ADP_InvalidConnectionOptionValue { + internal static string ADP_NonPooledOpenTimeout { get { - return ResourceManager.GetString("ADP_InvalidConnectionOptionValue", resourceCulture); + return ResourceManager.GetString("ADP_NonPooledOpenTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to The value's length for key '{0}' exceeds its limit of '{1}'.. + /// Looks up a localized string similar to Invalid {2} attempt at dataIndex '{0}'. With CommandBehavior.SequentialAccess, you may only read from dataIndex '{1}' or greater.. /// - internal static string ADP_InvalidConnectionOptionValueLength { + internal static string ADP_NonSeqByteAccess { get { - return ResourceManager.GetString("ADP_InvalidConnectionOptionValueLength", resourceCulture); + return ResourceManager.GetString("ADP_NonSeqByteAccess", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid 'Connect Timeout' value which must be an integer >= 0.. + /// Looks up a localized string similar to Invalid attempt to read from column ordinal '{0}'. With CommandBehavior.SequentialAccess, you may only read from column ordinal '{1}' or greater.. /// - internal static string ADP_InvalidConnectTimeoutValue { + internal static string ADP_NonSequentialColumnAccess { get { - return ResourceManager.GetString("ADP_InvalidConnectTimeoutValue", resourceCulture); + return ResourceManager.GetString("ADP_NonSequentialColumnAccess", resourceCulture); } } /// - /// Looks up a localized string similar to The DataDirectory substitute is not a string.. + /// Looks up a localized string similar to SqlBatchCommand list has not been initialized.. /// - internal static string ADP_InvalidDataDirectory { + internal static string ADP_NoSqlBatchCommandList { get { - return ResourceManager.GetString("ADP_InvalidDataDirectory", resourceCulture); + return ResourceManager.GetString("ADP_NoSqlBatchCommandList", resourceCulture); } } /// - /// Looks up a localized string similar to Data length '{0}' is less than 0.. + /// Looks up a localized string similar to The stored procedure '{0}' doesn't exist.. /// - internal static string ADP_InvalidDataLength { + internal static string ADP_NoStoredProcedureExists { get { - return ResourceManager.GetString("ADP_InvalidDataLength", resourceCulture); + return ResourceManager.GetString("ADP_NoStoredProcedureExists", resourceCulture); } } /// - /// Looks up a localized string similar to Specified length '{0}' is out of range.. + /// Looks up a localized string similar to Given security element is not a permission element.. /// - internal static string ADP_InvalidDataLength2 { + internal static string ADP_NotAPermissionElement { get { - return ResourceManager.GetString("ADP_InvalidDataLength2", resourceCulture); + return ResourceManager.GetString("ADP_NotAPermissionElement", resourceCulture); } } /// - /// Looks up a localized string similar to The parameter data type of {0} is invalid.. + /// Looks up a localized string similar to The {0} enumeration value, {1}, is not supported by the {2} method.. /// - internal static string ADP_InvalidDataType { + internal static string ADP_NotSupportedEnumerationValue { get { - return ResourceManager.GetString("ADP_InvalidDataType", resourceCulture); + return ResourceManager.GetString("ADP_NotSupportedEnumerationValue", resourceCulture); } } /// - /// Looks up a localized string similar to Data type '{0}' can not be formatted as a literal because it has an invalid date time digits.. + /// Looks up a localized string similar to 'null' value not supported for output parameter '{0}' of SqlDbtype Vector.. /// - internal static string ADP_InvalidDateTimeDigits { + internal static string ADP_NullOutputParameterValueForVector { get { - return ResourceManager.GetString("ADP_InvalidDateTimeDigits", resourceCulture); + return ResourceManager.GetString("ADP_NullOutputParameterValueForVector", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid destination buffer (size of {0}) offset: {1}. + /// Looks up a localized string similar to Not allowed to change the '{0}' property. {1}. /// - internal static string ADP_InvalidDestinationBufferIndex { + internal static string ADP_OpenConnectionPropertySet { get { - return ResourceManager.GetString("ADP_InvalidDestinationBufferIndex", resourceCulture); + return ResourceManager.GetString("ADP_OpenConnectionPropertySet", resourceCulture); } } /// - /// Looks up a localized string similar to The {0} enumeration value, {1}, is invalid.. + /// Looks up a localized string similar to {0} requires an open and available Connection. {1}. /// - internal static string ADP_InvalidEnumerationValue { + internal static string ADP_OpenConnectionRequired { get { - return ResourceManager.GetString("ADP_InvalidEnumerationValue", resourceCulture); + return ResourceManager.GetString("ADP_OpenConnectionRequired", resourceCulture); } } /// - /// Looks up a localized string similar to The value can not be formatted as a literal of the requested type.. + /// Looks up a localized string similar to There is already an open DataReader associated with this {0} which must be closed first.. /// - internal static string ADP_InvalidFormatValue { + internal static string ADP_OpenReaderExists { get { - return ResourceManager.GetString("ADP_InvalidFormatValue", resourceCulture); + return ResourceManager.GetString("ADP_OpenReaderExists", resourceCulture); } } /// - /// Looks up a localized string similar to Implicit conversion of object type '{0}' to data type '{1}' is not supported.. + /// Looks up a localized string similar to Operation aborted.. /// - internal static string ADP_InvalidImplicitConversion { + internal static string ADP_OperationAborted { get { - return ResourceManager.GetString("ADP_InvalidImplicitConversion", resourceCulture); + return ResourceManager.GetString("ADP_OperationAborted", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid keyword, contain one or more of 'no characters', 'control characters', 'leading or trailing whitespace' or 'leading semicolons'.. + /// Looks up a localized string similar to Operation aborted due to an exception (see InnerException for details).. /// - internal static string ADP_InvalidKey { + internal static string ADP_OperationAbortedExceptionMessage { get { - return ResourceManager.GetString("ADP_InvalidKey", resourceCulture); + return ResourceManager.GetString("ADP_OperationAbortedExceptionMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Data type '{0}' can not be formatted as a literal because it has an invalid maximum scale.. + /// Looks up a localized string similar to {0} does not support parallel transactions.. /// - internal static string ADP_InvalidMaximumScale { + internal static string ADP_ParallelTransactionsNotSupported { get { - return ResourceManager.GetString("ADP_InvalidMaximumScale", resourceCulture); + return ResourceManager.GetString("ADP_ParallelTransactionsNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to The MaxRecords value of {0} is invalid; the value must be >= 0.. + /// Looks up a localized string similar to Failed to convert parameter value from a {0} to a {1}.. /// - internal static string ADP_InvalidMaxRecords { + internal static string ADP_ParameterConversionFailed { get { - return ResourceManager.GetString("ADP_InvalidMaxRecords", resourceCulture); + return ResourceManager.GetString("ADP_ParameterConversionFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid value for this metadata.. + /// Looks up a localized string similar to Parameter value '{0}' is out of range.. /// - internal static string ADP_InvalidMetaDataValue { + internal static string ADP_ParameterValueOutOfRange { get { - return ResourceManager.GetString("ADP_InvalidMetaDataValue", resourceCulture); + return ResourceManager.GetString("ADP_ParameterValueOutOfRange", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid min or max pool size values, min pool size cannot be greater than the max pool size.. + /// Looks up a localized string similar to Can not start another operation while there is an asynchronous operation pending.. /// - internal static string ADP_InvalidMinMaxPoolSizeValues { + internal static string ADP_PendingAsyncOperation { get { - return ResourceManager.GetString("ADP_InvalidMinMaxPoolSizeValues", resourceCulture); + return ResourceManager.GetString("ADP_PendingAsyncOperation", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the AccessToken property if 'Authentication' has been specified in the connection string.. + /// Looks up a localized string similar to Type mismatch.. /// - internal static string ADP_InvalidMixedUsageOfAccessTokenAndAuthentication { + internal static string ADP_PermissionTypeMismatch { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndAuthentication", resourceCulture); + return ResourceManager.GetString("ADP_PermissionTypeMismatch", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the AccessToken property if the Credential property is already set.. + /// Looks up a localized string similar to Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.. /// - internal static string ADP_InvalidMixedUsageOfAccessTokenAndCredential { + internal static string ADP_PooledOpenTimeout { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndCredential", resourceCulture); + return ResourceManager.GetString("ADP_PooledOpenTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the AccessToken property if the 'Integrated Security' connection string keyword has been set to 'true' or 'SSPI'.. + /// Looks up a localized string similar to {0}.Prepare method requires parameters of type '{1}' have an explicitly set Precision and Scale.. /// - internal static string ADP_InvalidMixedUsageOfAccessTokenAndIntegratedSecurity { + internal static string ADP_PrepareParameterScale { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndIntegratedSecurity", resourceCulture); + return ResourceManager.GetString("ADP_PrepareParameterScale", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the AccessToken property if the AccessTokenCallback has been set.. + /// Looks up a localized string similar to {0}.Prepare method requires all variable length parameters to have an explicitly set non-zero Size.. /// - internal static string ADP_InvalidMixedUsageOfAccessTokenAndTokenCallback { + internal static string ADP_PrepareParameterSize { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndTokenCallback", resourceCulture); + return ResourceManager.GetString("ADP_PrepareParameterSize", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the AccessToken property if 'UserID', 'UID', 'Password', or 'PWD' has been specified in connection string.. + /// Looks up a localized string similar to {0}.Prepare method requires all parameters to have an explicitly set type.. /// - internal static string ADP_InvalidMixedUsageOfAccessTokenAndUserIDPassword { + internal static string ADP_PrepareParameterType { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenAndUserIDPassword", resourceCulture); + return ResourceManager.GetString("ADP_PrepareParameterType", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the AccessTokenCallback property if the 'Integrated Security' connection string keyword has been set to 'true' or 'SSPI'.. + /// Looks up a localized string similar to The only acceptable value for the property '{0}' is '{1}'.. /// - internal static string ADP_InvalidMixedUsageOfAccessTokenCallbackAndIntegratedSecurity { + internal static string ADP_SingleValuedProperty { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfAccessTokenCallbackAndIntegratedSecurity", resourceCulture); + return ResourceManager.GetString("ADP_SingleValuedProperty", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the AccessTokenCallback property if 'Authentication=Active Directory Default' has been specified in the connection string.. + /// Looks up a localized string similar to Invalid attempt to {0} when stream is closed.. /// - internal static string ADP_InvalidMixedUsageOfAuthenticationAndTokenCallback { + internal static string ADP_StreamClosed { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfAuthenticationAndTokenCallback", resourceCulture); + return ResourceManager.GetString("ADP_StreamClosed", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the Credential property if the AccessToken property is already set.. + /// Looks up a localized string similar to The transaction associated with the current connection has completed but has not been disposed. The transaction must be disposed before the connection can be used to execute SQL statements.. /// - internal static string ADP_InvalidMixedUsageOfCredentialAndAccessToken { + internal static string ADP_TransactionCompletedButNotDisposed { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfCredentialAndAccessToken", resourceCulture); + return ResourceManager.GetString("ADP_TransactionCompletedButNotDisposed", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot use Credential with UserID, UID, Password, or PWD connection string keywords.. + /// Looks up a localized string similar to The transaction is either not associated with the current connection or has been completed.. /// - internal static string ADP_InvalidMixedUsageOfSecureAndClearCredential { + internal static string ADP_TransactionConnectionMismatch { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfSecureAndClearCredential", resourceCulture); + return ResourceManager.GetString("ADP_TransactionConnectionMismatch", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot use Credential with Integrated Security connection string keyword.. + /// Looks up a localized string similar to Connection currently has transaction enlisted. Finish current transaction and retry.. /// - internal static string ADP_InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity { + internal static string ADP_TransactionPresent { get { - return ResourceManager.GetString("ADP_InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity", resourceCulture); + return ResourceManager.GetString("ADP_TransactionPresent", resourceCulture); } } /// - /// Looks up a localized string similar to {0} "{1}".. + /// Looks up a localized string similar to {0} requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized.. /// - internal static string ADP_InvalidMultipartName { + internal static string ADP_TransactionRequired { get { - return ResourceManager.GetString("ADP_InvalidMultipartName", resourceCulture); + return ResourceManager.GetString("ADP_TransactionRequired", resourceCulture); } } /// - /// Looks up a localized string similar to {0} "{1}", incorrect usage of quotes.. + /// Looks up a localized string similar to This {0} has completed; it is no longer usable.. /// - internal static string ADP_InvalidMultipartNameQuoteUsage { + internal static string ADP_TransactionZombied { get { - return ResourceManager.GetString("ADP_InvalidMultipartNameQuoteUsage", resourceCulture); + return ResourceManager.GetString("ADP_TransactionZombied", resourceCulture); } } /// - /// Looks up a localized string similar to {0} "{1}", the current limit of "{2}" is insufficient.. + /// Looks up a localized string similar to {1}[{0}]: the Size property has an invalid size of 0.. /// - internal static string ADP_InvalidMultipartNameToManyParts { + internal static string ADP_UninitializedParameterSize { get { - return ResourceManager.GetString("ADP_InvalidMultipartNameToManyParts", resourceCulture); + return ResourceManager.GetString("ADP_UninitializedParameterSize", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid parameter Offset value '{0}'. The value must be greater than or equal to 0.. + /// Looks up a localized string similar to No mapping exists from object type {0} to a known managed provider native type.. /// - internal static string ADP_InvalidOffsetValue { + internal static string ADP_UnknownDataType { get { - return ResourceManager.GetString("ADP_InvalidOffsetValue", resourceCulture); + return ResourceManager.GetString("ADP_UnknownDataType", resourceCulture); } } /// - /// Looks up a localized string similar to Specified QuotePrefix and QuoteSuffix values do not match.. + /// Looks up a localized string similar to Unable to handle an unknown TypeCode {0} returned by Type {1}.. /// - internal static string ADP_InvalidPrefixSuffix { + internal static string ADP_UnknownDataTypeCode { get { - return ResourceManager.GetString("ADP_InvalidPrefixSuffix", resourceCulture); + return ResourceManager.GetString("ADP_UnknownDataTypeCode", resourceCulture); } } /// - /// Looks up a localized string similar to Specified SeekOrigin value is invalid.. + /// Looks up a localized string similar to The version of SQL Server in use does not support datatype '{0}'.. /// - internal static string ADP_InvalidSeekOrigin { + internal static string ADP_VersionDoesNotSupportDataType { get { - return ResourceManager.GetString("ADP_InvalidSeekOrigin", resourceCulture); + return ResourceManager.GetString("ADP_VersionDoesNotSupportDataType", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid parameter Size value '{0}'. The value must be greater than or equal to 0.. + /// Looks up a localized string similar to Destination array is not long enough to copy all the items in the collection. Check array index and length.. /// - internal static string ADP_InvalidSizeValue { + internal static string Arg_ArrayPlusOffTooSmall { get { - return ResourceManager.GetString("ADP_InvalidSizeValue", resourceCulture); + return ResourceManager.GetString("Arg_ArrayPlusOffTooSmall", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid source buffer (size of {0}) offset: {1}. + /// Looks up a localized string similar to Only single dimensional arrays are supported for the requested action.. /// - internal static string ADP_InvalidSourceBufferIndex { + internal static string Arg_RankMultiDimNotSupported { get { - return ResourceManager.GetString("ADP_InvalidSourceBufferIndex", resourceCulture); + return ResourceManager.GetString("Arg_RankMultiDimNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to SourceColumn is required to be a non-empty string.. + /// Looks up a localized string similar to Non-negative number required.. /// - internal static string ADP_InvalidSourceColumn { + internal static string ArgumentOutOfRange_NeedNonNegNum { get { - return ResourceManager.GetString("ADP_InvalidSourceColumn", resourceCulture); + return ResourceManager.GetString("ArgumentOutOfRange_NeedNonNegNum", resourceCulture); } } /// - /// Looks up a localized string similar to SourceTable is required to be a non-empty string. + /// Looks up a localized string similar to Buffer advanced too far {0}. /// - internal static string ADP_InvalidSourceTable { + internal static string ArrayBufferWriterAdvancedTooFar { get { - return ResourceManager.GetString("ADP_InvalidSourceTable", resourceCulture); + return ResourceManager.GetString("ArrayBufferWriterAdvancedTooFar", resourceCulture); } } /// - /// Looks up a localized string similar to The StartRecord value of {0} is invalid; the value must be >= 0.. + /// Looks up a localized string similar to Buffer maximum size exceeded: {0}. /// - internal static string ADP_InvalidStartRecord { + internal static string ArrayBufferWriterMaximumSizeExceeded { get { - return ResourceManager.GetString("ADP_InvalidStartRecord", resourceCulture); + return ResourceManager.GetString("ArrayBufferWriterMaximumSizeExceeded", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid UDL file.. + /// 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 ADP_InvalidUDL { + internal static string AttestationTokenSignatureValidationFailed { get { - return ResourceManager.GetString("ADP_InvalidUDL", resourceCulture); + return ResourceManager.GetString("AttestationTokenSignatureValidationFailed", resourceCulture); } } /// - /// Looks up a localized string similar to The value contains embedded nulls (\u0000).. + /// Looks up a localized string similar to Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.. /// - internal static string ADP_InvalidValue { + internal static string Data_InvalidOffsetLength { get { - return ResourceManager.GetString("ADP_InvalidValue", resourceCulture); + return ResourceManager.GetString("Data_InvalidOffsetLength", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid Xml; can only parse elements of version one.. + /// Looks up a localized string similar to Advanced. /// - internal static string ADP_InvalidXMLBadVersion { + internal static string DataCategory_Advanced { get { - return ResourceManager.GetString("ADP_InvalidXMLBadVersion", resourceCulture); + return ResourceManager.GetString("DataCategory_Advanced", resourceCulture); } } /// - /// Looks up a localized string similar to Keyword not supported: '{0}'.. + /// Looks up a localized string similar to Connection Resiliency. /// - internal static string ADP_KeywordNotSupported { + internal static string DataCategory_ConnectionResiliency { get { - return ResourceManager.GetString("ADP_KeywordNotSupported", resourceCulture); + return ResourceManager.GetString("DataCategory_ConnectionResiliency", resourceCulture); } } /// - /// Looks up a localized string similar to The literal value provided is not a valid literal for the data type '{0}'.. + /// Looks up a localized string similar to Context. /// - internal static string ADP_LiteralValueIsInvalid { + internal static string DataCategory_Context { get { - return ResourceManager.GetString("ADP_LiteralValueIsInvalid", resourceCulture); + return ResourceManager.GetString("DataCategory_Context", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot enlist in the transaction because a local transaction is in progress on the connection. Finish local transaction and retry.. + /// Looks up a localized string similar to Data. /// - internal static string ADP_LocalTransactionPresent { + internal static string DataCategory_Data { get { - return ResourceManager.GetString("ADP_LocalTransactionPresent", resourceCulture); + return ResourceManager.GetString("DataCategory_Data", resourceCulture); } } /// - /// Looks up a localized string similar to Mismatched end method call for asyncResult. Expected call to {0} but {1} was called instead.. + /// Looks up a localized string similar to Fill. /// - internal static string ADP_MismatchedAsyncResult { + internal static string DataCategory_Fill { get { - return ResourceManager.GetString("ADP_MismatchedAsyncResult", resourceCulture); + return ResourceManager.GetString("DataCategory_Fill", resourceCulture); } } /// - /// Looks up a localized string similar to Missing SourceColumn mapping for '{0}'.. + /// Looks up a localized string similar to InfoMessage. /// - internal static string ADP_MissingColumnMapping { + internal static string DataCategory_InfoMessage { get { - return ResourceManager.GetString("ADP_MissingColumnMapping", resourceCulture); + return ResourceManager.GetString("DataCategory_InfoMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Use of key '{0}' requires the key '{1}' to be present.. + /// Looks up a localized string similar to Initialization. /// - internal static string ADP_MissingConnectionOptionValue { + internal static string DataCategory_Initialization { get { - return ResourceManager.GetString("ADP_MissingConnectionOptionValue", resourceCulture); + return ResourceManager.GetString("DataCategory_Initialization", resourceCulture); } } /// - /// Looks up a localized string similar to DataReader.GetFieldType({0}) returned null.. + /// Looks up a localized string similar to Notification. /// - internal static string ADP_MissingDataReaderFieldType { + internal static string DataCategory_Notification { get { - return ResourceManager.GetString("ADP_MissingDataReaderFieldType", resourceCulture); + return ResourceManager.GetString("DataCategory_Notification", resourceCulture); } } /// - /// Looks up a localized string similar to The SelectCommand property has not been initialized before calling '{0}'.. + /// Looks up a localized string similar to Pooling. /// - internal static string ADP_MissingSelectCommand { + internal static string DataCategory_Pooling { get { - return ResourceManager.GetString("ADP_MissingSelectCommand", resourceCulture); + return ResourceManager.GetString("DataCategory_Pooling", resourceCulture); } } /// - /// Looks up a localized string similar to The DataAdapter.SelectCommand property needs to be initialized.. + /// Looks up a localized string similar to Replication. /// - internal static string ADP_MissingSourceCommand { + internal static string DataCategory_Replication { get { - return ResourceManager.GetString("ADP_MissingSourceCommand", resourceCulture); + return ResourceManager.GetString("DataCategory_Replication", resourceCulture); } } /// - /// Looks up a localized string similar to The DataAdapter.SelectCommand.Connection property needs to be initialized;. + /// Looks up a localized string similar to Security. /// - internal static string ADP_MissingSourceCommandConnection { + internal static string DataCategory_Security { get { - return ResourceManager.GetString("ADP_MissingSourceCommandConnection", resourceCulture); + return ResourceManager.GetString("DataCategory_Security", resourceCulture); } } /// - /// Looks up a localized string similar to Missing SourceTable mapping: '{0}'. + /// Looks up a localized string similar to Source. /// - internal static string ADP_MissingTableMapping { + internal static string DataCategory_Source { get { - return ResourceManager.GetString("ADP_MissingTableMapping", resourceCulture); + return ResourceManager.GetString("DataCategory_Source", resourceCulture); } } /// - /// Looks up a localized string similar to Missing TableMapping when TableMapping.DataSetTable='{0}'.. + /// Looks up a localized string similar to StatementCompleted. /// - internal static string ADP_MissingTableMappingDestination { + internal static string DataCategory_StatementCompleted { get { - return ResourceManager.GetString("ADP_MissingTableMappingDestination", resourceCulture); + return ResourceManager.GetString("DataCategory_StatementCompleted", resourceCulture); } } /// - /// Looks up a localized string similar to Missing the '{0}' DataTable for the '{1}' SourceTable.. + /// Looks up a localized string similar to Update. /// - internal static string ADP_MissingTableSchema { + internal static string DataCategory_Update { get { - return ResourceManager.GetString("ADP_MissingTableSchema", resourceCulture); + return ResourceManager.GetString("DataCategory_Update", resourceCulture); } } /// - /// Looks up a localized string similar to Multiple return value parameters are not supported.. + /// Looks up a localized string similar to XML. /// - internal static string ADP_MultipleReturnValue { + internal static string DataCategory_Xml { get { - return ResourceManager.GetString("ADP_MultipleReturnValue", resourceCulture); + return ResourceManager.GetString("DataCategory_Xml", resourceCulture); } } /// - /// Looks up a localized string similar to {0} must be marked as read only.. + /// Looks up a localized string similar to Command text to execute.. /// - internal static string ADP_MustBeReadOnly { + internal static string DbCommand_CommandText { get { - return ResourceManager.GetString("ADP_MustBeReadOnly", resourceCulture); + return ResourceManager.GetString("DbCommand_CommandText", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid value for argument '{0}'. The value must be greater than or equal to 0.. + /// Looks up a localized string similar to Time to wait for command to execute.. /// - internal static string ADP_NegativeParameter { + internal static string DbCommand_CommandTimeout { get { - return ResourceManager.GetString("ADP_NegativeParameter", resourceCulture); + return ResourceManager.GetString("DbCommand_CommandTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to The ConnectionString property has not been initialized.. + /// Looks up a localized string similar to How to interpret the CommandText.. /// - internal static string ADP_NoConnectionString { + internal static string DbCommand_CommandType { get { - return ResourceManager.GetString("ADP_NoConnectionString", resourceCulture); + return ResourceManager.GetString("DbCommand_CommandType", resourceCulture); } } /// - /// Looks up a localized string similar to A Non CLS Exception was caught.. + /// Looks up a localized string similar to Connection used by the command.. /// - internal static string ADP_NonCLSException { + internal static string DbCommand_Connection { get { - return ResourceManager.GetString("ADP_NonCLSException", resourceCulture); + return ResourceManager.GetString("DbCommand_Connection", resourceCulture); } } /// - /// Looks up a localized string similar to Timeout attempting to open the connection. The time period elapsed prior to attempting to open the connection has been exceeded. This may have occurred because of too many simultaneous non-pooled connection attempts.. + /// Looks up a localized string similar to The parameters collection.. /// - internal static string ADP_NonPooledOpenTimeout { + internal static string DbCommand_Parameters { get { - return ResourceManager.GetString("ADP_NonPooledOpenTimeout", resourceCulture); + return ResourceManager.GetString("DbCommand_Parameters", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid {2} attempt at dataIndex '{0}'. With CommandBehavior.SequentialAccess, you may only read from dataIndex '{1}' or greater.. + /// Looks up a localized string similar to When records are affected by a given statement by the execution of the command.. /// - internal static string ADP_NonSeqByteAccess { + internal static string DbCommand_StatementCompleted { get { - return ResourceManager.GetString("ADP_NonSeqByteAccess", resourceCulture); + return ResourceManager.GetString("DbCommand_StatementCompleted", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid attempt to read from column ordinal '{0}'. With CommandBehavior.SequentialAccess, you may only read from column ordinal '{1}' or greater.. + /// Looks up a localized string similar to The transaction used by the command.. /// - internal static string ADP_NonSequentialColumnAccess { + internal static string DbCommand_Transaction { get { - return ResourceManager.GetString("ADP_NonSequentialColumnAccess", resourceCulture); + return ResourceManager.GetString("DbCommand_Transaction", resourceCulture); } } /// - /// Looks up a localized string similar to The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.. + /// Looks up a localized string similar to When used by a DataAdapter.Update, how command results are applied to the current DataRow.. /// - internal static string ADP_NoQuoteChange { + internal static string DbCommand_UpdatedRowSource { get { - return ResourceManager.GetString("ADP_NoQuoteChange", resourceCulture); + return ResourceManager.GetString("DbCommand_UpdatedRowSource", resourceCulture); } } - + /// - /// Looks up a localized string similar to SqlBatchCommand list has not been initialized.. + /// Looks up a localized string similar to Event triggered when messages arrive from the DataSource.. /// - internal static string ADP_NoSqlBatchCommandList - { - get - { - return ResourceManager.GetString("ADP_NoSqlBatchCommandList", resourceCulture); + internal static string DbConnection_InfoMessage { + get { + return ResourceManager.GetString("DbConnection_InfoMessage", resourceCulture); } } - + /// - /// Looks up a localized string similar to The stored procedure '{0}' doesn't exist.. + /// Looks up a localized string similar to The ConnectionState indicating whether the connection is open or closed.. /// - internal static string ADP_NoStoredProcedureExists { + internal static string DbConnection_State { get { - return ResourceManager.GetString("ADP_NoStoredProcedureExists", resourceCulture); + return ResourceManager.GetString("DbConnection_State", resourceCulture); } } /// - /// Looks up a localized string similar to Given security element is not a permission element.. + /// Looks up a localized string similar to Declares the application workload type when connecting to a server.. /// - internal static string ADP_NotAPermissionElement { + internal static string DbConnectionString_ApplicationIntent { get { - return ResourceManager.GetString("ADP_NotAPermissionElement", resourceCulture); + return ResourceManager.GetString("DbConnectionString_ApplicationIntent", resourceCulture); } } /// - /// Looks up a localized string similar to Metadata must be SqlDbType.Row. + /// Looks up a localized string similar to The name of the application.. /// - internal static string ADP_NotRowType { + internal static string DbConnectionString_ApplicationName { get { - return ResourceManager.GetString("ADP_NotRowType", resourceCulture); + return ResourceManager.GetString("DbConnectionString_ApplicationName", resourceCulture); } } /// - /// Looks up a localized string similar to The {0} enumeration value, {1}, is not supported by the {2} method.. + /// Looks up a localized string similar to The name of the primary file, including the full path name, of an attachable database.. /// - internal static string ADP_NotSupportedEnumerationValue { + internal static string DbConnectionString_AttachDBFilename { get { - return ResourceManager.GetString("ADP_NotSupportedEnumerationValue", resourceCulture); + return ResourceManager.GetString("DbConnectionString_AttachDBFilename", resourceCulture); } } /// - /// Looks up a localized string similar to Unexpected null DataSet argument.. + /// Looks up a localized string similar to Specifies the method of authenticating with SQL Server.. /// - internal static string ADP_NullDataSet { + internal static string DbConnectionString_Authentication { get { - return ResourceManager.GetString("ADP_NullDataSet", resourceCulture); + return ResourceManager.GetString("DbConnectionString_Authentication", resourceCulture); } } /// - /// Looks up a localized string similar to Unexpected null DataTable argument. + /// Looks up a localized string similar to When true, indicates the connection state is reset when removed from the pool.. /// - internal static string ADP_NullDataTable { + internal static string DbConnectionString_ConnectionReset { get { - return ResourceManager.GetString("ADP_NullDataTable", resourceCulture); + return ResourceManager.GetString("DbConnectionString_ConnectionReset", resourceCulture); } } /// - /// Looks up a localized string similar to The numerical value is too large to fit into a 96 bit decimal.. + /// Looks up a localized string similar to Number of attempts to restore connection.. /// - internal static string ADP_NumericToDecimalOverflow { + internal static string DbConnectionString_ConnectRetryCount { get { - return ResourceManager.GetString("ADP_NumericToDecimalOverflow", resourceCulture); + return ResourceManager.GetString("DbConnectionString_ConnectRetryCount", resourceCulture); } } /// - /// Looks up a localized string similar to The '{0}' keyword is obsolete. Use '{1}' instead.. + /// Looks up a localized string similar to Delay between attempts to restore connection.. /// - internal static string ADP_ObsoleteKeyword { + internal static string DbConnectionString_ConnectRetryInterval { get { - return ResourceManager.GetString("ADP_ObsoleteKeyword", resourceCulture); + return ResourceManager.GetString("DbConnectionString_ConnectRetryInterval", resourceCulture); } } /// - /// Looks up a localized string similar to The ODBC provider did not return results from SQLGETTYPEINFO.. + /// Looks up a localized string similar to The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.. /// - internal static string ADP_OdbcNoTypesFromProvider { + internal static string DbConnectionString_ConnectTimeout { get { - return ResourceManager.GetString("ADP_OdbcNoTypesFromProvider", resourceCulture); + return ResourceManager.GetString("DbConnectionString_ConnectTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to Offset must refer to a location within the value.. + /// Looks up a localized string similar to The SQL Server Language record name.. /// - internal static string ADP_OffsetOutOfRangeException { + internal static string DbConnectionString_CurrentLanguage { get { - return ResourceManager.GetString("ADP_OffsetOutOfRangeException", resourceCulture); + return ResourceManager.GetString("DbConnectionString_CurrentLanguage", resourceCulture); } } /// - /// Looks up a localized string similar to Only specify one item in the dataTables array when using non-zero values for startRecords or maxRecords.. + /// Looks up a localized string similar to Indicates the name of the data source to connect to.. /// - internal static string ADP_OnlyOneTableForStartRecordOrMaxRecords { + internal static string DbConnectionString_DataSource { get { - return ResourceManager.GetString("ADP_OnlyOneTableForStartRecordOrMaxRecords", resourceCulture); + return ResourceManager.GetString("DbConnectionString_DataSource", resourceCulture); } } /// - /// Looks up a localized string similar to Not allowed to change the '{0}' property. {1}. + /// Looks up a localized string similar to When true, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.. /// - internal static string ADP_OpenConnectionPropertySet { + internal static string DbConnectionString_Encrypt { get { - return ResourceManager.GetString("ADP_OpenConnectionPropertySet", resourceCulture); + return ResourceManager.GetString("DbConnectionString_Encrypt", resourceCulture); } } /// - /// Looks up a localized string similar to {0} requires an open and available Connection. {1}. + /// Looks up a localized string similar to Sessions in a Component Services (or MTS, if you are using Microsoft Windows NT) environment should automatically be enlisted in a global transaction where required.. /// - internal static string ADP_OpenConnectionRequired { + internal static string DbConnectionString_Enlist { get { - return ResourceManager.GetString("ADP_OpenConnectionRequired", resourceCulture); + return ResourceManager.GetString("DbConnectionString_Enlist", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the updating command to have an open connection object. {1}. + /// Looks up a localized string similar to The name or network address of the instance of SQL Server that acts as a failover partner.. /// - internal static string ADP_OpenConnectionRequired_Clone { + internal static string DbConnectionString_FailoverPartner { get { - return ResourceManager.GetString("ADP_OpenConnectionRequired_Clone", resourceCulture); + return ResourceManager.GetString("DbConnectionString_FailoverPartner", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the {0}Command to have an open connection object. {1}. + /// Looks up a localized string similar to The service principal name (SPN) of the failover partner.. /// - internal static string ADP_OpenConnectionRequired_Delete { + internal static string DbConnectionString_FailoverPartnerSPN { get { - return ResourceManager.GetString("ADP_OpenConnectionRequired_Delete", resourceCulture); + return ResourceManager.GetString("DbConnectionString_FailoverPartnerSPN", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the {0}Command to have an open connection object. {1}. + /// Looks up a localized string similar to The hostname to be expected in the server's certificate when encryption is negotiated, if it's different from the default value derived from Addr/Address/Server.. /// - internal static string ADP_OpenConnectionRequired_Insert { + internal static string DbConnectionString_HostNameInCertificate { get { - return ResourceManager.GetString("ADP_OpenConnectionRequired_Insert", resourceCulture); + return ResourceManager.GetString("DbConnectionString_HostNameInCertificate", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the {0}Command to have an open connection object. {1}. + /// Looks up a localized string similar to The name of the initial catalog or database in the data source.. /// - internal static string ADP_OpenConnectionRequired_Update { + internal static string DbConnectionString_InitialCatalog { get { - return ResourceManager.GetString("ADP_OpenConnectionRequired_Update", resourceCulture); + return ResourceManager.GetString("DbConnectionString_InitialCatalog", resourceCulture); } } /// - /// Looks up a localized string similar to There is already an open DataReader associated with this {0} which must be closed first.. + /// Looks up a localized string similar to Whether the connection is to be a secure connection or not.. /// - internal static string ADP_OpenReaderExists { + internal static string DbConnectionString_IntegratedSecurity { get { - return ResourceManager.GetString("ADP_OpenReaderExists", resourceCulture); + return ResourceManager.GetString("DbConnectionString_IntegratedSecurity", resourceCulture); } } /// - /// Looks up a localized string similar to There is already an open SqlResultSet associated with this command which must be closed first.. + /// Looks up a localized string similar to The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed.. /// - internal static string ADP_OpenResultSetExists { + internal static string DbConnectionString_LoadBalanceTimeout { get { - return ResourceManager.GetString("ADP_OpenResultSetExists", resourceCulture); + return ResourceManager.GetString("DbConnectionString_LoadBalanceTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to Operation aborted.. + /// Looks up a localized string similar to The maximum number of connections allowed in the pool.. /// - internal static string ADP_OperationAborted { + internal static string DbConnectionString_MaxPoolSize { get { - return ResourceManager.GetString("ADP_OperationAborted", resourceCulture); + return ResourceManager.GetString("DbConnectionString_MaxPoolSize", resourceCulture); } } /// - /// Looks up a localized string similar to Operation aborted due to an exception (see InnerException for details).. + /// Looks up a localized string similar to The minimum number of connections allowed in the pool.. /// - internal static string ADP_OperationAbortedExceptionMessage { + internal static string DbConnectionString_MinPoolSize { get { - return ResourceManager.GetString("ADP_OperationAbortedExceptionMessage", resourceCulture); + return ResourceManager.GetString("DbConnectionString_MinPoolSize", resourceCulture); } } /// - /// Looks up a localized string similar to {0} does not support parallel transactions.. + /// Looks up a localized string similar to When true, multiple result sets can be returned and read from one connection.. /// - internal static string ADP_ParallelTransactionsNotSupported { + internal static string DbConnectionString_MultipleActiveResultSets { get { - return ResourceManager.GetString("ADP_ParallelTransactionsNotSupported", resourceCulture); + return ResourceManager.GetString("DbConnectionString_MultipleActiveResultSets", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to convert parameter value from a {0} to a {1}.. + /// Looks up a localized string similar to If your application is connecting to a high-availability, disaster recovery (AlwaysOn) availability group (AG) on different subnets, MultiSubnetFailover=Yes configures SqlConnection to provide faster detection of and connection to the (currently) active server.. /// - internal static string ADP_ParameterConversionFailed { + internal static string DbConnectionString_MultiSubnetFailover { get { - return ResourceManager.GetString("ADP_ParameterConversionFailed", resourceCulture); + return ResourceManager.GetString("DbConnectionString_MultiSubnetFailover", resourceCulture); } } /// - /// Looks up a localized string similar to Parameter value '{0}' is out of range.. + /// Looks up a localized string similar to The network library used to establish a connection to an instance of SQL Server.. /// - internal static string ADP_ParameterValueOutOfRange { + internal static string DbConnectionString_NetworkLibrary { get { - return ResourceManager.GetString("ADP_ParameterValueOutOfRange", resourceCulture); + return ResourceManager.GetString("DbConnectionString_NetworkLibrary", resourceCulture); } } /// - /// Looks up a localized string similar to Can not start another operation while there is an asynchronous operation pending.. + /// Looks up a localized string similar to Size in bytes of the network packets used to communicate with an instance of SQL Server.. /// - internal static string ADP_PendingAsyncOperation { + internal static string DbConnectionString_PacketSize { get { - return ResourceManager.GetString("ADP_PendingAsyncOperation", resourceCulture); + return ResourceManager.GetString("DbConnectionString_PacketSize", resourceCulture); } } /// - /// Looks up a localized string similar to Type mismatch.. + /// Looks up a localized string similar to Indicates the password to be used when connecting to the data source.. /// - internal static string ADP_PermissionTypeMismatch { + internal static string DbConnectionString_Password { get { - return ResourceManager.GetString("ADP_PermissionTypeMismatch", resourceCulture); + return ResourceManager.GetString("DbConnectionString_Password", resourceCulture); } } /// - /// Looks up a localized string similar to Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.. + /// Looks up a localized string similar to When false, security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state.. /// - internal static string ADP_PooledOpenTimeout { + internal static string DbConnectionString_PersistSecurityInfo { get { - return ResourceManager.GetString("ADP_PooledOpenTimeout", resourceCulture); + return ResourceManager.GetString("DbConnectionString_PersistSecurityInfo", resourceCulture); } } /// - /// Looks up a localized string similar to {0}.Prepare method requires parameters of type '{1}' have an explicitly set Precision and Scale.. + /// Looks up a localized string similar to Defines the blocking period behavior for a connection pool.. /// - internal static string ADP_PrepareParameterScale { + internal static string DbConnectionString_PoolBlockingPeriod { get { - return ResourceManager.GetString("ADP_PrepareParameterScale", resourceCulture); + return ResourceManager.GetString("DbConnectionString_PoolBlockingPeriod", resourceCulture); } } /// - /// Looks up a localized string similar to {0}.Prepare method requires all variable length parameters to have an explicitly set non-zero Size.. + /// Looks up a localized string similar to When true, the connection object is drawn from the appropriate pool, or if necessary, is created and added to the appropriate pool.. /// - internal static string ADP_PrepareParameterSize { + internal static string DbConnectionString_Pooling { get { - return ResourceManager.GetString("ADP_PrepareParameterSize", resourceCulture); + return ResourceManager.GetString("DbConnectionString_Pooling", resourceCulture); } } /// - /// Looks up a localized string similar to {0}.Prepare method requires all parameters to have an explicitly set type.. + /// Looks up a localized string similar to Used by SQL Server in Replication.. /// - internal static string ADP_PrepareParameterType { + internal static string DbConnectionString_Replication { get { - return ResourceManager.GetString("ADP_PrepareParameterType", resourceCulture); + return ResourceManager.GetString("DbConnectionString_Replication", resourceCulture); } } /// - /// Looks up a localized string similar to The '{0}' property requires Microsoft WindowsNT or a WindowsNT based operating system.. + /// Looks up a localized string similar to The path to a certificate file to match against the SQL Server TLS/SSL certificate.. /// - internal static string ADP_PropertyNotSupported { + internal static string DbConnectionString_ServerCertificate { get { - return ResourceManager.GetString("ADP_PropertyNotSupported", resourceCulture); + return ResourceManager.GetString("DbConnectionString_ServerCertificate", resourceCulture); } } /// - /// Looks up a localized string similar to {0} requires open connection when the quote prefix has not been set.. + /// Looks up a localized string similar to The service principal name (SPN) of the server.. /// - internal static string ADP_QuotePrefixNotSet { + internal static string DbConnectionString_ServerSPN { get { - return ResourceManager.GetString("ADP_QuotePrefixNotSet", resourceCulture); + return ResourceManager.GetString("DbConnectionString_ServerSPN", resourceCulture); } } /// - /// Looks up a localized string similar to When batching, the command's UpdatedRowSource property value of UpdateRowSource.FirstReturnedRecord or UpdateRowSource.Both is invalid.. + /// Looks up a localized string similar to Indicates binding behavior of connection to a System.Transactions Transaction when enlisted.. /// - internal static string ADP_ResultsNotAllowedDuringBatch { + internal static string DbConnectionString_TransactionBinding { get { - return ResourceManager.GetString("ADP_ResultsNotAllowedDuringBatch", resourceCulture); + return ResourceManager.GetString("DbConnectionString_TransactionBinding", resourceCulture); } } /// - /// Looks up a localized string similar to RowUpdatedEvent: Errors occurred; no additional is information available.. + /// Looks up a localized string similar to If your application connects to different networks, TransparentNetworkIPResolution=Yes configures SqlConnection to provide transparent connection resolution to the currently active server, independently of the network IP topology.. /// - internal static string ADP_RowUpdatedErrors { + internal static string DbConnectionString_TransparentNetworkIPResolution { get { - return ResourceManager.GetString("ADP_RowUpdatedErrors", resourceCulture); + return ResourceManager.GetString("DbConnectionString_TransparentNetworkIPResolution", resourceCulture); } } /// - /// Looks up a localized string similar to RowUpdatingEvent: Errors occurred; no additional is information available.. + /// Looks up a localized string similar to When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate.. /// - internal static string ADP_RowUpdatingErrors { + internal static string DbConnectionString_TrustServerCertificate { get { - return ResourceManager.GetString("ADP_RowUpdatingErrors", resourceCulture); + return ResourceManager.GetString("DbConnectionString_TrustServerCertificate", resourceCulture); } } /// - /// Looks up a localized string similar to The only acceptable value for the property '{0}' is '{1}'.. + /// Looks up a localized string similar to Indicates which server type system the provider will expose through the DataReader.. /// - internal static string ADP_SingleValuedProperty { + internal static string DbConnectionString_TypeSystemVersion { get { - return ResourceManager.GetString("ADP_SingleValuedProperty", resourceCulture); + return ResourceManager.GetString("DbConnectionString_TypeSystemVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid attempt to {0} when stream is closed.. + /// Looks up a localized string similar to Indicates the user ID to be used when connecting to the data source.. /// - internal static string ADP_StreamClosed { + internal static string DbConnectionString_UserID { get { - return ResourceManager.GetString("ADP_StreamClosed", resourceCulture); + return ResourceManager.GetString("DbConnectionString_UserID", resourceCulture); } } /// - /// Looks up a localized string similar to The transaction assigned to this command must be the most nested pending local transaction.. + /// Looks up a localized string similar to Indicates whether the connection will be re-directed to connect to an instance of SQL Server running under the user's account.. /// - internal static string ADP_TransactionCompleted { + internal static string DbConnectionString_UserInstance { get { - return ResourceManager.GetString("ADP_TransactionCompleted", resourceCulture); + return ResourceManager.GetString("DbConnectionString_UserInstance", resourceCulture); } } /// - /// Looks up a localized string similar to The transaction associated with the current connection has completed but has not been disposed. The transaction must be disposed before the connection can be used to execute SQL statements.. + /// Looks up a localized string similar to The name of the workstation connecting to SQL Server.. /// - internal static string ADP_TransactionCompletedButNotDisposed { + internal static string DbConnectionString_WorkstationID { get { - return ResourceManager.GetString("ADP_TransactionCompletedButNotDisposed", resourceCulture); + return ResourceManager.GetString("DbConnectionString_WorkstationID", resourceCulture); } } /// - /// Looks up a localized string similar to The transaction is either not associated with the current connection or has been completed.. + /// Looks up a localized string similar to Used during Update for deleted rows in DataSet.. /// - internal static string ADP_TransactionConnectionMismatch { + internal static string DbDataAdapter_DeleteCommand { get { - return ResourceManager.GetString("ADP_TransactionConnectionMismatch", resourceCulture); + return ResourceManager.GetString("DbDataAdapter_DeleteCommand", resourceCulture); } } /// - /// Looks up a localized string similar to Connection currently has transaction enlisted. Finish current transaction and retry.. + /// Looks up a localized string similar to Used during Update for new rows in DataSet.. /// - internal static string ADP_TransactionPresent { + internal static string DbDataAdapter_InsertCommand { get { - return ResourceManager.GetString("ADP_TransactionPresent", resourceCulture); + return ResourceManager.GetString("DbDataAdapter_InsertCommand", resourceCulture); } } /// - /// Looks up a localized string similar to {0} requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized.. + /// Looks up a localized string similar to Event triggered before every DataRow during Update.. /// - internal static string ADP_TransactionRequired { + internal static string DbDataAdapter_RowUpdated { get { - return ResourceManager.GetString("ADP_TransactionRequired", resourceCulture); + return ResourceManager.GetString("DbDataAdapter_RowUpdated", resourceCulture); } } /// - /// Looks up a localized string similar to This {0} has completed; it is no longer usable.. + /// Looks up a localized string similar to Event triggered after every DataRow during Update.. /// - internal static string ADP_TransactionZombied { + internal static string DbDataAdapter_RowUpdating { get { - return ResourceManager.GetString("ADP_TransactionZombied", resourceCulture); + return ResourceManager.GetString("DbDataAdapter_RowUpdating", resourceCulture); } } /// - /// Looks up a localized string similar to Unable to load the UDL file.. + /// Looks up a localized string similar to Used during Fill/FillSchema.. /// - internal static string ADP_UdlFileError { + internal static string DbDataAdapter_SelectCommand { get { - return ResourceManager.GetString("ADP_UdlFileError", resourceCulture); + return ResourceManager.GetString("DbDataAdapter_SelectCommand", resourceCulture); } } /// - /// Looks up a localized string similar to Can not determine the correct boolean literal values. Boolean literals can not be created.. + /// Looks up a localized string similar to Used during Update for modified rows in DataSet.. /// - internal static string ADP_UnableToCreateBooleanLiteral { + internal static string DbDataAdapter_UpdateCommand { get { - return ResourceManager.GetString("ADP_UnableToCreateBooleanLiteral", resourceCulture); + return ResourceManager.GetString("DbDataAdapter_UpdateCommand", resourceCulture); } } /// - /// Looks up a localized string similar to {1}[{0}]: the Size property has an invalid size of 0.. + /// Looks up a localized string similar to Internal error occurred when retrying the download of the HGS root certificate after the initial request failed. Contact Customer Support Services.. /// - internal static string ADP_UninitializedParameterSize { + internal static string EnclaveRetrySleepInSecondsValueException { get { - return ResourceManager.GetString("ADP_UninitializedParameterSize", resourceCulture); + return ResourceManager.GetString("EnclaveRetrySleepInSecondsValueException", resourceCulture); } } /// - /// Looks up a localized string similar to No mapping exists from object type {0} to a known managed provider native type.. + /// Looks up a localized string similar to Internal error. Unable to invalidate the requested enclave session, because it does not exist in the cache. Contact Customer Support Services.. /// - internal static string ADP_UnknownDataType { + internal static string EnclaveSessionInvalidationFailed { get { - return ResourceManager.GetString("ADP_UnknownDataType", resourceCulture); + return ResourceManager.GetString("EnclaveSessionInvalidationFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Unable to handle an unknown TypeCode {0} returned by Type {1}.. + /// Looks up a localized string similar to The validation of an attestation token failed. The token received from SQL Server is expired. Contact Customer Support Services.. /// - internal static string ADP_UnknownDataTypeCode { + internal static string ExpiredAttestationToken { get { - return ResourceManager.GetString("ADP_UnknownDataTypeCode", resourceCulture); + return ResourceManager.GetString("ExpiredAttestationToken", resourceCulture); } } /// - /// Looks up a localized string similar to Literals of the native data type associated with data type '{0}' are not supported.. + /// Looks up a localized string similar to Failed to create enclave session as attestation server is busy.. /// - internal static string ADP_UnsupportedNativeDataTypeOleDb { + internal static string FailToCreateEnclaveSession { get { - return ResourceManager.GetString("ADP_UnsupportedNativeDataTypeOleDb", resourceCulture); + return ResourceManager.GetString("FailToCreateEnclaveSession", resourceCulture); } } /// - /// Looks up a localized string similar to The StatementType {0} is not expected here.. + /// Looks up a localized string similar to The validation of an attestation information failed. The attestation information has an invalid format. Contact Customer Support Services. Error details: '{0}'.. /// - internal static string ADP_UnwantedStatementType { + internal static string FailToParseAttestationInfo { get { - return ResourceManager.GetString("ADP_UnwantedStatementType", resourceCulture); + return ResourceManager.GetString("FailToParseAttestationInfo", resourceCulture); } } /// - /// Looks up a localized string similar to Concurrency violation: the batched command affected {0} of the expected {1} records.. + /// Looks up a localized string similar to The validation of an attestation token failed. The token has an invalid format. Contact Customer Support Services. Error details: '{0}'.. /// - internal static string ADP_UpdateConcurrencyViolation_Batch { + internal static string FailToParseAttestationToken { get { - return ResourceManager.GetString("ADP_UpdateConcurrencyViolation_Batch", resourceCulture); + return ResourceManager.GetString("FailToParseAttestationToken", resourceCulture); } } /// - /// Looks up a localized string similar to Concurrency violation: the DeleteCommand affected {0} of the expected {1} records.. + /// Looks up a localized string similar to The attestation service returned an expired HGS root certificate for attestation URL '{0}'. Check the HGS root certificate configured for your HGS instance - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details.. /// - internal static string ADP_UpdateConcurrencyViolation_Delete { + internal static string GetAttestationSigningCertificateFailedInvalidCertificate { get { - return ResourceManager.GetString("ADP_UpdateConcurrencyViolation_Delete", resourceCulture); + return ResourceManager.GetString("GetAttestationSigningCertificateFailedInvalidCertificate", resourceCulture); } } /// - /// Looks up a localized string similar to Concurrency violation: the UpdateCommand affected {0} of the expected {1} records.. + /// Looks up a localized string similar to The obtained HGS root certificate for attestation URL '{0}' has an invalid format. Verify the attestation URL is correct and the HGS server is online and fully initialized - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details. For additional support contact Customer Support Services. Error details: '{1}'.. /// - internal static string ADP_UpdateConcurrencyViolation_Update { + internal static string GetAttestationSigningCertificateRequestFailedFormat { get { - return ResourceManager.GetString("ADP_UpdateConcurrencyViolation_Update", resourceCulture); + return ResourceManager.GetString("GetAttestationSigningCertificateRequestFailedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to DataRow[{0}] is from a different DataTable than DataRow[0].. + /// Looks up a localized string similar to The validation of an attestation token failed. Cannot retrieve a public key from the attestation public key endpoint, or the retrieved key has an invalid format. Error details: '{0}'.. /// - internal static string ADP_UpdateMismatchRowTable { + internal static string GetAttestationTokenSigningKeysFailed { get { - return ResourceManager.GetString("ADP_UpdateMismatchRowTable", resourceCulture); + return ResourceManager.GetString("GetAttestationTokenSigningKeysFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires the command clone to be valid.. + /// Looks up a localized string similar to Signature verification of the enclave's Diffie-Hellman key failed. Contact Customer Support Services.. /// - internal static string ADP_UpdateRequiresCommandClone { + internal static string GetSharedSecretFailed { get { - return ResourceManager.GetString("ADP_UpdateRequiresCommandClone", resourceCulture); + return ResourceManager.GetString("GetSharedSecretFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires a valid DeleteCommand when passed DataRow collection with deleted rows.. + /// Looks up a localized string similar to Global Transactions are not enabled for this Azure SQL Database. Please contact Azure SQL Database support for assistance.. /// - internal static string ADP_UpdateRequiresCommandDelete { + internal static string GT_Disabled { get { - return ResourceManager.GetString("ADP_UpdateRequiresCommandDelete", resourceCulture); + return ResourceManager.GetString("GT_Disabled", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires a valid InsertCommand when passed DataRow collection with new rows.. + /// Looks up a localized string similar to The currently loaded System.Transactions.dll does not support Global Transactions. Please upgrade to .NET Framework 4.6.2 or later.. /// - internal static string ADP_UpdateRequiresCommandInsert { + internal static string GT_UnsupportedSysTxVersion { get { - return ResourceManager.GetString("ADP_UpdateRequiresCommandInsert", resourceCulture); + return ResourceManager.GetString("GT_UnsupportedSysTxVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Auto SQL generation during Update requires a valid SelectCommand.. + /// Looks up a localized string similar to There are no records in the SqlDataRecord enumeration. To send a table-valued parameter with no rows, use a null reference for the value instead.. /// - internal static string ADP_UpdateRequiresCommandSelect { + internal static string IEnumerableOfSqlDataRecordHasNoRows { get { - return ResourceManager.GetString("ADP_UpdateRequiresCommandSelect", resourceCulture); + return ResourceManager.GetString("IEnumerableOfSqlDataRecordHasNoRows", resourceCulture); } } /// - /// Looks up a localized string similar to Update requires a valid UpdateCommand when passed DataRow collection with modified rows.. + /// Looks up a localized string similar to The validation of an attestation token failed due to an error while decoding the enclave public key obtained from SQL Server. Contact Customer Support Services.. /// - internal static string ADP_UpdateRequiresCommandUpdate { + internal static string InvalidArgumentToBase64UrlDecoder { get { - return ResourceManager.GetString("ADP_UpdateRequiresCommandUpdate", resourceCulture); + return ResourceManager.GetString("InvalidArgumentToBase64UrlDecoder", resourceCulture); } } /// - /// Looks up a localized string similar to Update unable to find TableMapping['{0}'] or DataTable '{0}'.. + /// Looks up a localized string similar to The validation of an attestation token failed due to an error while computing a hash of the enclave public key obtained from SQL Server. Contact Customer Support Services.. /// - internal static string ADP_UpdateRequiresSourceTable { + internal static string InvalidArgumentToSHA256 { get { - return ResourceManager.GetString("ADP_UpdateRequiresSourceTable", resourceCulture); + return ResourceManager.GetString("InvalidArgumentToSHA256", resourceCulture); } } /// - /// Looks up a localized string similar to Update: expected a non-empty SourceTable name.. + /// Looks up a localized string similar to The validation of the attestation token has failed during signature validation. Exception: '{0}'.. /// - internal static string ADP_UpdateRequiresSourceTableName { + internal static string InvalidAttestationToken { get { - return ResourceManager.GetString("ADP_UpdateRequiresSourceTableName", resourceCulture); + return ResourceManager.GetString("InvalidAttestationToken", resourceCulture); } } /// - /// Looks up a localized string similar to The version of SQL Server in use does not support datatype '{0}'.. + /// Looks up a localized string similar to The validation of an attestation token failed. Claim '{0}' in the token has an invalid value of '{1}'. Verify the attestation policy - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details. If the policy is correct, contact Customer Support Services.. /// - internal static string ADP_VersionDoesNotSupportDataType { + internal static string InvalidClaimInAttestationToken { get { - return ResourceManager.GetString("ADP_VersionDoesNotSupportDataType", resourceCulture); + return ResourceManager.GetString("InvalidClaimInAttestationToken", resourceCulture); } } /// - /// Looks up a localized string similar to Destination array is not long enough to copy all the items in the collection. Check array index and length.. + /// Looks up a localized string similar to Invalid column ordinals in schema table. ColumnOrdinals, if present, must not have duplicates or gaps.. /// - internal static string Arg_ArrayPlusOffTooSmall { + internal static string InvalidSchemaTableOrdinals { get { - return ResourceManager.GetString("Arg_ArrayPlusOffTooSmall", resourceCulture); + return ResourceManager.GetString("InvalidSchemaTableOrdinals", resourceCulture); } } /// - /// Looks up a localized string similar to Only single dimensional arrays are supported for the requested action.. + /// Looks up a localized string similar to Local Database Runtime: system.data.localdb configuration file section is of unknown type.. /// - internal static string Arg_RankMultiDimNotSupported { + internal static string LocalDB_BadConfigSectionType { get { - return ResourceManager.GetString("Arg_RankMultiDimNotSupported", resourceCulture); + return ResourceManager.GetString("LocalDB_BadConfigSectionType", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove the specified item because it was not found in the specified Collection.. + /// Looks up a localized string similar to Local Database Runtime: Cannot create named instance.. /// - internal static string Arg_RemoveArgNotFound { + internal static string LocalDB_CreateFailed { get { - return ResourceManager.GetString("Arg_RemoveArgNotFound", resourceCulture); + return ResourceManager.GetString("LocalDB_CreateFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Non-negative number required.. + /// Looks up a localized string similar to Local Database Runtime: Cannot load SQLUserInstance.dll.. /// - internal static string ArgumentOutOfRange_NeedNonNegNum { + internal static string LocalDB_FailedGetDLLHandle { get { - return ResourceManager.GetString("ArgumentOutOfRange_NeedNonNegNum", resourceCulture); + return ResourceManager.GetString("LocalDB_FailedGetDLLHandle", resourceCulture); } } /// - /// Looks up a localized string similar to Buffer advanced too far {0}. + /// Looks up a localized string similar to Local Database Runtime: Invalid instance version specification found in the configuration file.. /// - internal static string ArrayBufferWriterAdvancedTooFar { + internal static string LocalDB_InvalidVersion { get { - return ResourceManager.GetString("ArrayBufferWriterAdvancedTooFar", resourceCulture); + return ResourceManager.GetString("LocalDB_InvalidVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Buffer maximum size exceeded: {0}. + /// Looks up a localized string similar to Invalid SQLUserInstance.dll found at the location specified in the registry. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. /// - internal static string ArrayBufferWriterMaximumSizeExceeded { + internal static string LocalDB_MethodNotFound { get { - return ResourceManager.GetString("ArrayBufferWriterMaximumSizeExceeded", resourceCulture); + return ResourceManager.GetString("LocalDB_MethodNotFound", resourceCulture); } } /// - /// 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 Cannot obtain Local Database Runtime error message. /// - internal static string AttestationTokenSignatureValidationFailed { + internal static string LocalDB_UnobtainableMessage { get { - return ResourceManager.GetString("AttestationTokenSignatureValidationFailed", resourceCulture); + return ResourceManager.GetString("LocalDB_UnobtainableMessage", resourceCulture); } } /// - /// Looks up a localized string similar to There is more than one table with the same name '{0}' (even if namespace is different).. + /// Looks up a localized string similar to LocalDB is not supported on this platform.. /// - internal static string CodeGen_DuplicateTableName { + internal static string LocalDBNotSupported { get { - return ResourceManager.GetString("CodeGen_DuplicateTableName", resourceCulture); + return ResourceManager.GetString("LocalDBNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot generate identifier for name '{0}'.. + /// Looks up a localized string similar to The collection name '{0}' matches at least two collections with the same name but with different case, but does not match any of them exactly.. /// - internal static string CodeGen_InvalidIdentifier { + internal static string MDF_AmbiguousCollectionName { get { - return ResourceManager.GetString("CodeGen_InvalidIdentifier", resourceCulture); + return ResourceManager.GetString("MDF_AmbiguousCollectionName", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}': Type '{1}' does not have parameterless constructor.. + /// Looks up a localized string similar to There are multiple collections named '{0}'.. /// - internal static string CodeGen_NoCtor0 { + internal static string MDF_CollectionNameISNotUnique { get { - return ResourceManager.GetString("CodeGen_NoCtor0", resourceCulture); + return ResourceManager.GetString("MDF_CollectionNameISNotUnique", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}': Type '{1}' does not have constructor with string argument.. + /// Looks up a localized string similar to The collection '{0}' is missing from the metadata XML.. /// - internal static string CodeGen_NoCtor1 { + internal static string MDF_DataTableDoesNotExist { get { - return ResourceManager.GetString("CodeGen_NoCtor1", resourceCulture); + return ResourceManager.GetString("MDF_DataTableDoesNotExist", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}': Type '{1}' cannot be null.. + /// Looks up a localized string similar to The metadata XML is invalid. The {1} column of the {0} collection must contain a non-empty string.. /// - internal static string CodeGen_TypeCantBeNull { + internal static string MDF_InvalidXmlInvalidValue { get { - return ResourceManager.GetString("CodeGen_TypeCantBeNull", resourceCulture); + return ResourceManager.GetString("MDF_InvalidXmlInvalidValue", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs whenever this collection's membership changes.. + /// Looks up a localized string similar to A restriction exists for which there is no matching row in the restrictions collection.. /// - internal static string collectionChangedEventDescr { + internal static string MDF_MissingRestrictionRow { get { - return ResourceManager.GetString("collectionChangedEventDescr", resourceCulture); + return ResourceManager.GetString("MDF_MissingRestrictionRow", resourceCulture); } } /// - /// Looks up a localized string similar to Only elements allowed.. + /// Looks up a localized string similar to The schema table contains no columns.. /// - internal static string ConfigBaseElementsOnly { + internal static string MDF_NoColumns { get { - return ResourceManager.GetString("ConfigBaseElementsOnly", resourceCulture); + return ResourceManager.GetString("MDF_NoColumns", resourceCulture); } } /// - /// Looks up a localized string similar to Child nodes not allowed.. + /// Looks up a localized string similar to Unable to build the '{0}' collection because execution of the SQL query failed. See the inner exception for details.. /// - internal static string ConfigBaseNoChildNodes { + internal static string MDF_QueryFailed { get { - return ResourceManager.GetString("ConfigBaseNoChildNodes", resourceCulture); + return ResourceManager.GetString("MDF_QueryFailed", resourceCulture); } } /// - /// Looks up a localized string similar to The requested .NET Framework Data Provider's implementation does not have an Instance field of a System.Data.Common.DbProviderFactory derived type.. + /// Looks up a localized string similar to More restrictions were provided than the requested schema ('{0}') supports.. /// - internal static string ConfigProviderInvalid { + internal static string MDF_TooManyRestrictions { get { - return ResourceManager.GetString("ConfigProviderInvalid", resourceCulture); + return ResourceManager.GetString("MDF_TooManyRestrictions", resourceCulture); } } /// - /// Looks up a localized string similar to The missing .NET Framework Data Provider's assembly qualified name is required.. + /// Looks up a localized string similar to Unable to build schema collection '{0}';. /// - internal static string ConfigProviderMissing { + internal static string MDF_UnableToBuildCollection { get { - return ResourceManager.GetString("ConfigProviderMissing", resourceCulture); + return ResourceManager.GetString("MDF_UnableToBuildCollection", resourceCulture); } } /// - /// Looks up a localized string similar to Unable to find the requested .NET Framework Data Provider. It may not be installed.. + /// Looks up a localized string similar to The requested collection ({0}) is not defined.. /// - internal static string ConfigProviderNotFound { + internal static string MDF_UndefinedCollection { get { - return ResourceManager.GetString("ConfigProviderNotFound", resourceCulture); + return ResourceManager.GetString("MDF_UndefinedCollection", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to find or load the registered .NET Framework Data Provider.. + /// Looks up a localized string similar to The population mechanism '{0}' is not defined.. /// - internal static string ConfigProviderNotInstalled { + internal static string MDF_UndefinedPopulationMechanism { get { - return ResourceManager.GetString("ConfigProviderNotInstalled", resourceCulture); + return ResourceManager.GetString("MDF_UndefinedPopulationMechanism", resourceCulture); } } /// - /// Looks up a localized string similar to Required attribute '{0}' cannot be empty.. + /// Looks up a localized string similar to The requested collection ({0}) is not supported by this version of the provider.. /// - internal static string ConfigRequiredAttributeEmpty { + internal static string MDF_UnsupportedVersion { get { - return ResourceManager.GetString("ConfigRequiredAttributeEmpty", resourceCulture); + return ResourceManager.GetString("MDF_UnsupportedVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Required attribute '{0}' not found.. + /// Looks up a localized string similar to The validation of the attestation token failed. Claim '{0}' is missing in the token. Verify the attestation policy - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details. If the policy is correct, contact Customer Support Services.. /// - internal static string ConfigRequiredAttributeMissing { + internal static string MissingClaimInAttestationToken { get { - return ResourceManager.GetString("ConfigRequiredAttributeMissing", resourceCulture); + return ResourceManager.GetString("MissingClaimInAttestationToken", resourceCulture); } } /// - /// Looks up a localized string similar to The '{0}' section can only appear once per config file.. + /// Looks up a localized string similar to The specified value is not valid in the '{0}' enumeration.. /// - internal static string ConfigSectionsUnique { + internal static string net_invalid_enum { get { - return ResourceManager.GetString("ConfigSectionsUnique", resourceCulture); + return ResourceManager.GetString("net_invalid_enum", resourceCulture); } } /// - /// Looks up a localized string similar to Unrecognized attribute '{0}'.. + /// Looks up a localized string similar to This method is not implemented by this class.. /// - internal static string ConfigUnrecognizedAttributes { + internal static string net_MethodNotImplementedException { get { - return ResourceManager.GetString("ConfigUnrecognizedAttributes", resourceCulture); + return ResourceManager.GetString("net_MethodNotImplementedException", resourceCulture); } } /// - /// Looks up a localized string similar to Unrecognized element.. + /// Looks up a localized string similar to DateType column for field '{0}' in schema table is null. DataType must be non-null.. /// - internal static string ConfigUnrecognizedElement { + internal static string NullSchemaTableDataTypeNotSupported { get { - return ResourceManager.GetString("ConfigUnrecognizedElement", resourceCulture); + return ResourceManager.GetString("NullSchemaTableDataTypeNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the name of this constraint.. + /// Looks up a localized string similar to Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.. /// - internal static string ConstraintNameDescr { + internal static string SEC_ProtocolWarning { get { - return ResourceManager.GetString("ConstraintNameDescr", resourceCulture); + return ResourceManager.GetString("SEC_ProtocolWarning", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the table of this constraint.. + /// Looks up a localized string similar to I/O Error detected in read/write operation. /// - internal static string ConstraintTableDescr { + internal static string SNI_ERROR_1 { get { - return ResourceManager.GetString("ConstraintTableDescr", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_1", resourceCulture); } } /// - /// Looks up a localized string similar to '{0}' argument contains null value.. + /// Looks up a localized string similar to Socket is null. /// - internal static string Data_ArgumentContainsNull { + internal static string SNI_ERROR_10 { get { - return ResourceManager.GetString("Data_ArgumentContainsNull", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_10", resourceCulture); } } /// - /// Looks up a localized string similar to '{0}' argument cannot be null.. + /// Looks up a localized string similar to Timeout error. /// - internal static string Data_ArgumentNull { + internal static string SNI_ERROR_11 { get { - return ResourceManager.GetString("Data_ArgumentNull", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_11", resourceCulture); } } /// - /// Looks up a localized string similar to '{0}' argument is out of range.. + /// Looks up a localized string similar to No server name supplied. /// - internal static string Data_ArgumentOutOfRange { + internal static string SNI_ERROR_12 { get { - return ResourceManager.GetString("Data_ArgumentOutOfRange", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_12", resourceCulture); } } /// - /// Looks up a localized string similar to Collection itself is not modifiable.. + /// Looks up a localized string similar to TerminateListener() has been called. /// - internal static string Data_CannotModifyCollection { + internal static string SNI_ERROR_13 { get { - return ResourceManager.GetString("Data_CannotModifyCollection", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_13", resourceCulture); } } /// - /// Looks up a localized string similar to The given name '{0}' matches at least two names in the collection object with different cases, but does not match either of them with the same case.. + /// Looks up a localized string similar to Win9x not supported. /// - internal static string Data_CaseInsensitiveNameConflict { + internal static string SNI_ERROR_14 { get { - return ResourceManager.GetString("Data_CaseInsensitiveNameConflict", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_14", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.. + /// Looks up a localized string similar to Function not supported. /// - internal static string Data_EnforceConstraints { + internal static string SNI_ERROR_15 { get { - return ResourceManager.GetString("Data_EnforceConstraints", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_15", resourceCulture); } } /// - /// Looks up a localized string similar to Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.. + /// Looks up a localized string similar to Shared-Memory heap error. /// - internal static string Data_InvalidOffsetLength { + internal static string SNI_ERROR_16 { get { - return ResourceManager.GetString("Data_InvalidOffsetLength", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_16", resourceCulture); } } /// - /// Looks up a localized string similar to The given name '{0}' matches at least two names in the collection object with different namespaces.. + /// Looks up a localized string similar to Cannot find an ip/ipv6 type address to connect. /// - internal static string Data_NamespaceNameConflict { + internal static string SNI_ERROR_17 { get { - return ResourceManager.GetString("Data_NamespaceNameConflict", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_17", resourceCulture); } } /// - /// Looks up a localized string similar to Whether or not Fill will call DataRow.AcceptChanges.. + /// Looks up a localized string similar to Connection has been closed by peer. /// - internal static string DataAdapter_AcceptChangesDuringFill { + internal static string SNI_ERROR_18 { get { - return ResourceManager.GetString("DataAdapter_AcceptChangesDuringFill", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_18", resourceCulture); } } /// - /// Looks up a localized string similar to Whether or not Update will call DataRow.AcceptChanges.. + /// Looks up a localized string similar to Physical connection is not usable. /// - internal static string DataAdapter_AcceptChangesDuringUpdate { + internal static string SNI_ERROR_19 { get { - return ResourceManager.GetString("DataAdapter_AcceptChangesDuringUpdate", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_19", resourceCulture); } } /// - /// Looks up a localized string similar to Whether or not to continue to the next DataRow when the Update events, RowUpdating and RowUpdated, Status is UpdateStatus.ErrorsOccurred.. + /// Looks up a localized string similar to Connection was terminated. /// - internal static string DataAdapter_ContinueUpdateOnError { + internal static string SNI_ERROR_2 { get { - return ResourceManager.GetString("DataAdapter_ContinueUpdateOnError", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_2", resourceCulture); } } /// - /// Looks up a localized string similar to Event triggered when a recoverable error occurs during Fill.. + /// Looks up a localized string similar to Connection has been closed. /// - internal static string DataAdapter_FillError { + internal static string SNI_ERROR_20 { get { - return ResourceManager.GetString("DataAdapter_FillError", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_20", resourceCulture); } } /// - /// Looks up a localized string similar to How the adapter fills the DataTable from the DataReader.. + /// Looks up a localized string similar to Encryption is enforced but there is no valid certificate. /// - internal static string DataAdapter_FillLoadOption { + internal static string SNI_ERROR_21 { get { - return ResourceManager.GetString("DataAdapter_FillLoadOption", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_21", resourceCulture); } } /// - /// Looks up a localized string similar to The action taken when a table or column in the TableMappings is missing.. + /// Looks up a localized string similar to Couldn't load library. /// - internal static string DataAdapter_MissingMappingAction { + internal static string SNI_ERROR_22 { get { - return ResourceManager.GetString("DataAdapter_MissingMappingAction", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_22", resourceCulture); } } /// - /// Looks up a localized string similar to The action taken when a table or column in the DataSet is missing.. + /// Looks up a localized string similar to Cannot open a new thread in server process. /// - internal static string DataAdapter_MissingSchemaAction { + internal static string SNI_ERROR_23 { get { - return ResourceManager.GetString("DataAdapter_MissingSchemaAction", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_23", resourceCulture); } } /// - /// Looks up a localized string similar to Should Fill return provider specific values or common CLSCompliant values.. + /// Looks up a localized string similar to Cannot post event to completion port. /// - internal static string DataAdapter_ReturnProviderSpecificTypes { + internal static string SNI_ERROR_24 { get { - return ResourceManager.GetString("DataAdapter_ReturnProviderSpecificTypes", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_24", resourceCulture); } } /// - /// Looks up a localized string similar to How to map source table to DataSet table.. + /// Looks up a localized string similar to Connection string is not valid. /// - internal static string DataAdapter_TableMappings { + internal static string SNI_ERROR_25 { get { - return ResourceManager.GetString("DataAdapter_TableMappings", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_25", resourceCulture); } } /// - /// Looks up a localized string similar to Action. + /// Looks up a localized string similar to Error Locating Server/Instance Specified. /// - internal static string DataCategory_Action { + internal static string SNI_ERROR_26 { get { - return ResourceManager.GetString("DataCategory_Action", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_26", resourceCulture); } } /// - /// Looks up a localized string similar to Advanced. + /// Looks up a localized string similar to Error getting enabled protocols list from registry. /// - internal static string DataCategory_Advanced { + internal static string SNI_ERROR_27 { get { - return ResourceManager.GetString("DataCategory_Advanced", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_27", resourceCulture); } } /// - /// Looks up a localized string similar to Behavior. + /// Looks up a localized string similar to Server doesn't support requested protocol. /// - internal static string DataCategory_Behavior { + internal static string SNI_ERROR_28 { get { - return ResourceManager.GetString("DataCategory_Behavior", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_28", resourceCulture); } } /// - /// Looks up a localized string similar to Connection Resiliency. + /// Looks up a localized string similar to Shared Memory is not supported for clustered server connectivity. /// - internal static string DataCategory_ConnectionResiliency { + internal static string SNI_ERROR_29 { get { - return ResourceManager.GetString("DataCategory_ConnectionResiliency", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_29", resourceCulture); } } /// - /// Looks up a localized string similar to Context. + /// Looks up a localized string similar to Asynchronous operations not supported. /// - internal static string DataCategory_Context { + internal static string SNI_ERROR_3 { get { - return ResourceManager.GetString("DataCategory_Context", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_3", resourceCulture); } } /// - /// Looks up a localized string similar to Data. + /// Looks up a localized string similar to Invalid attempt bind to shared memory segment. /// - internal static string DataCategory_Data { + internal static string SNI_ERROR_30 { get { - return ResourceManager.GetString("DataCategory_Data", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_30", resourceCulture); } } /// - /// Looks up a localized string similar to Fill. + /// Looks up a localized string similar to Encryption(ssl/tls) handshake failed. /// - internal static string DataCategory_Fill { + internal static string SNI_ERROR_31 { get { - return ResourceManager.GetString("DataCategory_Fill", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_31", resourceCulture); } } /// - /// Looks up a localized string similar to InfoMessage. + /// Looks up a localized string similar to Packet size too large for SSL Encrypt/Decrypt operations. /// - internal static string DataCategory_InfoMessage { + internal static string SNI_ERROR_32 { get { - return ResourceManager.GetString("DataCategory_InfoMessage", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_32", resourceCulture); } } /// - /// Looks up a localized string similar to Initialization. + /// Looks up a localized string similar to SSRP error. /// - internal static string DataCategory_Initialization { + internal static string SNI_ERROR_33 { get { - return ResourceManager.GetString("DataCategory_Initialization", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_33", resourceCulture); } } /// - /// Looks up a localized string similar to Mapping. + /// Looks up a localized string similar to Could not connect to the Shared Memory pipe. /// - internal static string DataCategory_Mapping { + internal static string SNI_ERROR_34 { get { - return ResourceManager.GetString("DataCategory_Mapping", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_34", resourceCulture); } } /// - /// Looks up a localized string similar to Named ConnectionString. + /// Looks up a localized string similar to An internal exception was caught. /// - internal static string DataCategory_NamedConnectionString { + internal static string SNI_ERROR_35 { get { - return ResourceManager.GetString("DataCategory_NamedConnectionString", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_35", resourceCulture); } } /// - /// Looks up a localized string similar to Notification. + /// Looks up a localized string similar to The Shared Memory dll used to connect to SQL Server 2000 was not found. /// - internal static string DataCategory_Notification { + internal static string SNI_ERROR_36 { get { - return ResourceManager.GetString("DataCategory_Notification", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_36", resourceCulture); } } /// - /// Looks up a localized string similar to Pooling. + /// Looks up a localized string similar to The SQL Server 2000 Shared Memory client dll appears to be invalid/corrupted. /// - internal static string DataCategory_Pooling { + internal static string SNI_ERROR_37 { get { - return ResourceManager.GetString("DataCategory_Pooling", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_37", resourceCulture); } } /// - /// Looks up a localized string similar to Replication. + /// Looks up a localized string similar to Cannot open a Shared Memory connection to SQL Server 2000. /// - internal static string DataCategory_Replication { + internal static string SNI_ERROR_38 { get { - return ResourceManager.GetString("DataCategory_Replication", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_38", resourceCulture); } } /// - /// Looks up a localized string similar to Schema. + /// Looks up a localized string similar to Shared memory connectivity to SQL Server 2000 is either disabled or not available on this machine. /// - internal static string DataCategory_Schema { + internal static string SNI_ERROR_39 { get { - return ResourceManager.GetString("DataCategory_Schema", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_39", resourceCulture); } } /// - /// Looks up a localized string similar to Security. + /// Looks up a localized string similar to . /// - internal static string DataCategory_Security { + internal static string SNI_ERROR_4 { get { - return ResourceManager.GetString("DataCategory_Security", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_4", resourceCulture); } } /// - /// Looks up a localized string similar to Source. + /// Looks up a localized string similar to Could not open a connection to SQL Server. /// - internal static string DataCategory_Source { + internal static string SNI_ERROR_40 { get { - return ResourceManager.GetString("DataCategory_Source", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_40", resourceCulture); } } /// - /// Looks up a localized string similar to StateChange. + /// Looks up a localized string similar to Cannot open a Shared Memory connection to a remote SQL server. /// - internal static string DataCategory_StateChange { + internal static string SNI_ERROR_41 { get { - return ResourceManager.GetString("DataCategory_StateChange", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_41", resourceCulture); } } /// - /// Looks up a localized string similar to StatementCompleted. + /// Looks up a localized string similar to Could not establish dedicated administrator connection (DAC) on default port. Make sure that DAC is enabled. /// - internal static string DataCategory_StatementCompleted { + internal static string SNI_ERROR_42 { get { - return ResourceManager.GetString("DataCategory_StatementCompleted", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_42", resourceCulture); } } /// - /// Looks up a localized string similar to UDT. + /// Looks up a localized string similar to An error occurred while obtaining the dedicated administrator connection (DAC) port. Make sure that SQL Browser is running, or check the error log for the port number. /// - internal static string DataCategory_Udt { + internal static string SNI_ERROR_43 { get { - return ResourceManager.GetString("DataCategory_Udt", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_43", resourceCulture); } } /// - /// Looks up a localized string similar to Update. + /// Looks up a localized string similar to Could not compose Service Principal Name (SPN) for Windows Integrated Authentication. Possible causes are server(s) incorrectly specified to connection API calls, Domain Name System (DNS) lookup failure or memory shortage. /// - internal static string DataCategory_Update { + internal static string SNI_ERROR_44 { get { - return ResourceManager.GetString("DataCategory_Update", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_44", resourceCulture); } } /// - /// Looks up a localized string similar to XML. + /// Looks up a localized string similar to Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported.. /// - internal static string DataCategory_Xml { + internal static string SNI_ERROR_47 { get { - return ResourceManager.GetString("DataCategory_Xml", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_47", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set AutoIncrement property for a column with DefaultValue set.. + /// Looks up a localized string similar to Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported.. /// - internal static string DataColumn_AutoIncrementAndDefaultValue { + internal static string SNI_ERROR_48 { get { - return ResourceManager.GetString("DataColumn_AutoIncrementAndDefaultValue", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_48", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set AutoIncrement property for a computed column.. + /// Looks up a localized string similar to Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol.. /// - internal static string DataColumn_AutoIncrementAndExpression { + internal static string SNI_ERROR_49 { get { - return ResourceManager.GetString("DataColumn_AutoIncrementAndExpression", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_49", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change AutoIncrement of a DataColumn with type '{0}' once it has data.. + /// Looks up a localized string similar to Invalid parameter(s) found. /// - internal static string DataColumn_AutoIncrementCannotSetIfHasData { + internal static string SNI_ERROR_5 { get { - return ResourceManager.GetString("DataColumn_AutoIncrementCannotSetIfHasData", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_5", resourceCulture); } } /// - /// Looks up a localized string similar to AutoIncrementStep must be a non-zero value.. + /// Looks up a localized string similar to Local Database Runtime error occurred. . /// - internal static string DataColumn_AutoIncrementSeed { + internal static string SNI_ERROR_50 { get { - return ResourceManager.GetString("DataColumn_AutoIncrementSeed", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_50", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the Column '{0}' property Namespace. The Column is SimpleContent.. + /// Looks up a localized string similar to An instance name was not specified while connecting to a Local Database Runtime. Specify an instance name in the format (localdb)\instance_name.. /// - internal static string DataColumn_CannotChangeNamespace { + internal static string SNI_ERROR_51 { get { - return ResourceManager.GetString("DataColumn_CannotChangeNamespace", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_51", resourceCulture); } } /// - /// Looks up a localized string similar to The DateTimeMode can be set only on DataColumns of type DateTime.. + /// Looks up a localized string similar to Unable to locate a Local Database Runtime installation. Verify that SQL Server Express is properly installed and that the Local Database Runtime feature is enabled.. /// - internal static string DataColumn_CannotSetDateTimeModeForNonDateTimeColumns { + internal static string SNI_ERROR_52 { get { - return ResourceManager.GetString("DataColumn_CannotSetDateTimeModeForNonDateTimeColumns", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_52", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set Column '{0}' property MaxLength to '{1}'. There is at least one string in the table longer than the new limit.. + /// Looks up a localized string similar to Invalid Local Database Runtime registry configuration found. Verify that SQL Server Express is properly installed.. /// - internal static string DataColumn_CannotSetMaxLength { + internal static string SNI_ERROR_53 { get { - return ResourceManager.GetString("DataColumn_CannotSetMaxLength", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_53", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set Column '{0}' property MaxLength. The Column is SimpleContent.. + /// Looks up a localized string similar to Unable to locate the registry entry for SQLUserInstance.dll file path. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. /// - internal static string DataColumn_CannotSetMaxLength2 { + internal static string SNI_ERROR_54 { get { - return ResourceManager.GetString("DataColumn_CannotSetMaxLength2", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_54", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set Column '{0}' to be null. Please use DBNull instead.. + /// Looks up a localized string similar to Registry value contains an invalid SQLUserInstance.dll file path. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. /// - internal static string DataColumn_CannotSetToNull { + internal static string SNI_ERROR_55 { get { - return ResourceManager.GetString("DataColumn_CannotSetToNull", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_55", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set Column '{0}' property MappingType to SimpleContent. The Column DataType is {1}.. + /// Looks up a localized string similar to Unable to load the SQLUserInstance.dll from the location specified in the registry. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. /// - internal static string DataColumn_CannotSimpleContent { + internal static string SNI_ERROR_56 { get { - return ResourceManager.GetString("DataColumn_CannotSimpleContent", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_56", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set Column '{0}' property DataType to {1}. The Column is SimpleContent.. + /// Looks up a localized string similar to Invalid SQLUserInstance.dll found at the location specified in the registry. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. /// - internal static string DataColumn_CannotSimpleContentType { + internal static string SNI_ERROR_57 { get { - return ResourceManager.GetString("DataColumn_CannotSimpleContentType", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_57", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change DataType of a column once it has data.. + /// Looks up a localized string similar to Unsupported protocol specified. /// - internal static string DataColumn_ChangeDataType { + internal static string SNI_ERROR_6 { get { - return ResourceManager.GetString("DataColumn_ChangeDataType", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_6", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change DateTimeMode from '{0}' to '{1}' once the table has data.. + /// Looks up a localized string similar to Invalid connection found when setting up new session protocol. /// - internal static string DataColumn_DateTimeMode { + internal static string SNI_ERROR_7 { get { - return ResourceManager.GetString("DataColumn_DateTimeMode", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_7", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set a DefaultValue on an AutoIncrement column.. + /// Looks up a localized string similar to Protocol not supported. /// - internal static string DataColumn_DefaultValueAndAutoIncrement { + internal static string SNI_ERROR_8 { get { - return ResourceManager.GetString("DataColumn_DefaultValueAndAutoIncrement", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_8", resourceCulture); } } /// - /// Looks up a localized string similar to The DefaultValue for column {0} is of type {1}, but the column is of type {2}.. + /// Looks up a localized string similar to Associating port with I/O completion mechanism failed. /// - internal static string DataColumn_DefaultValueColumnDataType { + internal static string SNI_ERROR_9 { get { - return ResourceManager.GetString("DataColumn_DefaultValueColumnDataType", resourceCulture); + return ResourceManager.GetString("SNI_ERROR_9", resourceCulture); } } /// - /// Looks up a localized string similar to The DefaultValue for column {0} is of type {1} and cannot be converted to {2}.. + /// Looks up a localized string similar to Incorrect physicalConnection type.. /// - internal static string DataColumn_DefaultValueDataType { + internal static string SNI_IncorrectPhysicalConnectionType { get { - return ResourceManager.GetString("DataColumn_DefaultValueDataType", resourceCulture); + return ResourceManager.GetString("SNI_IncorrectPhysicalConnectionType", resourceCulture); } } /// - /// Looks up a localized string similar to The DefaultValue for the column is of type {0} and cannot be converted to {1}.. + /// Looks up a localized string similar to The '{0}' platform is not supported when targeting .NET Framework.. /// - internal static string DataColumn_DefaultValueDataType1 { + internal static string SNI_PlatformNotSupportedNetFx { get { - return ResourceManager.GetString("DataColumn_DefaultValueDataType1", resourceCulture); + return ResourceManager.GetString("SNI_PlatformNotSupportedNetFx", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' exceeds the MaxLength limit.. + /// Looks up a localized string similar to HTTP Provider. /// - internal static string DataColumn_ExceedMaxLength { + internal static string SNI_PN0 { get { - return ResourceManager.GetString("DataColumn_ExceedMaxLength", resourceCulture); + return ResourceManager.GetString("SNI_PN0", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set Expression property on column {0}, because it is a part of a constraint.. + /// Looks up a localized string similar to Named Pipes Provider. /// - internal static string DataColumn_ExpressionAndConstraint { + internal static string SNI_PN1 { get { - return ResourceManager.GetString("DataColumn_ExpressionAndConstraint", resourceCulture); + return ResourceManager.GetString("SNI_PN1", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set expression because column cannot be made ReadOnly.. + /// Looks up a localized string similar to . /// - internal static string DataColumn_ExpressionAndReadOnly { + internal static string SNI_PN10 { get { - return ResourceManager.GetString("DataColumn_ExpressionAndReadOnly", resourceCulture); + return ResourceManager.GetString("SNI_PN10", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create an expression on a column that has AutoIncrement or Unique.. + /// Looks up a localized string similar to SQL Network Interfaces. /// - internal static string DataColumn_ExpressionAndUnique { + internal static string SNI_PN11 { get { - return ResourceManager.GetString("DataColumn_ExpressionAndUnique", resourceCulture); + return ResourceManager.GetString("SNI_PN11", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set Expression property due to circular reference in the expression.. + /// Looks up a localized string similar to Session Provider. /// - internal static string DataColumn_ExpressionCircular { + internal static string SNI_PN2 { get { - return ResourceManager.GetString("DataColumn_ExpressionCircular", resourceCulture); + return ResourceManager.GetString("SNI_PN2", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create a constraint based on Expression column {0}.. + /// Looks up a localized string similar to Sign Provider. /// - internal static string DataColumn_ExpressionInConstraint { + internal static string SNI_PN3 { get { - return ResourceManager.GetString("DataColumn_ExpressionInConstraint", resourceCulture); + return ResourceManager.GetString("SNI_PN3", resourceCulture); } } /// - /// Looks up a localized string similar to MaxLength applies to string data type only. You cannot set Column '{0}' property MaxLength to be non-negative number.. + /// Looks up a localized string similar to Shared Memory Provider. /// - internal static string DataColumn_HasToBeStringType { + internal static string SNI_PN4 { get { - return ResourceManager.GetString("DataColumn_HasToBeStringType", resourceCulture); + return ResourceManager.GetString("SNI_PN4", resourceCulture); } } /// - /// Looks up a localized string similar to Type '{0}' does not contain static Null property or field.. + /// Looks up a localized string similar to SMux Provider. /// - internal static string DataColumn_INullableUDTwithoutStaticNull { + internal static string SNI_PN5 { get { - return ResourceManager.GetString("DataColumn_INullableUDTwithoutStaticNull", resourceCulture); + return ResourceManager.GetString("SNI_PN5", resourceCulture); } } /// - /// Looks up a localized string similar to DataColumn with type '{0}' is a complexType. Can not serialize value of a complex type as Attribute. + /// Looks up a localized string similar to SSL Provider. /// - internal static string DataColumn_InvalidDataColumnMapping { + internal static string SNI_PN6 { get { - return ResourceManager.GetString("DataColumn_InvalidDataColumnMapping", resourceCulture); + return ResourceManager.GetString("SNI_PN6", resourceCulture); } } /// - /// Looks up a localized string similar to '{0}' is Invalid DataSetDateTime value.. + /// Looks up a localized string similar to TCP Provider. /// - internal static string DataColumn_InvalidDateTimeMode { + internal static string SNI_PN7 { get { - return ResourceManager.GetString("DataColumn_InvalidDateTimeMode", resourceCulture); + return ResourceManager.GetString("SNI_PN7", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set column '{0}'. The value violates the MaxLength limit of this column.. + /// Looks up a localized string similar to VIA Provider. /// - internal static string DataColumn_LongerThanMaxLength { + internal static string SNI_PN8 { get { - return ResourceManager.GetString("DataColumn_LongerThanMaxLength", resourceCulture); + return ResourceManager.GetString("SNI_PN8", resourceCulture); } } /// - /// Looks up a localized string similar to ColumnName is required when it is part of a DataTable.. + /// Looks up a localized string similar to CTAIP Provider. /// - internal static string DataColumn_NameRequired { + internal static string SNI_PN9 { get { - return ResourceManager.GetString("DataColumn_NameRequired", resourceCulture); + return ResourceManager.GetString("SNI_PN9", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' contains non-unique values.. + /// Looks up a localized string similar to Connection open and login was successful, but then an error occurred while enlisting the connection into the current distributed transaction.. /// - internal static string DataColumn_NonUniqueValues { + internal static string Snix_AutoEnlist { get { - return ResourceManager.GetString("DataColumn_NonUniqueValues", resourceCulture); + return ResourceManager.GetString("Snix_AutoEnlist", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' does not allow DBNull.Value.. + /// Looks up a localized string similar to A transport-level error has occurred during connection clean-up.. /// - internal static string DataColumn_NotAllowDBNull { + internal static string Snix_Close { get { - return ResourceManager.GetString("DataColumn_NotAllowDBNull", resourceCulture); + return ResourceManager.GetString("Snix_Close", resourceCulture); } } /// - /// Looks up a localized string similar to Column must belong to a table.. + /// Looks up a localized string similar to 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.. /// - internal static string DataColumn_NotInAnyTable { + internal static string Snix_Connect { get { - return ResourceManager.GetString("DataColumn_NotInAnyTable", resourceCulture); + return ResourceManager.GetString("Snix_Connect", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' does not belong to table {1}.. + /// Looks up a localized string similar to Connection open and login was successful, but then an error occurred while enabling MARS for this connection.. /// - internal static string DataColumn_NotInTheTable { + internal static string Snix_EnableMars { get { - return ResourceManager.GetString("DataColumn_NotInTheTable", resourceCulture); + return ResourceManager.GetString("Snix_EnableMars", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' does not belong to underlying table '{1}'.. + /// Looks up a localized string similar to A transport-level error has occurred when sending the request to the server.. /// - internal static string DataColumn_NotInTheUnderlyingTable { + internal static string Snix_Execute { get { - return ResourceManager.GetString("DataColumn_NotInTheUnderlyingTable", resourceCulture); + return ResourceManager.GetString("Snix_Execute", resourceCulture); } } /// - /// Looks up a localized string similar to DataSet does not support System.Nullable<>.. + /// Looks up a localized string similar to Failed to establish a MARS session in preparation to send the request to the server.. /// - internal static string DataColumn_NullableTypesNotSupported { + internal static string Snix_GetMarsSession { get { - return ResourceManager.GetString("DataColumn_NullableTypesNotSupported", resourceCulture); + return ResourceManager.GetString("Snix_GetMarsSession", resourceCulture); } } /// - /// Looks up a localized string similar to Column requires a valid DataType.. + /// Looks up a localized string similar to A connection was successfully established with the server, but then an error occurred during the login process.. /// - internal static string DataColumn_NullDataType { + internal static string Snix_Login { get { - return ResourceManager.GetString("DataColumn_NullDataType", resourceCulture); + return ResourceManager.GetString("Snix_Login", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' has null values in it.. + /// Looks up a localized string similar to A connection was successfully established with the server, but then an error occurred when obtaining the security/SSPI context information for integrated security login.. /// - internal static string DataColumn_NullKeyValues { + internal static string Snix_LoginSspi { get { - return ResourceManager.GetString("DataColumn_NullKeyValues", resourceCulture); + return ResourceManager.GetString("Snix_LoginSspi", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' does not allow nulls.. + /// Looks up a localized string similar to A connection was successfully established with the server, but then an error occurred during the pre-login handshake.. /// - internal static string DataColumn_NullValues { + internal static string Snix_PreLogin { get { - return ResourceManager.GetString("DataColumn_NullValues", resourceCulture); + return ResourceManager.GetString("Snix_PreLogin", resourceCulture); } } /// - /// Looks up a localized string similar to Ordinal '{0}' exceeds the maximum number.. + /// 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 DataColumn_OrdinalExceedMaximum { + internal static string Snix_PreLoginBeforeSuccessfulWrite { get { - return ResourceManager.GetString("DataColumn_OrdinalExceedMaximum", resourceCulture); + return ResourceManager.GetString("Snix_PreLoginBeforeSuccessfulWrite", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' is read only.. + /// Looks up a localized string similar to A transport-level error has occurred during SSPI handshake.. /// - internal static string DataColumn_ReadOnly { + internal static string Snix_ProcessSspi { get { - return ResourceManager.GetString("DataColumn_ReadOnly", resourceCulture); + return ResourceManager.GetString("Snix_ProcessSspi", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change ReadOnly property for the expression column.. + /// Looks up a localized string similar to A transport-level error has occurred when receiving results from the server.. /// - internal static string DataColumn_ReadOnlyAndExpression { + internal static string Snix_Read { get { - return ResourceManager.GetString("DataColumn_ReadOnlyAndExpression", resourceCulture); + return ResourceManager.GetString("Snix_Read", resourceCulture); } } /// - /// Looks up a localized string similar to SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.. + /// Looks up a localized string similar to A transport-level error has occurred while sending information to the server.. /// - internal static string DataColumn_SetAddedAndModifiedCalledOnNonUnchanged { + internal static string Snix_SendRows { get { - return ResourceManager.GetString("DataColumn_SetAddedAndModifiedCalledOnNonUnchanged", resourceCulture); + return ResourceManager.GetString("Snix_SendRows", resourceCulture); } } /// - /// Looks up a localized string similar to Couldn't store <{0}> in {1} Column. Expected type is {2}.. + /// Looks up a localized string similar to Cannot transition from state '{0}' to '{1}'.. /// - internal static string DataColumn_SetFailed { + internal static string SQL_ActiveDirectoryInvalidStateTransition { get { - return ResourceManager.GetString("DataColumn_SetFailed", resourceCulture); + return ResourceManager.GetString("SQL_ActiveDirectoryInvalidStateTransition", resourceCulture); } } /// - /// Looks up a localized string similar to Type '{0}' does not implement IRevertibleChangeTracking; therefore can not proceed with RejectChanges().. + /// Looks up a localized string similar to Unsupported state: '{0}'.. /// - internal static string DataColumn_UDTImplementsIChangeTrackingButnotIRevertible { + internal static string SQL_ActiveDirectoryUnsupportedState { get { - return ResourceManager.GetString("DataColumn_UDTImplementsIChangeTrackingButnotIRevertible", resourceCulture); + return ResourceManager.GetString("SQL_ActiveDirectoryUnsupportedState", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change Unique property for the expression column.. + /// Looks up a localized string similar to Cannot use 'Authentication' with 'Integrated Security'.. /// - internal static string DataColumn_UniqueAndExpression { + internal static string SQL_AuthenticationAndIntegratedSecurity { get { - return ResourceManager.GetString("DataColumn_UniqueAndExpression", resourceCulture); + return ResourceManager.GetString("SQL_AuthenticationAndIntegratedSecurity", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether null values are allowed in this column.. + /// Looks up a localized string similar to SqlBulkCopy.WriteToServer failed because the SqlBulkCopy.DestinationTableName is an invalid multipart name. /// - internal static string DataColumnAllowNullDescr { + internal static string SQL_BulkCopyDestinationTableName { get { - return ResourceManager.GetString("DataColumnAllowNullDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkCopyDestinationTableName", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether the column automatically increments itself for new rows added to the table. The type of this column must be Int16, Int32, or Int64.. + /// Looks up a localized string similar to The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}] Row {5}.. /// - internal static string DataColumnAutoIncrementDescr { + internal static string SQL_BulkLoadCannotConvertValue { get { - return ResourceManager.GetString("DataColumnAutoIncrementDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadCannotConvertValue", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the starting value for an AutoIncrement column.. + /// Looks up a localized string similar to The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].. /// - internal static string DataColumnAutoIncrementSeedDescr { + internal static string SQL_BulkLoadCannotConvertValueWithoutRowNo { get { - return ResourceManager.GetString("DataColumnAutoIncrementSeedDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadCannotConvertValueWithoutRowNo", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the increment used by an AutoIncrement column.. + /// Looks up a localized string similar to Must not specify SqlBulkCopyOption.UseInternalTransaction and pass an external Transaction at the same time.. /// - internal static string DataColumnAutoIncrementStepDescr { + internal static string SQL_BulkLoadConflictingTransactionOption { get { - return ResourceManager.GetString("DataColumnAutoIncrementStepDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadConflictingTransactionOption", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the default user-interface caption for this column.. + /// Looks up a localized string similar to Unexpected existing transaction.. /// - internal static string DataColumnCaptionDescr { + internal static string SQL_BulkLoadExistingTransaction { get { - return ResourceManager.GetString("DataColumnCaptionDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadExistingTransaction", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the name used to look up this column in the Columns collection of a DataTable.. + /// Looks up a localized string similar to Cannot access destination table '{0}'.. /// - internal static string DataColumnColumnNameDescr { + internal static string SQL_BulkLoadInvalidDestinationTable { get { - return ResourceManager.GetString("DataColumnColumnNameDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadInvalidDestinationTable", resourceCulture); } } /// - /// Looks up a localized string similar to Returns the DataTable to which this column belongs.. + /// Looks up a localized string similar to Function must not be called during event.. /// - internal static string DataColumnDataTableDescr { + internal static string SQL_BulkLoadInvalidOperationInsideEvent { get { - return ResourceManager.GetString("DataColumnDataTableDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadInvalidOperationInsideEvent", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the type of data stored in this column.. + /// Looks up a localized string similar to The given column order hint is not valid.. /// - internal static string DataColumnDataTypeDescr { + internal static string SQL_BulkLoadInvalidOrderHint { get { - return ResourceManager.GetString("DataColumnDataTypeDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadInvalidOrderHint", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates DateTimeMode of this DataColumn.. + /// Looks up a localized string similar to Timeout Value '{0}' is less than 0.. /// - internal static string DataColumnDateTimeModeDescr { + internal static string SQL_BulkLoadInvalidTimeout { get { - return ResourceManager.GetString("DataColumnDateTimeModeDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadInvalidTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the default column value used when adding new rows to the table.. + /// Looks up a localized string similar to Value cannot be converted to SqlVariant.. /// - internal static string DataColumnDefaultValueDescr { + internal static string SQL_BulkLoadInvalidVariantValue { get { - return ResourceManager.GetString("DataColumnDefaultValueDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadInvalidVariantValue", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the value that this column computes for each row based on other columns instead of taking user input.. + /// Looks up a localized string similar to The locale id '{0}' of the source column '{1}' and the locale id '{2}' of the destination column '{3}' do not match.. /// - internal static string DataColumnExpressionDescr { + internal static string Sql_BulkLoadLcidMismatch { get { - return ResourceManager.GetString("DataColumnExpressionDescr", resourceCulture); + return ResourceManager.GetString("Sql_BulkLoadLcidMismatch", resourceCulture); } } /// - /// Looks up a localized string similar to DataColumn.ColumnName. + /// Looks up a localized string similar to The mapped collection is in use and cannot be accessed at this time;. /// - internal static string DataColumnMapping_DataSetColumn { + internal static string SQL_BulkLoadMappingInaccessible { get { - return ResourceManager.GetString("DataColumnMapping_DataSetColumn", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadMappingInaccessible", resourceCulture); } } /// - /// Looks up a localized string similar to Source column name - case sensitive.. + /// Looks up a localized string similar to Mappings must be either all name or all ordinal based.. /// - internal static string DataColumnMapping_SourceColumn { + internal static string SQL_BulkLoadMappingsNamesOrOrdinalsOnly { get { - return ResourceManager.GetString("DataColumnMapping_SourceColumn", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadMappingsNamesOrOrdinalsOnly", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing.. + /// Looks up a localized string similar to The DestinationTableName property must be set before calling this method.. /// - internal static string DataColumnMappingDescr { + internal static string SQL_BulkLoadMissingDestinationTable { get { - return ResourceManager.GetString("DataColumnMappingDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadMissingDestinationTable", resourceCulture); } } /// - /// Looks up a localized string similar to The number of items in the collection. + /// Looks up a localized string similar to Failed to obtain column collation information for the destination table. If the table is not in the current database the name must be qualified using the database name (e.g. [mydb]..[mytable](e.g. [mydb]..[mytable]); this also applies to temporary-tables (e.g. #mytable would be specified as tempdb..#mytable).. /// - internal static string DataColumnMappings_Count { + internal static string SQL_BulkLoadNoCollation { get { - return ResourceManager.GetString("DataColumnMappings_Count", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadNoCollation", resourceCulture); } } /// - /// Looks up a localized string similar to The specified DataColumnMapping object.. + /// Looks up a localized string similar to The given ColumnMapping does not match up with any column in the source or destination.. /// - internal static string DataColumnMappings_Item { + internal static string SQL_BulkLoadNonMatchingColumnMapping { get { - return ResourceManager.GetString("DataColumnMappings_Item", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadNonMatchingColumnMapping", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the maximum length of the value this column allows.. + /// Looks up a localized string similar to The given ColumnName '{0}' does not match up with any column in data source.. /// - internal static string DataColumnMaxLengthDescr { + internal static string SQL_BulkLoadNonMatchingColumnName { get { - return ResourceManager.GetString("DataColumnMaxLengthDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadNonMatchingColumnName", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the XML uri for elements or attributes stored in this column.. + /// Looks up a localized string similar to Column '{0}' does not allow DBNull.Value.. /// - internal static string DataColumnNamespaceDescr { + internal static string SQL_BulkLoadNotAllowDBNull { get { - return ResourceManager.GetString("DataColumnNamespaceDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadNotAllowDBNull", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the index of this column in the Columns collection.. + /// Looks up a localized string similar to The column '{0}' was specified more than once.. /// - internal static string DataColumnOrdinalDescr { + internal static string SQL_BulkLoadOrderHintDuplicateColumn { get { - return ResourceManager.GetString("DataColumnOrdinalDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadOrderHintDuplicateColumn", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the Prefix used for this DataColumn in xml representation.. + /// Looks up a localized string similar to The sorted column '{0}' is not valid in the destination table.. /// - internal static string DataColumnPrefixDescr { + internal static string SQL_BulkLoadOrderHintInvalidColumn { get { - return ResourceManager.GetString("DataColumnPrefixDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadOrderHintInvalidColumn", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether this column allows changes once a row has been added to the table.. + /// Looks up a localized string similar to Attempt to invoke bulk copy on an object that has a pending operation.. /// - internal static string DataColumnReadOnlyDescr { + internal static string SQL_BulkLoadPendingOperation { get { - return ResourceManager.GetString("DataColumnReadOnlyDescr", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadPendingOperation", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' already belongs to this DataTable.. + /// Looks up a localized string similar to String or binary data would be truncated in table '{0}', column '{1}'. Truncated value: '{2}'.. /// - internal static string DataColumns_Add1 { + internal static string SQL_BulkLoadStringTooLong { get { - return ResourceManager.GetString("DataColumns_Add1", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadStringTooLong", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' already belongs to another DataTable.. + /// Looks up a localized string similar to A column order hint cannot have an unspecified sort order.. /// - internal static string DataColumns_Add2 { + internal static string SQL_BulkLoadUnspecifiedSortOrder { get { - return ResourceManager.GetString("DataColumns_Add2", resourceCulture); + return ResourceManager.GetString("SQL_BulkLoadUnspecifiedSortOrder", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot have more than one SimpleContent columns in a DataTable.. + /// Looks up a localized string similar to Failed to instantiate a SqlAuthenticationInitializer with type '{0}'.. /// - internal static string DataColumns_Add3 { + internal static string SQL_CannotCreateAuthInitializer { get { - return ResourceManager.GetString("DataColumns_Add3", resourceCulture); + return ResourceManager.GetString("SQL_CannotCreateAuthInitializer", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add a SimpleContent column to a table containing element columns or nested relations.. + /// Looks up a localized string similar to Failed to instantiate an authentication provider with type '{1}' for '{0}'.. /// - internal static string DataColumns_Add4 { + internal static string SQL_CannotCreateAuthProvider { get { - return ResourceManager.GetString("DataColumns_Add4", resourceCulture); + return ResourceManager.GetString("SQL_CannotCreateAuthProvider", resourceCulture); } } /// - /// Looks up a localized string similar to A column named '{0}' already belongs to this DataTable.. + /// Looks up a localized string similar to Cannot create normalizer for '{0}'.. /// - internal static string DataColumns_AddDuplicate { + internal static string SQL_CannotCreateNormalizer { get { - return ResourceManager.GetString("DataColumns_AddDuplicate", resourceCulture); + return ResourceManager.GetString("SQL_CannotCreateNormalizer", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add a column named '{0}': a nested table with the same name already belongs to this DataTable.. + /// Looks up a localized string similar to Cannot find an authentication provider for '{0}'. Install the 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet package (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) to use Active Directory (Entra ID) authentication methods.. /// - internal static string DataColumns_AddDuplicate2 { + internal static string SQL_CannotFindActiveDirectoryAuthProvider { get { - return ResourceManager.GetString("DataColumns_AddDuplicate2", resourceCulture); + return ResourceManager.GetString("SQL_CannotFindActiveDirectoryAuthProvider", resourceCulture); } } /// - /// Looks up a localized string similar to A column named '{0}' already belongs to this DataTable: cannot set a nested table name to the same name.. + /// Looks up a localized string similar to Cannot find an authentication provider for '{0}'.. /// - internal static string DataColumns_AddDuplicate3 { + internal static string SQL_CannotFindAuthProvider { get { - return ResourceManager.GetString("DataColumns_AddDuplicate3", resourceCulture); + return ResourceManager.GetString("SQL_CannotFindAuthProvider", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot find column {0}.. + /// Looks up a localized string similar to Unable to get the address of the distributed transaction coordinator for the server, from the server. Is DTC enabled on the server?. /// - internal static string DataColumns_OutOfRange { + internal static string SQL_CannotGetDTCAddress { get { - return ResourceManager.GetString("DataColumns_OutOfRange", resourceCulture); + return ResourceManager.GetString("SQL_CannotGetDTCAddress", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove a column that doesn't belong to this table.. + /// Looks up a localized string similar to {0} cannot be changed while async operation is in progress.. /// - internal static string DataColumns_Remove { + internal static string SQL_CannotModifyPropertyAsyncOperationInProgress { get { - return ResourceManager.GetString("DataColumns_Remove", resourceCulture); + return ResourceManager.GetString("SQL_CannotModifyPropertyAsyncOperationInProgress", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove this column, because it is part of the parent key for relationship {0}.. + /// Looks up a localized string similar to The '{0}' argument must not be null or empty.. /// - internal static string DataColumns_RemoveChildKey { + internal static string SQL_ChangePasswordArgumentMissing { get { - return ResourceManager.GetString("DataColumns_RemoveChildKey", resourceCulture); + return ResourceManager.GetString("SQL_ChangePasswordArgumentMissing", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove this column, because it is a part of the constraint {0} on the table {1}.. + /// Looks up a localized string similar to ChangePassword can only be used with SQL authentication, not with integrated security.. /// - internal static string DataColumns_RemoveConstraint { + internal static string SQL_ChangePasswordConflictsWithSSPI { get { - return ResourceManager.GetString("DataColumns_RemoveConstraint", resourceCulture); + return ResourceManager.GetString("SQL_ChangePasswordConflictsWithSSPI", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove this column, because it is part of an expression: {0} = {1}.. + /// Looks up a localized string similar to The keyword '{0}' must not be specified in the connectionString argument to ChangePassword.. /// - internal static string DataColumns_RemoveExpression { + internal static string SQL_ChangePasswordUseOfUnallowedKey { get { - return ResourceManager.GetString("DataColumns_RemoveExpression", resourceCulture); + return ResourceManager.GetString("SQL_ChangePasswordUseOfUnallowedKey", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove this column, because it's part of the primary key.. + /// Looks up a localized string similar to The requested operation cannot be completed because the connection has been broken.. /// - internal static string DataColumns_RemovePrimaryKey { + internal static string SQL_ConnectionDoomed { get { - return ResourceManager.GetString("DataColumns_RemovePrimaryKey", resourceCulture); + return ResourceManager.GetString("SQL_ConnectionDoomed", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether this column should restrict its values in the rows of the table to be unique.. + /// Looks up a localized string similar to The connection cannot be used because there is an ongoing operation that must be finished.. /// - internal static string DataColumnUniqueDescr { + internal static string SQL_ConnectionLockedForBcpEvent { get { - return ResourceManager.GetString("DataColumnUniqueDescr", resourceCulture); + return ResourceManager.GetString("SQL_ConnectionLockedForBcpEvent", resourceCulture); } } /// - /// Looks up a localized string similar to This constraint cannot be added since ForeignKey doesn't belong to table {0}.. + /// Looks up a localized string similar to Could not find an empty slot in the connection pool.. /// - internal static string DataConstraint_AddFailed { + internal static string SQL_ConnectionPoolNoEmptySlot { get { - return ResourceManager.GetString("DataConstraint_AddFailed", resourceCulture); + return ResourceManager.GetString("SQL_ConnectionPoolNoEmptySlot", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add primary key constraint since primary key is already set for the table.. + /// Looks up a localized string similar to The connection pool has been shut down.. /// - internal static string DataConstraint_AddPrimaryKeyConstraint { + internal static string SQL_ConnectionPoolShutDown { get { - return ResourceManager.GetString("DataConstraint_AddPrimaryKeyConstraint", resourceCulture); + return ResourceManager.GetString("SQL_ConnectionPoolShutDown", resourceCulture); } } /// - /// Looks up a localized string similar to Property not accessible because '{0}'.. + /// Looks up a localized string similar to The connection attempt timed out.. /// - internal static string DataConstraint_BadObjectPropertyAccess { + internal static string SQL_ConnectTimeout { get { - return ResourceManager.GetString("DataConstraint_BadObjectPropertyAccess", resourceCulture); + return ResourceManager.GetString("SQL_ConnectTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add constraint to DataTable '{0}' which is a child table in two nested relations.. + /// Looks up a localized string similar to Connecting to the context connection using Microsoft.Data.SqlClient is not supported.. /// - internal static string DataConstraint_CantAddConstraintToMultipleNestedTable { + internal static string SQL_ContextConnectionIsUnsupported { get { - return ResourceManager.GetString("DataConstraint_CantAddConstraintToMultipleNestedTable", resourceCulture); + return ResourceManager.GetString("SQL_ContextConnectionIsUnsupported", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot delete this row because constraints are enforced on relation {0}, and deleting this row will strand child rows.. + /// Looks up a localized string similar to Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication={0}'.. /// - internal static string DataConstraint_CascadeDelete { + internal static string SQL_CredentialsNotProvided { get { - return ResourceManager.GetString("DataConstraint_CascadeDelete", resourceCulture); + return ResourceManager.GetString("SQL_CredentialsNotProvided", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot make this change because constraints are enforced on relation {0}, and changing this value will strand child rows.. + /// Looks up a localized string similar to The Collation specified by SQL Server is not supported.. /// - internal static string DataConstraint_CascadeUpdate { + internal static string SQL_CultureIdError { get { - return ResourceManager.GetString("DataConstraint_CascadeUpdate", resourceCulture); + return ResourceManager.GetString("SQL_CultureIdError", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot clear table {0} because ForeignKeyConstraint {1} enforces constraints and there are child rows in {2}.. + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Device Code Flow' with 'User ID', 'UID', 'Password' or 'PWD' connection string keywords.. /// - internal static string DataConstraint_ClearParentTable { + internal static string SQL_DeviceFlowWithUsernamePassword { get { - return ResourceManager.GetString("DataConstraint_ClearParentTable", resourceCulture); + return ResourceManager.GetString("SQL_DeviceFlowWithUsernamePassword", resourceCulture); } } /// - /// Looks up a localized string similar to Constraint matches constraint named {0} already in collection.. + /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0}; handshake={1}; [Login] initialization={2}; . /// - internal static string DataConstraint_Duplicate { + internal static string SQL_Duration_Login_Begin { get { - return ResourceManager.GetString("DataConstraint_Duplicate", resourceCulture); + return ResourceManager.GetString("SQL_Duration_Login_Begin", resourceCulture); } } /// - /// Looks up a localized string similar to A Constraint named '{0}' already belongs to this DataTable.. + /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0}; handshake={1}; [Login] initialization={2}; authentication={3}; . /// - internal static string DataConstraint_DuplicateName { + internal static string SQL_Duration_Login_ProcessConnectionAuth { get { - return ResourceManager.GetString("DataConstraint_DuplicateName", resourceCulture); + return ResourceManager.GetString("SQL_Duration_Login_ProcessConnectionAuth", resourceCulture); } } /// - /// Looks up a localized string similar to ForeignKeyConstraint {0} requires the child key values ({1}) to exist in the parent table.. + /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0}; handshake={1}; [Login] initialization={2}; authentication={3}; [Post-Login] complete={4}; . /// - internal static string DataConstraint_ForeignKeyViolation { + internal static string SQL_Duration_PostLogin { get { - return ResourceManager.GetString("DataConstraint_ForeignKeyViolation", resourceCulture); + return ResourceManager.GetString("SQL_Duration_PostLogin", resourceCulture); } } /// - /// Looks up a localized string similar to These columns don't point to this table.. + /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0};. /// - internal static string DataConstraint_ForeignTable { + internal static string SQL_Duration_PreLogin_Begin { get { - return ResourceManager.GetString("DataConstraint_ForeignTable", resourceCulture); + return ResourceManager.GetString("SQL_Duration_PreLogin_Begin", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove unique constraint '{0}'. Remove foreign key constraint '{1}' first.. + /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0}; handshake={1}; . /// - internal static string DataConstraint_NeededForForeignKeyConstraint { + internal static string SQL_Duration_PreLoginHandshake { get { - return ResourceManager.GetString("DataConstraint_NeededForForeignKeyConstraint", resourceCulture); + return ResourceManager.GetString("SQL_Duration_PreLoginHandshake", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the name of a constraint to empty string when it is in the ConstraintCollection.. + /// Looks up a localized string similar to The instance of SQL Server you attempted to connect to requires encryption but this machine does not support it.. /// - internal static string DataConstraint_NoName { + internal static string SQL_EncryptionNotSupportedByClient { get { - return ResourceManager.GetString("DataConstraint_NoName", resourceCulture); + return ResourceManager.GetString("SQL_EncryptionNotSupportedByClient", resourceCulture); } } /// - /// Looks up a localized string similar to Constraint '{0}' does not belong to this DataTable.. + /// Looks up a localized string similar to The instance of SQL Server you attempted to connect to does not support encryption.. /// - internal static string DataConstraint_NotInTheTable { + internal static string SQL_EncryptionNotSupportedByServer { get { - return ResourceManager.GetString("DataConstraint_NotInTheTable", resourceCulture); + return ResourceManager.GetString("SQL_EncryptionNotSupportedByServer", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot find constraint {0}.. + /// Looks up a localized string similar to Number of fields in record '{0}' does not match the number in the original record.. /// - internal static string DataConstraint_OutOfRange { + internal static string SQL_EnumeratedRecordFieldCountChanged { get { - return ResourceManager.GetString("DataConstraint_OutOfRange", resourceCulture); + return ResourceManager.GetString("SQL_EnumeratedRecordFieldCountChanged", resourceCulture); } } /// - /// Looks up a localized string similar to This constraint cannot be enabled as not all values have corresponding parent values.. + /// Looks up a localized string similar to Metadata for field '{0}' of record '{1}' did not match the original record's metadata.. /// - internal static string DataConstraint_ParentValues { + internal static string SQL_EnumeratedRecordMetaDataChanged { get { - return ResourceManager.GetString("DataConstraint_ParentValues", resourceCulture); + return ResourceManager.GetString("SQL_EnumeratedRecordMetaDataChanged", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove a constraint that doesn't belong to this table.. + /// Looks up a localized string similar to ClientConnectionId:{0}. /// - internal static string DataConstraint_RemoveFailed { + internal static string SQL_ExClientConnectionId { get { - return ResourceManager.GetString("DataConstraint_RemoveFailed", resourceCulture); + return ResourceManager.GetString("SQL_ExClientConnectionId", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove this row because it has child rows, and constraints on relation {0} are enforced.. + /// Looks up a localized string similar to Error Number:{0},State:{1},Class:{2}. /// - internal static string DataConstraint_RemoveParentRow { + internal static string SQL_ExErrorNumberStateClass { get { - return ResourceManager.GetString("DataConstraint_RemoveParentRow", resourceCulture); + return ResourceManager.GetString("SQL_ExErrorNumberStateClass", resourceCulture); } } /// - /// Looks up a localized string similar to These columns don't currently have unique values.. + /// Looks up a localized string similar to ClientConnectionId before routing:{0}. /// - internal static string DataConstraint_UniqueViolation { + internal static string SQL_ExOriginalClientConnectionId { get { - return ResourceManager.GetString("DataConstraint_UniqueViolation", resourceCulture); + return ResourceManager.GetString("SQL_ExOriginalClientConnectionId", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot enforce constraints on constraint {0}.. + /// Looks up a localized string similar to Routing Destination:{0}. /// - internal static string DataConstraint_Violation { + internal static string SQL_ExRoutingDestination { get { - return ResourceManager.GetString("DataConstraint_Violation", resourceCulture); + return ResourceManager.GetString("SQL_ExRoutingDestination", resourceCulture); } } /// - /// Looks up a localized string similar to Column '{0}' is constrained to be unique. Value '{1}' is already present.. + /// Looks up a localized string similar to Attempted to pend a read when the fail async pends test hook was enabled.. /// - internal static string DataConstraint_ViolationValue { + internal static string SQL_FailAsyncPendsEnabled { get { - return ResourceManager.GetString("DataConstraint_ViolationValue", resourceCulture); + return ResourceManager.GetString("SQL_FailAsyncPendsEnabled", resourceCulture); } } /// - /// Looks up a localized string similar to This type of node cannot be cloned: {0}.. + /// Looks up a localized string similar to Globalization Invariant Mode is not supported.. /// - internal static string DataDom_CloneNode { + internal static string SQL_GlobalizationInvariantModeNotSupported { get { - return ResourceManager.GetString("DataDom_CloneNode", resourceCulture); + return ResourceManager.GetString("SQL_GlobalizationInvariantModeNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the ColumnMapping property once the associated DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to Instance failure.. /// - internal static string DataDom_ColumnMappingChange { + internal static string SQL_InstanceFailure { get { - return ResourceManager.GetString("DataDom_ColumnMappingChange", resourceCulture); + return ResourceManager.GetString("SQL_InstanceFailure", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the column name once the associated DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Integrated' with 'Password' or 'PWD' connection string keywords.. /// - internal static string DataDom_ColumnNameChange { + internal static string SQL_IntegratedWithPassword { get { - return ResourceManager.GetString("DataDom_ColumnNameChange", resourceCulture); + return ResourceManager.GetString("SQL_IntegratedWithPassword", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the column namespace once the associated DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Interactive' with 'Password' or 'PWD' connection string keywords.. /// - internal static string DataDom_ColumnNamespaceChange { + internal static string SQL_InteractiveWithPassword { get { - return ResourceManager.GetString("DataDom_ColumnNamespaceChange", resourceCulture); + return ResourceManager.GetString("SQL_InteractiveWithPassword", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the DataSet name once the DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to Internal Error. /// - internal static string DataDom_DataSetNameChange { + internal static string Sql_InternalError { get { - return ResourceManager.GetString("DataDom_DataSetNameChange", resourceCulture); + return ResourceManager.GetString("Sql_InternalError", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add, remove, or change Nested relations from the DataSet once the DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to Buffer offset '{1}' plus the bytes available '{0}' is greater than the length of the passed in buffer.. /// - internal static string DataDom_DataSetNestedRelationsChange { + internal static string SQL_InvalidBufferSizeOrIndex { get { - return ResourceManager.GetString("DataDom_DataSetNestedRelationsChange", resourceCulture); + return ResourceManager.GetString("SQL_InvalidBufferSizeOrIndex", resourceCulture); } } /// - /// Looks up a localized string similar to The DataSet parameter is invalid. It cannot be null.. + /// Looks up a localized string similar to Data length '{0}' is less than 0.. /// - internal static string DataDom_DataSetNull { + internal static string SQL_InvalidDataLength { get { - return ResourceManager.GetString("DataDom_DataSetNull", resourceCulture); + return ResourceManager.GetString("SQL_InvalidDataLength", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add or remove tables from the DataSet once the DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to Invalid internal packet size:. /// - internal static string DataDom_DataSetTablesChange { + internal static string SQL_InvalidInternalPacketSize { get { - return ResourceManager.GetString("DataDom_DataSetTablesChange", resourceCulture); + return ResourceManager.GetString("SQL_InvalidInternalPacketSize", resourceCulture); } } /// - /// Looks up a localized string similar to Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations.. + /// Looks up a localized string similar to Invalid 'Packet Size'. The value must be an integer >= 512 and <= 32768.. /// - internal static string DataDom_EnforceConstraintsShouldBeOff { + internal static string SQL_InvalidPacketSizeValue { get { - return ResourceManager.GetString("DataDom_EnforceConstraintsShouldBeOff", resourceCulture); + return ResourceManager.GetString("SQL_InvalidPacketSizeValue", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid foliation.. + /// Looks up a localized string similar to The length of the parameter '{0}' exceeds the limit of 128 characters.. /// - internal static string DataDom_Foliation { + internal static string SQL_InvalidParameterNameLength { get { - return ResourceManager.GetString("DataDom_Foliation", resourceCulture); + return ResourceManager.GetString("SQL_InvalidParameterNameLength", resourceCulture); } } /// - /// Looks up a localized string similar to DataSet can be associated with at most one XmlDataDocument. Cannot associate the DataSet with the current XmlDataDocument because the DataSet is already associated with another XmlDataDocument.. + /// Looks up a localized string similar to Invalid 3 part name format for TypeName.. /// - internal static string DataDom_MultipleDataSet { + internal static string SQL_InvalidParameterTypeNameFormat { get { - return ResourceManager.GetString("DataDom_MultipleDataSet", resourceCulture); + return ResourceManager.GetString("SQL_InvalidParameterTypeNameFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot load XmlDataDocument if it already contains data. Please use a new XmlDataDocument.. + /// Looks up a localized string similar to Server {0}, database {1} is not configured for database mirroring.. /// - internal static string DataDom_MultipleLoad { + internal static string SQL_InvalidPartnerConfiguration { get { - return ResourceManager.GetString("DataDom_MultipleLoad", resourceCulture); + return ResourceManager.GetString("SQL_InvalidPartnerConfiguration", resourceCulture); } } /// - /// Looks up a localized string similar to Clear function on DateSet and DataTable is not supported on XmlDataDocument.. + /// Looks up a localized string similar to Invalid attempt to read when no data is present.. /// - internal static string DataDom_NotSupport_Clear { + internal static string SQL_InvalidRead { get { - return ResourceManager.GetString("DataDom_NotSupport_Clear", resourceCulture); + return ResourceManager.GetString("SQL_InvalidRead", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create entity references on DataDocument.. + /// Looks up a localized string similar to Unsupported SQL Server version. The .NET Framework SqlClient Data Provider can only be used with SQL Server versions 7.0 and later.. /// - internal static string DataDom_NotSupport_EntRef { + internal static string SQL_InvalidSQLServerVersionUnknown { get { - return ResourceManager.GetString("DataDom_NotSupport_EntRef", resourceCulture); + return ResourceManager.GetString("SQL_InvalidSQLServerVersionUnknown", resourceCulture); } } /// - /// Looks up a localized string similar to GetElementById() is not supported on DataDocument.. + /// Looks up a localized string similar to Invalid SSPI packet size.. /// - internal static string DataDom_NotSupport_GetElementById { + internal static string SQL_InvalidSSPIPacketSize { get { - return ResourceManager.GetString("DataDom_NotSupport_GetElementById", resourceCulture); + return ResourceManager.GetString("SQL_InvalidSSPIPacketSize", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add or remove columns from the table once the DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to Invalid Packet Size.. /// - internal static string DataDom_TableColumnsChange { + internal static string SQL_InvalidTDSPacketSize { get { - return ResourceManager.GetString("DataDom_TableColumnsChange", resourceCulture); + return ResourceManager.GetString("SQL_InvalidTDSPacketSize", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the table name once the associated DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to The SQL Server instance returned an invalid or unsupported protocol version during login negotiation.. /// - internal static string DataDom_TableNameChange { + internal static string SQL_InvalidTDSVersion { get { - return ResourceManager.GetString("DataDom_TableNameChange", resourceCulture); + return ResourceManager.GetString("SQL_InvalidTDSVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the table namespace once the associated DataSet is mapped to a loaded XML document.. + /// Looks up a localized string similar to Invalid 3 part name format for UdtTypeName.. /// - internal static string DataDom_TableNamespaceChange { + internal static string SQL_InvalidUdt3PartNameFormat { get { - return ResourceManager.GetString("DataDom_TableNamespaceChange", resourceCulture); + return ResourceManager.GetString("SQL_InvalidUdt3PartNameFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Find finds a row based on a Sort order, and no Sort order is specified.. + /// Looks up a localized string similar to The type '{0}' has an invalid size.. /// - internal static string DataIndex_FindWithoutSortOrder { + internal static string SQL_InvalidUdtSize { get { - return ResourceManager.GetString("DataIndex_FindWithoutSortOrder", resourceCulture); + return ResourceManager.GetString("SQL_InvalidUdtSize", resourceCulture); } } /// - /// Looks up a localized string similar to Expecting {0} value(s) for the key being indexed, but received {1} value(s).. + /// 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 DataIndex_KeyLength { + internal static string SQL_JsonDocumentNotSupportedOnColumnType { get { - return ResourceManager.GetString("DataIndex_KeyLength", resourceCulture); + return ResourceManager.GetString("SQL_JsonDocumentNotSupportedOnColumnType", resourceCulture); } } /// - /// Looks up a localized string similar to The RowStates parameter must be set to a valid combination of values from the DataViewRowState enumeration.. + /// Looks up a localized string similar to The connection does not support MultipleActiveResultSets.. /// - internal static string DataIndex_RecordStateRange { + internal static string SQL_MarsUnsupportedOnConnection { get { - return ResourceManager.GetString("DataIndex_RecordStateRange", resourceCulture); + return ResourceManager.GetString("SQL_MarsUnsupportedOnConnection", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create a Key when the same column is listed more than once: '{0}'. + /// Looks up a localized string similar to SqlDbType.SmallMoney overflow. Value '{0}' is out of range. Must be between -214,748.3648 and 214,748.3647.. /// - internal static string DataKey_DuplicateColumns { + internal static string SQL_MoneyOverflow { get { - return ResourceManager.GetString("DataKey_DuplicateColumns", resourceCulture); + return ResourceManager.GetString("SQL_MoneyOverflow", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot have 0 columns.. + /// Looks up a localized string similar to Failed to authenticate the user {0} in Active Directory (Authentication={1}).. /// - internal static string DataKey_NoColumns { + internal static string SQL_MSALFailure { get { - return ResourceManager.GetString("DataKey_NoColumns", resourceCulture); + return ResourceManager.GetString("SQL_MSALFailure", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove unique constraint since it's the primary key of a table.. + /// Looks up a localized string similar to Error code 0x{0}. /// - internal static string DataKey_RemovePrimaryKey { + internal static string SQL_MSALInnerException { get { - return ResourceManager.GetString("DataKey_RemovePrimaryKey", resourceCulture); + return ResourceManager.GetString("SQL_MSALInnerException", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove unique constraint since it's the primary key of table {0}.. + /// Looks up a localized string similar to MultiplexPackets cannot return both consumePartialPacket and consumeInputDirectly.. /// - internal static string DataKey_RemovePrimaryKey1 { + internal static string SQL_Multiplexer_ConflictingConsumeModes { get { - return ResourceManager.GetString("DataKey_RemovePrimaryKey1", resourceCulture); + return ResourceManager.GetString("SQL_Multiplexer_ConflictingConsumeModes", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create a Key from Columns that belong to different tables.. + /// Looks up a localized string similar to Partial packet cannot be non-null when setting to non-null.. /// - internal static string DataKey_TableMismatch { + internal static string SQL_Multiplexer_PartialPacketNotNull { get { - return ResourceManager.GetString("DataKey_TableMismatch", resourceCulture); + return ResourceManager.GetString("SQL_Multiplexer_PartialPacketNotNull", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot have more than {0} columns.. + /// Looks up a localized string similar to The keyword 'Network Library' is not supported on this platform, prefix the 'Data Source' with the protocol desired instead ('tcp:' for a TCP connection, or 'np:' for a Named Pipe connection).. /// - internal static string DataKey_TooManyColumns { + internal static string SQL_NetworkLibraryNotSupported { get { - return ResourceManager.GetString("DataKey_TooManyColumns", resourceCulture); + return ResourceManager.GetString("SQL_NetworkLibraryNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to <target>.{0} and <source>.{0} have conflicting properties: DataType property mismatch.. + /// Looks up a localized string similar to Invalid attempt to GetBytes on column '{0}'. The GetBytes function can only be used on columns of type Text, NText, or Image.. /// - internal static string DataMerge_DataTypeMismatch { + internal static string SQL_NonBlobColumn { get { - return ResourceManager.GetString("DataMerge_DataTypeMismatch", resourceCulture); + return ResourceManager.GetString("SQL_NonBlobColumn", resourceCulture); } } /// - /// Looks up a localized string similar to Target table {0} missing definition for column {1}.. + /// Looks up a localized string similar to Invalid attempt to GetChars on column '{0}'. The GetChars function can only be used on columns of type Text, NText, Xml, VarChar or NVarChar.. /// - internal static string DataMerge_MissingColumnDefinition { + internal static string SQL_NonCharColumn { get { - return ResourceManager.GetString("DataMerge_MissingColumnDefinition", resourceCulture); + return ResourceManager.GetString("SQL_NonCharColumn", resourceCulture); } } /// - /// Looks up a localized string similar to Target DataSet missing {0} {1}.. + /// Looks up a localized string similar to Cannot use 'Authentication={0}' with 'Password' or 'PWD' connection string keywords.. /// - internal static string DataMerge_MissingConstraint { + internal static string SQL_NonInteractiveWithPassword { get { - return ResourceManager.GetString("DataMerge_MissingConstraint", resourceCulture); + return ResourceManager.GetString("SQL_NonInteractiveWithPassword", resourceCulture); } } /// - /// Looks up a localized string similar to Target DataSet missing definition for {0}.. + /// Looks up a localized string similar to SSE Instance re-direction is not supported for non-local user instances.. /// - internal static string DataMerge_MissingDefinition { + internal static string SQL_NonLocalSSEInstance { get { - return ResourceManager.GetString("DataMerge_MissingDefinition", resourceCulture); + return ResourceManager.GetString("SQL_NonLocalSSEInstance", resourceCulture); } } /// - /// Looks up a localized string similar to PrimaryKey column {0} does not exist in source Table.. + /// Looks up a localized string similar to Invalid command sent to ExecuteXmlReader. The command must return an Xml result.. /// - internal static string DataMerge_MissingPrimaryKeyColumnInSource { + internal static string SQL_NonXmlResult { get { - return ResourceManager.GetString("DataMerge_MissingPrimaryKeyColumnInSource", resourceCulture); + return ResourceManager.GetString("SQL_NonXmlResult", resourceCulture); } } /// - /// Looks up a localized string similar to Mismatch columns in the PrimaryKey : <target>.{0} versus <source>.{1}.. + /// Looks up a localized string similar to The {0} enumeration value, {1}, is not supported by the .NET Framework SqlClient Data Provider.. /// - internal static string DataMerge_PrimaryKeyColumnsMismatch { + internal static string SQL_NotSupportedEnumerationValue { get { - return ResourceManager.GetString("DataMerge_PrimaryKeyColumnsMismatch", resourceCulture); + return ResourceManager.GetString("SQL_NotSupportedEnumerationValue", resourceCulture); } } /// - /// Looks up a localized string similar to <target>.PrimaryKey and <source>.PrimaryKey have different Length.. + /// Looks up a localized string similar to Invalid transaction or invalid name for a point at which to save within the transaction.. /// - internal static string DataMerge_PrimaryKeyMismatch { + internal static string SQL_NullEmptyTransactionName { get { - return ResourceManager.GetString("DataMerge_PrimaryKeyMismatch", resourceCulture); + return ResourceManager.GetString("SQL_NullEmptyTransactionName", resourceCulture); } } /// - /// Looks up a localized string similar to Relation {0} cannot be merged, because keys have mismatch columns.. + /// Looks up a localized string similar to Can only rent from a pool if an onCreate delegate is available.. /// - internal static string DataMerge_RelationKeyColumnsMismatch { + internal static string SQL_ObjectPoolNoCreateDelegate { get { - return ResourceManager.GetString("DataMerge_RelationKeyColumnsMismatch", resourceCulture); + return ResourceManager.GetString("SQL_ObjectPoolNoCreateDelegate", resourceCulture); } } /// - /// Looks up a localized string similar to A relation already exists for these child columns.. + /// Looks up a localized string similar to Open result count exceeded.. /// - internal static string DataRelation_AlreadyExists { + internal static string SQL_OpenResultCountExceeded { get { - return ResourceManager.GetString("DataRelation_AlreadyExists", resourceCulture); + return ResourceManager.GetString("SQL_OpenResultCountExceeded", resourceCulture); } } /// - /// Looks up a localized string similar to This relation already belongs to another DataSet.. + /// Looks up a localized string similar to Operation cancelled by user.. /// - internal static string DataRelation_AlreadyInOtherDataSet { + internal static string SQL_OperationCancelled { get { - return ResourceManager.GetString("DataRelation_AlreadyInOtherDataSet", resourceCulture); + return ResourceManager.GetString("SQL_OperationCancelled", resourceCulture); } } /// - /// Looks up a localized string similar to This relation already belongs to this DataSet.. + /// Looks up a localized string similar to Cannot get RequiredLength when HasDataLength is false.. /// - internal static string DataRelation_AlreadyInTheDataSet { + internal static string SQL_Packet_RequiredLengthUnavailable { get { - return ResourceManager.GetString("DataRelation_AlreadyInTheDataSet", resourceCulture); + return ResourceManager.GetString("SQL_Packet_RequiredLengthUnavailable", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add a DataRelation or Constraint that has different Locale or CaseSensitive settings between its parent and child tables.. + /// Looks up a localized string similar to Packet modification detected. Hash from packet creation does not match hash from packet check.. /// - internal static string DataRelation_CaseLocaleMismatch { + internal static string SQL_PacketHashMismatch { get { - return ResourceManager.GetString("DataRelation_CaseLocaleMismatch", resourceCulture); + return ResourceManager.GetString("SQL_PacketHashMismatch", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add a relation to this table's ParentRelation collection where this table isn't the child table.. + /// Looks up a localized string similar to Packet modification detected. Hash is null but packet contains non-null buffer.. /// - internal static string DataRelation_ChildTableMismatch { + internal static string SQL_PacketHashNullWithNonNullBuffer { get { - return ResourceManager.GetString("DataRelation_ChildTableMismatch", resourceCulture); + return ResourceManager.GetString("SQL_PacketHashNullWithNonNullBuffer", resourceCulture); } } /// - /// Looks up a localized string similar to Parent Columns and Child Columns don't have type-matching columns.. + /// Looks up a localized string similar to Parameter '{0}' cannot be null or empty.. /// - internal static string DataRelation_ColumnsTypeMismatch { + internal static string SQL_ParameterCannotBeEmpty { get { - return ResourceManager.GetString("DataRelation_ColumnsTypeMismatch", resourceCulture); + return ResourceManager.GetString("SQL_ParameterCannotBeEmpty", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot have a relationship between tables in different DataSets.. + /// Looks up a localized string similar to Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command.. /// - internal static string DataRelation_DataSetMismatch { + internal static string SQL_ParameterDirectionInvalidForOptimizedBinding { get { - return ResourceManager.GetString("DataRelation_DataSetMismatch", resourceCulture); + return ResourceManager.GetString("SQL_ParameterDirectionInvalidForOptimizedBinding", resourceCulture); } } /// - /// Looks up a localized string similar to This relation doesn't belong to this relation collection.. + /// Looks up a localized string similar to Parameter '{0}' exceeds the size limit for the sql_variant datatype.. /// - internal static string DataRelation_DoesNotExist { + internal static string SQL_ParameterInvalidVariant { get { - return ResourceManager.GetString("DataRelation_DoesNotExist", resourceCulture); + return ResourceManager.GetString("SQL_ParameterInvalidVariant", resourceCulture); } } /// - /// Looks up a localized string similar to A Relation named '{0}' already belongs to this DataSet.. + /// Looks up a localized string similar to The {0} type parameter '{1}' must have a valid type name.. /// - internal static string DataRelation_DuplicateName { + internal static string SQL_ParameterTypeNameRequired { get { - return ResourceManager.GetString("DataRelation_DuplicateName", resourceCulture); + return ResourceManager.GetString("SQL_ParameterTypeNameRequired", resourceCulture); } } /// - /// Looks up a localized string similar to This relation should connect two tables in this DataSet to be added to this DataSet.. + /// Looks up a localized string similar to Internal connection fatal error.. /// - internal static string DataRelation_ForeignDataSet { + internal static string SQL_ParsingError { get { - return ResourceManager.GetString("DataRelation_ForeignDataSet", resourceCulture); + return ResourceManager.GetString("SQL_ParsingError", resourceCulture); } } /// - /// Looks up a localized string similar to The row doesn't belong to the same DataSet as this relation.. + /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Authentication Library Type: {1}. /// - internal static string DataRelation_ForeignRow { + internal static string SQL_ParsingErrorAuthLibraryType { get { - return ResourceManager.GetString("DataRelation_ForeignRow", resourceCulture); + return ResourceManager.GetString("SQL_ParsingErrorAuthLibraryType", resourceCulture); } } /// - /// Looks up a localized string similar to GetChildRows requires a row whose Table is {0}, but the specified row's Table is {1}.. + /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Feature Id: {1}. /// - internal static string DataRelation_ForeignTable { + internal static string SQL_ParsingErrorFeatureId { get { - return ResourceManager.GetString("DataRelation_ForeignTable", resourceCulture); + return ResourceManager.GetString("SQL_ParsingErrorFeatureId", resourceCulture); } } /// - /// Looks up a localized string similar to GetParentRow requires a row whose Table is {0}, but the specified row's Table is {1}.. + /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Length: {1}. /// - internal static string DataRelation_GetParentRowTableMismatch { + internal static string SQL_ParsingErrorLength { get { - return ResourceManager.GetString("DataRelation_GetParentRowTableMismatch", resourceCulture); + return ResourceManager.GetString("SQL_ParsingErrorLength", resourceCulture); } } /// - /// Looks up a localized string similar to Nested table '{0}' with empty namespace cannot have multiple parent tables in different namespaces.. + /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Offset: {1}. /// - internal static string DataRelation_InValidNamespaceInNestedRelation { + internal static string SQL_ParsingErrorOffset { get { - return ResourceManager.GetString("DataRelation_InValidNamespaceInNestedRelation", resourceCulture); + return ResourceManager.GetString("SQL_ParsingErrorOffset", resourceCulture); } } /// - /// Looks up a localized string similar to Nested table '{0}' which inherits its namespace cannot have multiple parent tables in different namespaces.. + /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Status: {1}. /// - internal static string DataRelation_InValidNestedRelation { + internal static string SQL_ParsingErrorStatus { get { - return ResourceManager.GetString("DataRelation_InValidNestedRelation", resourceCulture); + return ResourceManager.GetString("SQL_ParsingErrorStatus", resourceCulture); } } /// - /// Looks up a localized string similar to ParentKey and ChildKey are identical.. + /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Token : {1}. /// - internal static string DataRelation_KeyColumnsIdentical { + internal static string SQL_ParsingErrorToken { get { - return ResourceManager.GetString("DataRelation_KeyColumnsIdentical", resourceCulture); + return ResourceManager.GetString("SQL_ParsingErrorToken", resourceCulture); } } /// - /// Looks up a localized string similar to ParentColumns and ChildColumns should be the same length.. + /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Value: {1}. /// - internal static string DataRelation_KeyLengthMismatch { + internal static string SQL_ParsingErrorValue { get { - return ResourceManager.GetString("DataRelation_KeyLengthMismatch", resourceCulture); + return ResourceManager.GetString("SQL_ParsingErrorValue", resourceCulture); } } /// - /// Looks up a localized string similar to ParentColumns and ChildColumns must not be zero length.. + /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}. /// - internal static string DataRelation_KeyZeroLength { + internal static string SQL_ParsingErrorWithState { get { - return ResourceManager.GetString("DataRelation_KeyZeroLength", resourceCulture); + return ResourceManager.GetString("SQL_ParsingErrorWithState", resourceCulture); } } /// - /// Looks up a localized string similar to The table ({0}) cannot be the child table to itself in nested relations.. + /// Looks up a localized string similar to The command execution cannot proceed due to a pending asynchronous operation already in progress.. /// - internal static string DataRelation_LoopInNestedRelations { + internal static string SQL_PendingBeginXXXExists { get { - return ResourceManager.GetString("DataRelation_LoopInNestedRelations", resourceCulture); + return ResourceManager.GetString("SQL_PendingBeginXXXExists", resourceCulture); } } /// - /// Looks up a localized string similar to RelationName is required when it is part of a DataSet.. + /// Looks up a localized string similar to Precision value '{0}' is either less than 0 or greater than the maximum allowed precision of 38.. /// - internal static string DataRelation_NoName { + internal static string SQL_PrecisionValueOutOfRange { get { - return ResourceManager.GetString("DataRelation_NoName", resourceCulture); + return ResourceManager.GetString("SQL_PrecisionValueOutOfRange", resourceCulture); } } /// - /// Looks up a localized string similar to Relation {0} does not belong to this DataSet.. + /// Looks up a localized string similar to Certificate failed chain validation. Error(s): '{0}'.. /// - internal static string DataRelation_NotInTheDataSet { + internal static string SQL_RemoteCertificateChainErrors { get { - return ResourceManager.GetString("DataRelation_NotInTheDataSet", resourceCulture); + return ResourceManager.GetString("SQL_RemoteCertificateChainErrors", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot find relation {0}.. + /// Looks up a localized string similar to The certificate provided by the server does not match the certificate provided by the ServerCertificate option.. /// - internal static string DataRelation_OutOfRange { + internal static string SQL_RemoteCertificateDoesNotMatchServerCertificate { get { - return ResourceManager.GetString("DataRelation_OutOfRange", resourceCulture); + return ResourceManager.GetString("SQL_RemoteCertificateDoesNotMatchServerCertificate", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create a DataRelation if Parent or Child Columns are not in a DataSet.. + /// Looks up a localized string similar to Certificate name mismatch. The provided 'DataSource' or 'HostNameInCertificate' does not match the name in the certificate.. /// - internal static string DataRelation_ParentOrChildColumnsDoNotHaveDataSet { + internal static string SQL_RemoteCertificateNameMismatch { get { - return ResourceManager.GetString("DataRelation_ParentOrChildColumnsDoNotHaveDataSet", resourceCulture); + return ResourceManager.GetString("SQL_RemoteCertificateNameMismatch", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add a relation to this table's ChildRelation collection where this table isn't the parent table.. + /// Looks up a localized string similar to Certificate not available while validating the certificate.. /// - internal static string DataRelation_ParentTableMismatch { + internal static string SQL_RemoteCertificateNotAvailable { get { - return ResourceManager.GetString("DataRelation_ParentTableMismatch", resourceCulture); + return ResourceManager.GetString("SQL_RemoteCertificateNotAvailable", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set the 'Nested' property to false for this relation.. + /// Looks up a localized string similar to Scale value '{0}' is either less than 0 or greater than the maximum allowed scale of 38.. /// - internal static string DataRelation_RelationNestedReadOnly { + internal static string SQL_ScaleValueOutOfRange { get { - return ResourceManager.GetString("DataRelation_RelationNestedReadOnly", resourceCulture); + return ResourceManager.GetString("SQL_ScaleValueOutOfRange", resourceCulture); } } /// - /// Looks up a localized string similar to SetParentRow requires a child row whose Table is {0}, but the specified row's Table is {1}.. + /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication=Active Directory Device Code Flow' has been specified in the connection string.. /// - internal static string DataRelation_SetParentRowTableMismatch { + internal static string SQL_SettingCredentialWithDeviceFlow { get { - return ResourceManager.GetString("DataRelation_SetParentRowTableMismatch", resourceCulture); + return ResourceManager.GetString("SQL_SettingCredentialWithDeviceFlow", resourceCulture); } } /// - /// Looks up a localized string similar to The same table '{0}' cannot be the child table in two nested relations.. + /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication=Active Directory Integrated' has been specified in the connection string.. /// - internal static string DataRelation_TableCantBeNestedInTwoTables { + internal static string SQL_SettingCredentialWithIntegrated { get { - return ResourceManager.GetString("DataRelation_TableCantBeNestedInTwoTables", resourceCulture); + return ResourceManager.GetString("SQL_SettingCredentialWithIntegrated", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create a collection on a null table.. + /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.. /// - internal static string DataRelation_TableNull { + internal static string SQL_SettingCredentialWithInteractive { get { - return ResourceManager.GetString("DataRelation_TableNull", resourceCulture); + return ResourceManager.GetString("SQL_SettingCredentialWithInteractive", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create a relation between tables in different DataSets.. + /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication={0}' has been specified in the connection string.. /// - internal static string DataRelation_TablesInDifferentSets { + internal static string SQL_SettingCredentialWithNonInteractive { get { - return ResourceManager.GetString("DataRelation_TablesInDifferentSets", resourceCulture); + return ResourceManager.GetString("SQL_SettingCredentialWithNonInteractive", resourceCulture); } } /// - /// Looks up a localized string similar to The table this collection displays relations for has been removed from its DataSet.. + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Device Code Flow', if the Credential property has been set.. /// - internal static string DataRelation_TableWasRemoved { + internal static string SQL_SettingDeviceFlowWithCredential { get { - return ResourceManager.GetString("DataRelation_TableWasRemoved", resourceCulture); + return ResourceManager.GetString("SQL_SettingDeviceFlowWithCredential", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the child columns of this relation.. + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Integrated', if the Credential property has been set.. /// - internal static string DataRelationChildColumnsDescr { + internal static string SQL_SettingIntegratedWithCredential { get { - return ResourceManager.GetString("DataRelationChildColumnsDescr", resourceCulture); + return ResourceManager.GetString("SQL_SettingIntegratedWithCredential", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether relations are nested.. + /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.. /// - internal static string DataRelationNested { + internal static string SQL_SettingInteractiveWithCredential { get { - return ResourceManager.GetString("DataRelationNested", resourceCulture); + return ResourceManager.GetString("SQL_SettingInteractiveWithCredential", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the parent columns of this relation.. + /// Looks up a localized string similar to Cannot use 'Authentication={0}', if the Credential property has been set.. /// - internal static string DataRelationParentColumnsDescr { + internal static string SQL_SettingNonInteractiveWithCredential { get { - return ResourceManager.GetString("DataRelationParentColumnsDescr", resourceCulture); + return ResourceManager.GetString("SQL_SettingNonInteractiveWithCredential", resourceCulture); } } /// - /// Looks up a localized string similar to The name used to look up this relation in the Relations collection of a DataSet.. + /// Looks up a localized string similar to A severe error occurred on the current command. The results, if any, should be discarded.. /// - internal static string DataRelationRelationNameDescr { + internal static string SQL_SevereError { get { - return ResourceManager.GetString("DataRelationRelationNameDescr", resourceCulture); + return ResourceManager.GetString("SQL_SevereError", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot delete this row since it's already deleted.. + /// Looks up a localized string similar to SqlDbType.SmallDateTime overflow. Value '{0}' is out of range. Must be between 1/1/1900 12:00:00 AM and 6/6/2079 11:59:59 PM.. /// - internal static string DataRow_AlreadyDeleted { + internal static string SQL_SmallDateTimeOverflow { get { - return ResourceManager.GetString("DataRow_AlreadyDeleted", resourceCulture); + return ResourceManager.GetString("SQL_SmallDateTimeOverflow", resourceCulture); } } /// - /// Looks up a localized string similar to This row already belongs to another table.. + /// Looks up a localized string similar to Memory allocation for internal connection failed.. /// - internal static string DataRow_AlreadyInOtherCollection { + internal static string SQL_SNIPacketAllocationFailure { get { - return ResourceManager.GetString("DataRow_AlreadyInOtherCollection", resourceCulture); + return ResourceManager.GetString("SQL_SNIPacketAllocationFailure", resourceCulture); } } /// - /// Looks up a localized string similar to This row already belongs to this table.. + /// Looks up a localized string similar to Socket did not throw expected '{0}' with error code '{1}'.. /// - internal static string DataRow_AlreadyInTheCollection { + internal static string SQL_SocketDidNotThrow { get { - return ResourceManager.GetString("DataRow_AlreadyInTheCollection", resourceCulture); + return ResourceManager.GetString("SQL_SocketDidNotThrow", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove a row that's already been removed.. + /// Looks up a localized string similar to SqlCommand.DeriveParameters failed because the SqlCommand.CommandText property value is an invalid multipart name. /// - internal static string DataRow_AlreadyRemoved { + internal static string SQL_SqlCommandCommandText { get { - return ResourceManager.GetString("DataRow_AlreadyRemoved", resourceCulture); + return ResourceManager.GetString("SQL_SqlCommandCommandText", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot call BeginEdit() inside the RowChanging event.. + /// Looks up a localized string similar to The target principal name is incorrect. Cannot generate SSPI context.. /// - internal static string DataRow_BeginEditInRowChanging { + internal static string SQL_SSPIGenerateError { get { - return ResourceManager.GetString("DataRow_BeginEditInRowChanging", resourceCulture); + return ResourceManager.GetString("SQL_SSPIGenerateError", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot call CancelEdit() inside an OnRowChanging event. Throw an exception to cancel this update.. + /// Looks up a localized string similar to Cannot initialize SSPI package.. /// - internal static string DataRow_CancelEditInRowChanging { + internal static string SQL_SSPIInitializeError { get { - return ResourceManager.GetString("DataRow_CancelEditInRowChanging", resourceCulture); + return ResourceManager.GetString("SQL_SSPIInitializeError", resourceCulture); } } /// - /// Looks up a localized string similar to Deleted row information cannot be accessed through the row.. + /// Looks up a localized string similar to Invalid attempt to GetStream on column '{0}'. The GetStream function can only be used on columns of type Binary, Image, Udt or VarBinary.. /// - internal static string DataRow_DeletedRowInaccessible { + internal static string SQL_StreamNotSupportOnColumnType { get { - return ResourceManager.GetString("DataRow_DeletedRowInaccessible", resourceCulture); + return ResourceManager.GetString("SQL_StreamNotSupportOnColumnType", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot call Delete inside an OnRowDeleting event. Throw an exception to cancel this delete.. + /// Looks up a localized string similar to The Stream does not support reading.. /// - internal static string DataRow_DeleteInRowDeleting { + internal static string SQL_StreamReadNotSupported { get { - return ResourceManager.GetString("DataRow_DeleteInRowDeleting", resourceCulture); + return ResourceManager.GetString("SQL_StreamReadNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change a proposed value in the RowChanging event.. + /// Looks up a localized string similar to The Stream does not support seeking.. /// - internal static string DataRow_EditInRowChanging { + internal static string SQL_StreamSeekNotSupported { get { - return ResourceManager.GetString("DataRow_EditInRowChanging", resourceCulture); + return ResourceManager.GetString("SQL_StreamSeekNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to This row is empty.. + /// Looks up a localized string similar to The Stream does not support writing.. /// - internal static string DataRow_Empty { + internal static string SQL_StreamWriteNotSupported { get { - return ResourceManager.GetString("DataRow_Empty", resourceCulture); + return ResourceManager.GetString("SQL_StreamWriteNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot call EndEdit() inside an OnRowChanging event.. + /// Looks up a localized string similar to Processing of results from SQL Server failed because of an invalid multipart name. /// - internal static string DataRow_EndEditInRowChanging { + internal static string SQL_TDSParserTableName { get { - return ResourceManager.GetString("DataRow_EndEditInRowChanging", resourceCulture); + return ResourceManager.GetString("SQL_TDSParserTableName", resourceCulture); } } /// - /// Looks up a localized string similar to Unrecognized row state bit pattern.. + /// Looks up a localized string similar to Invalid attempt to GetTextReader on column '{0}'. The GetTextReader function can only be used on columns of type Char, NChar, NText, NVarChar, Text or VarChar.. /// - internal static string DataRow_InvalidRowBitPattern { + internal static string SQL_TextReaderNotSupportOnColumnType { get { - return ResourceManager.GetString("DataRow_InvalidRowBitPattern", resourceCulture); + return ResourceManager.GetString("SQL_TextReaderNotSupportOnColumnType", resourceCulture); } } /// - /// Looks up a localized string similar to Version must be Original, Current, or Proposed.. + /// Looks up a localized string similar to Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.. /// - internal static string DataRow_InvalidVersion { + internal static string SQL_Timeout_Execution { get { - return ResourceManager.GetString("DataRow_InvalidVersion", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_Execution", resourceCulture); } } /// - /// Looks up a localized string similar to A child row has multiple parents.. + /// Looks up a localized string similar to This failure occurred while attempting to connect to the {0} server.. /// - internal static string DataRow_MultipleParents { + internal static string SQL_Timeout_FailoverInfo { get { - return ResourceManager.GetString("DataRow_MultipleParents", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_FailoverInfo", resourceCulture); } } /// - /// Looks up a localized string similar to There is no Current data to access.. + /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed at the start of the login phase. This could be because of insufficient time provided for connection timeout.. /// - internal static string DataRow_NoCurrentData { + internal static string SQL_Timeout_Login_Begin { get { - return ResourceManager.GetString("DataRow_NoCurrentData", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_Login_Begin", resourceCulture); } } /// - /// Looks up a localized string similar to There is no Original data to access.. + /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed while attempting to authenticate the login. This could be because the server failed to authenticate the user or the server was unable to respond back in time.. /// - internal static string DataRow_NoOriginalData { + internal static string SQL_Timeout_Login_ProcessConnectionAuth { get { - return ResourceManager.GetString("DataRow_NoOriginalData", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_Login_ProcessConnectionAuth", resourceCulture); } } /// - /// Looks up a localized string similar to There is no Proposed data to access.. + /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections.. /// - internal static string DataRow_NoProposedData { + internal static string SQL_Timeout_PostLogin { get { - return ResourceManager.GetString("DataRow_NoProposedData", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_PostLogin", resourceCulture); } } /// - /// Looks up a localized string similar to The row doesn't belong to the same DataSet as this relation.. + /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed at the start of the pre-login phase. This could be because of insufficient time provided for connection timeout.. /// - internal static string DataRow_NotInTheDataSet { + internal static string SQL_Timeout_PreLogin_Begin { get { - return ResourceManager.GetString("DataRow_NotInTheDataSet", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_PreLogin_Begin", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot perform this operation on a row not in the table.. + /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement. This could be because the pre-login handshake failed or the server was unable to respond back in time.. /// - internal static string DataRow_NotInTheTable { + internal static string SQL_Timeout_PreLogin_ConsumeHandshake { get { - return ResourceManager.GetString("DataRow_NotInTheTable", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_PreLogin_ConsumeHandshake", resourceCulture); } } /// - /// Looks up a localized string similar to There is no row at position {0}.. + /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed while attempting to create and initialize a socket to the server. This could be either because the server was unreachable or unable to respond back in time.. /// - internal static string DataRow_OutOfRange { + internal static string SQL_Timeout_PreLogin_InitializeConnection { get { - return ResourceManager.GetString("DataRow_OutOfRange", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_PreLogin_InitializeConnection", resourceCulture); } } /// - /// Looks up a localized string similar to This relation and child row don't belong to same DataSet.. + /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed while making a pre-login handshake request. This could be because the server was unable to respond back in time.. /// - internal static string DataRow_ParentRowNotInTheDataSet { + internal static string SQL_Timeout_PreLogin_SendHandshake { get { - return ResourceManager.GetString("DataRow_ParentRowNotInTheDataSet", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_PreLogin_SendHandshake", resourceCulture); } } /// - /// Looks up a localized string similar to This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.. + /// 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 DataRow_RemovedFromTheTable { + internal static string SQL_Timeout_RoutingDestinationInfo { get { - return ResourceManager.GetString("DataRow_RemovedFromTheTable", resourceCulture); + return ResourceManager.GetString("SQL_Timeout_RoutingDestinationInfo", resourceCulture); } } /// - /// Looks up a localized string similar to Values are missing in the rowOrder sequence for table '{0}'.. + /// Looks up a localized string similar to SqlDbType.Time overflow. Value '{0}' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999.. /// - internal static string DataRow_RowInsertMissing { + internal static string SQL_TimeOverflow { get { - return ResourceManager.GetString("DataRow_RowInsertMissing", resourceCulture); + return ResourceManager.GetString("SQL_TimeOverflow", resourceCulture); } } /// - /// Looks up a localized string similar to The row insert position {0} is invalid.. + /// Looks up a localized string similar to Scale value '{0}' is either less than 0 or greater than the maximum allowed scale of 7.. /// - internal static string DataRow_RowInsertOutOfRange { + internal static string SQL_TimeScaleValueOutOfRange { get { - return ResourceManager.GetString("DataRow_RowInsertOutOfRange", resourceCulture); + return ResourceManager.GetString("SQL_TimeScaleValueOutOfRange", resourceCulture); } } /// - /// Looks up a localized string similar to The rowOrder value={0} has been found twice for table named '{1}'.. + /// Looks up a localized string similar to SqlParameter.TypeName is an invalid multipart name. /// - internal static string DataRow_RowInsertTwice { + internal static string SQL_TypeName { get { - return ResourceManager.GetString("DataRow_RowInsertTwice", resourceCulture); + return ResourceManager.GetString("SQL_TypeName", resourceCulture); } } /// - /// Looks up a localized string similar to The given DataRow is not in the current DataRowCollection.. + /// Looks up a localized string similar to SqlParameter.UdtTypeName is an invalid multipart name. /// - internal static string DataRow_RowOutOfRange { + internal static string SQL_UDTTypeName { get { - return ResourceManager.GetString("DataRow_RowOutOfRange", resourceCulture); + return ResourceManager.GetString("SQL_UDTTypeName", resourceCulture); } } /// - /// Looks up a localized string similar to Input array is longer than the number of columns in this table.. + /// Looks up a localized string similar to Unrecognized System.Transactions.IsolationLevel enumeration value: {0}.. /// - internal static string DataRow_ValuesArrayLength { + internal static string SQL_UnknownSysTxIsolationLevel { get { - return ResourceManager.GetString("DataRow_ValuesArrayLength", resourceCulture); + return ResourceManager.GetString("SQL_UnknownSysTxIsolationLevel", resourceCulture); } } /// - /// Looks up a localized string similar to {0} is neither a DataColumn nor a DataRelation for table {1}.. + /// Looks up a localized string similar to The authentication '{0}' is not supported.. /// - internal static string DataROWView_PropertyNotFound { + internal static string SQL_UnsupportedAuthentication { get { - return ResourceManager.GetString("DataROWView_PropertyNotFound", resourceCulture); + return ResourceManager.GetString("SQL_UnsupportedAuthentication", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change CaseSensitive or Locale property. This change would lead to at least one DataRelation or Constraint to have different Locale or CaseSensitive settings between its related tables.. + /// Looks up a localized string similar to The provider '{0}' does not support authentication '{1}'.. /// - internal static string DataSet_CannotChangeCaseLocale { + internal static string SQL_UnsupportedAuthenticationByProvider { get { - return ResourceManager.GetString("DataSet_CannotChangeCaseLocale", resourceCulture); + return ResourceManager.GetString("SQL_UnsupportedAuthenticationByProvider", resourceCulture); } } /// - /// Looks up a localized string similar to SchemaSerializationMode property can be set only if it is overridden by derived DataSet.. + /// Looks up a localized string similar to Unsupported authentication specified in this context: {0}. /// - internal static string DataSet_CannotChangeSchemaSerializationMode { + internal static string SQL_UnsupportedAuthenticationSpecified { get { - return ResourceManager.GetString("DataSet_CannotChangeSchemaSerializationMode", resourceCulture); + return ResourceManager.GetString("SQL_UnsupportedAuthenticationSpecified", resourceCulture); } } /// - /// Looks up a localized string similar to Constraint Exception.. + /// Looks up a localized string similar to The keyword '{0}' is not supported on this platform.. /// - internal static string DataSet_DefaultConstraintException { + internal static string SQL_UnsupportedKeyword { get { - return ResourceManager.GetString("DataSet_DefaultConstraintException", resourceCulture); + return ResourceManager.GetString("SQL_UnsupportedKeyword", resourceCulture); } } /// - /// Looks up a localized string similar to Data Exception.. + /// Looks up a localized string similar to The currently loaded System.Transactions.dll does not support Global Transactions.. /// - internal static string DataSet_DefaultDataException { + internal static string SQL_UnsupportedSysTxVersion { get { - return ResourceManager.GetString("DataSet_DefaultDataException", resourceCulture); + return ResourceManager.GetString("SQL_UnsupportedSysTxVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Deleted rows inaccessible.. + /// Looks up a localized string similar to User Instance and Failover are not compatible options. Please choose only one of the two in the connection string.. /// - internal static string DataSet_DefaultDeletedRowInaccessibleException { + internal static string SQL_UserInstanceFailoverNotCompatible { get { - return ResourceManager.GetString("DataSet_DefaultDeletedRowInaccessibleException", resourceCulture); + return ResourceManager.GetString("SQL_UserInstanceFailoverNotCompatible", resourceCulture); } } /// - /// Looks up a localized string similar to Duplicate name not allowed.. + /// Looks up a localized string similar to A user instance was requested in the connection string but the server specified does not support this option.. /// - internal static string DataSet_DefaultDuplicateNameException { + internal static string SQL_UserInstanceFailure { get { - return ResourceManager.GetString("DataSet_DefaultDuplicateNameException", resourceCulture); + return ResourceManager.GetString("SQL_UserInstanceFailure", resourceCulture); } } /// - /// Looks up a localized string similar to Operation not supported in the RowChanging event.. + /// 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 DataSet_DefaultInRowChangingEventException { + internal static string SQL_VectorNotSupportedOnColumnType { get { - return ResourceManager.GetString("DataSet_DefaultInRowChangingEventException", resourceCulture); + return ResourceManager.GetString("SQL_VectorNotSupportedOnColumnType", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid constraint.. + /// Looks up a localized string similar to Unsupported Vector type '{0}'.. /// - internal static string DataSet_DefaultInvalidConstraintException { + internal static string SQL_VectorTypeNotSupported { get { - return ResourceManager.GetString("DataSet_DefaultInvalidConstraintException", resourceCulture); + return ResourceManager.GetString("SQL_VectorTypeNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Missing primary key.. + /// Looks up a localized string similar to Invalid attempt to GetXmlReader on column '{0}'. The GetXmlReader function can only be used on columns of type Xml.. /// - internal static string DataSet_DefaultMissingPrimaryKeyException { + internal static string SQL_XmlReaderNotSupportOnColumnType { get { - return ResourceManager.GetString("DataSet_DefaultMissingPrimaryKeyException", resourceCulture); + return ResourceManager.GetString("SQL_XmlReaderNotSupportOnColumnType", resourceCulture); } } /// - /// Looks up a localized string similar to Null not allowed.. + /// Looks up a localized string similar to Exception occurred while trying to set the AppContext Switch '{0}'={1}.. /// - internal static string DataSet_DefaultNoNullAllowedException { + internal static string SqlAppContextSwitchManager_InvalidValue { get { - return ResourceManager.GetString("DataSet_DefaultNoNullAllowedException", resourceCulture); + return ResourceManager.GetString("SqlAppContextSwitchManager_InvalidValue", resourceCulture); } } /// - /// Looks up a localized string similar to Column is marked read only.. + /// Looks up a localized string similar to Notification values used by Microsoft SQL Server.. /// - internal static string DataSet_DefaultReadOnlyException { + internal static string SqlCommand_Notification { get { - return ResourceManager.GetString("DataSet_DefaultReadOnlyException", resourceCulture); + return ResourceManager.GetString("SqlCommand_Notification", resourceCulture); } } /// - /// Looks up a localized string similar to Row not found in table.. + /// Looks up a localized string similar to Automatic enlistment in notifications used by Microsoft SQL Server.. /// - internal static string DataSet_DefaultRowNotInTableException { + internal static string SqlCommand_NotificationAutoEnlist { get { - return ResourceManager.GetString("DataSet_DefaultRowNotInTableException", resourceCulture); + return ResourceManager.GetString("SqlCommand_NotificationAutoEnlist", resourceCulture); } } /// - /// Looks up a localized string similar to Version not found.. + /// Looks up a localized string similar to The DataAdapter for which to automatically generate SqlCommands. /// - internal static string DataSet_DefaultVersionNotFoundException { + internal static string SqlCommandBuilder_DataAdapter { get { - return ResourceManager.GetString("DataSet_DefaultVersionNotFoundException", resourceCulture); + return ResourceManager.GetString("SqlCommandBuilder_DataAdapter", resourceCulture); } } /// - /// Looks up a localized string similar to The name '{0}' is invalid. A DataSet cannot have the same name of the DataTable.. + /// Looks up a localized string similar to Access token to use for authentication.. /// - internal static string DataSet_SetDataSetNameConflicting { + internal static string SqlConnection_AccessToken { get { - return ResourceManager.GetString("DataSet_SetDataSetNameConflicting", resourceCulture); + return ResourceManager.GetString("SqlConnection_AccessToken", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change the name of the DataSet to an empty string.. + /// Looks up a localized string similar to A guid to represent the physical connection.. /// - internal static string DataSet_SetNameToEmpty { + internal static string SqlConnection_ClientConnectionId { get { - return ResourceManager.GetString("DataSet_SetNameToEmpty", resourceCulture); + return ResourceManager.GetString("SqlConnection_ClientConnectionId", resourceCulture); } } /// - /// Looks up a localized string similar to The schema namespace is invalid. Please use this one instead: {0}.. + /// Looks up a localized string similar to Information used to connect to a DataSource, such as 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'.. /// - internal static string DataSet_UnsupportedSchema { + internal static string SqlConnection_ConnectionString { get { - return ResourceManager.GetString("DataSet_UnsupportedSchema", resourceCulture); + return ResourceManager.GetString("SqlConnection_ConnectionString", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether comparing strings within the DataSet is case sensitive.. + /// Looks up a localized string similar to Current connection timeout value, 'Connect Timeout=X' in the ConnectionString.. /// - internal static string DataSetCaseSensitiveDescr { + internal static string SqlConnection_ConnectionTimeout { get { - return ResourceManager.GetString("DataSetCaseSensitiveDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_ConnectionTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to The name of this DataSet.. + /// Looks up a localized string similar to User Id and secure password to use for authentication.. /// - internal static string DataSetDataSetNameDescr { + internal static string SqlConnection_Credential { get { - return ResourceManager.GetString("DataSetDataSetNameDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_Credential", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates a custom "view" of the data contained by the DataSet. This view allows filtering, searching, and navigating through the custom data view.. + /// Looks up a localized string similar to Current SQL Server database, 'Initial Catalog=X' in the connection string.. /// - internal static string DataSetDefaultViewDescr { + internal static string SqlConnection_Database { get { - return ResourceManager.GetString("DataSetDefaultViewDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_Database", resourceCulture); } } /// - /// Looks up a localized string similar to Represents an in-memory cache of data.. + /// Looks up a localized string similar to Current SqlServer that the connection is opened to, 'Data Source=X' in the connection string.. /// - internal static string DataSetDescr { + internal static string SqlConnection_DataSource { get { - return ResourceManager.GetString("DataSetDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_DataSource", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether constraint rules are to be followed.. + /// Looks up a localized string similar to Network packet size, 'Packet Size=x' in the connection string.. /// - internal static string DataSetEnforceConstraintsDescr { + internal static string SqlConnection_PacketSize { get { - return ResourceManager.GetString("DataSetEnforceConstraintsDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_PacketSize", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates that the DataSet has errors.. + /// Looks up a localized string similar to Server Process Id (SPID) of the active connection.. /// - internal static string DataSetHasErrorsDescr { + internal static string SqlConnection_ServerProcessId { get { - return ResourceManager.GetString("DataSetHasErrorsDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_ServerProcessId", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs after Initialization is finished.. + /// Looks up a localized string similar to Version of the SQL Server accessed by the SqlConnection.. /// - internal static string DataSetInitializedDescr { + internal static string SqlConnection_ServerVersion { get { - return ResourceManager.GetString("DataSetInitializedDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_ServerVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates a locale under which to compare strings within the DataSet.. + /// Looks up a localized string similar to Collect statistics for this connection.. /// - internal static string DataSetLocaleDescr { + internal static string SqlConnection_StatisticsEnabled { get { - return ResourceManager.GetString("DataSetLocaleDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_StatisticsEnabled", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs when it is not possible to merge schemas for two tables with the same name.. + /// Looks up a localized string similar to Workstation Id, 'Workstation ID=x' in the connection string.. /// - internal static string DataSetMergeFailedDescr { + internal static string SqlConnection_WorkstationId { get { - return ResourceManager.GetString("DataSetMergeFailedDescr", resourceCulture); + return ResourceManager.GetString("SqlConnection_WorkstationId", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the XML uri namespace for the root element pointed at by this DataSet.. + /// Looks up a localized string similar to Cannot convert object of type '{0}' to object of type '{1}'.. /// - internal static string DataSetNamespaceDescr { + internal static string SqlConvert_ConvertFailed { get { - return ResourceManager.GetString("DataSetNamespaceDescr", resourceCulture); + return ResourceManager.GetString("SqlConvert_ConvertFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the prefix of the namespace used for this DataSet.. + /// Looks up a localized string similar to The connection is broken and recovery is not possible. The client driver attempted to recover the connection one or more times and all attempts failed. Increase the value of ConnectRetryCount to increase the number of recovery attempts.. /// - internal static string DataSetPrefixDescr { + internal static string SQLCR_AllAttemptsFailed { get { - return ResourceManager.GetString("DataSetPrefixDescr", resourceCulture); + return ResourceManager.GetString("SQLCR_AllAttemptsFailed", resourceCulture); } } /// - /// Looks up a localized string similar to The collection that holds the relations for this DataSet.. + /// Looks up a localized string similar to The server did not preserve SSL encryption during a recovery attempt, connection recovery is not possible.. /// - internal static string DataSetRelationsDescr { + internal static string SQLCR_EncryptionChanged { get { - return ResourceManager.GetString("DataSetRelationsDescr", resourceCulture); + return ResourceManager.GetString("SQLCR_EncryptionChanged", resourceCulture); } } /// - /// Looks up a localized string similar to The collection that holds the tables for this DataSet.. + /// Looks up a localized string similar to Invalid ConnectRetryCount value (should be 0-255).. /// - internal static string DataSetTablesDescr { + internal static string SQLCR_InvalidConnectRetryCountValue { get { - return ResourceManager.GetString("DataSetTablesDescr", resourceCulture); + return ResourceManager.GetString("SQLCR_InvalidConnectRetryCountValue", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid usage of aggregate function {0}() and Type: {1}.. + /// Looks up a localized string similar to Invalid ConnectRetryInterval value (should be 1-60).. /// - internal static string DataStorage_AggregateException { + internal static string SQLCR_InvalidConnectRetryIntervalValue { get { - return ResourceManager.GetString("DataStorage_AggregateException", resourceCulture); + return ResourceManager.GetString("SQLCR_InvalidConnectRetryIntervalValue", resourceCulture); } } /// - /// Looks up a localized string similar to Type '{0}' does not implement IComparable interface. Comparison cannot be done.. + /// Looks up a localized string similar to Failed to create SqlRetryLogicBaseProvider object because of invalid retryMethod parameters.{0}Parameter '{1} {2}' doesn't have a default value.. /// - internal static string DataStorage_IComparableNotDefined { + internal static string SQLCR_InvalidParameterNoDefault { get { - return ResourceManager.GetString("DataStorage_IComparableNotDefined", resourceCulture); + return ResourceManager.GetString("SQLCR_InvalidParameterNoDefault", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid storage type: {0}.. + /// Looks up a localized string similar to Failed to create SqlRetryLogicBaseProvider object because of invalid '{0}' parameters.{1}The function must have a parameter of type 'SqlRetryLogicOption'.. /// - internal static string DataStorage_InvalidStorageType { + internal static string SQLCR_InvalidRetryParameters { get { - return ResourceManager.GetString("DataStorage_InvalidStorageType", resourceCulture); + return ResourceManager.GetString("SQLCR_InvalidRetryParameters", resourceCulture); } } /// - /// Looks up a localized string similar to The DataSet Xml persistency does not support the value '{0}' as Char value, please use Byte storage instead.. + /// Looks up a localized string similar to Invalid return type; return type must be of '{0}' type.. /// - internal static string DataStorage_ProblematicChars { + internal static string SQLCR_InvalidReturnType { get { - return ResourceManager.GetString("DataStorage_ProblematicChars", resourceCulture); + return ResourceManager.GetString("SQLCR_InvalidReturnType", resourceCulture); } } /// - /// Looks up a localized string similar to Type of value has a mismatch with column type. + /// Looks up a localized string similar to Next reconnection attempt will exceed query timeout. Reconnection was terminated.. /// - internal static string DataStorage_SetInvalidDataType { + internal static string SQLCR_NextAttemptWillExceedQueryTimeout { get { - return ResourceManager.GetString("DataStorage_SetInvalidDataType", resourceCulture); + return ResourceManager.GetString("SQLCR_NextAttemptWillExceedQueryTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to DataTable already belongs to another DataSet.. + /// Looks up a localized string similar to The server did not acknowledge a recovery attempt, connection recovery is not possible.. /// - internal static string DataTable_AlreadyInOtherDataSet { + internal static string SQLCR_NoCRAckAtReconnection { get { - return ResourceManager.GetString("DataTable_AlreadyInOtherDataSet", resourceCulture); + return ResourceManager.GetString("SQLCR_NoCRAckAtReconnection", resourceCulture); } } /// - /// Looks up a localized string similar to DataTable already belongs to this DataSet.. + /// Looks up a localized string similar to Failed to resolve the '{0}' method from '{1}' type.. /// - internal static string DataTable_AlreadyInTheDataSet { + internal static string SQLCR_ResolveMethodFailed { get { - return ResourceManager.GetString("DataTable_AlreadyInTheDataSet", resourceCulture); + return ResourceManager.GetString("SQLCR_ResolveMethodFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add a nested relation or an element column to a table containing a SimpleContent column.. + /// Looks up a localized string similar to Exception occurred when running the '{0}.{1}()' method.. /// - internal static string DataTable_CannotAddToSimpleContent { + internal static string SQLCR_RetryMethodException { get { - return ResourceManager.GetString("DataTable_CannotAddToSimpleContent", resourceCulture); + return ResourceManager.GetString("SQLCR_RetryMethodException", resourceCulture); } } /// - /// Looks up a localized string similar to This DataTable can only be remoted as part of DataSet. One or more Expression Columns has reference to other DataTable(s).. + /// Looks up a localized string similar to Failed to create SqlRetryLogicBaseProvider object because the retryMethod value is null or empty.. /// - internal static string DataTable_CanNotRemoteDataTable { + internal static string SQLCR_RetryMethodNullOrEmpty { get { - return ResourceManager.GetString("DataTable_CanNotRemoteDataTable", resourceCulture); + return ResourceManager.GetString("SQLCR_RetryMethodNullOrEmpty", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot serialize the DataTable. A DataTable being used in one or more DataColumn expressions is not a descendant of current DataTable.. + /// Looks up a localized string similar to Sequence contains more than one matching element.. /// - internal static string DataTable_CanNotSerializeDataTableHierarchy { + internal static string SQLCR_SequenceMoreThanOneMatch { get { - return ResourceManager.GetString("DataTable_CanNotSerializeDataTableHierarchy", resourceCulture); + return ResourceManager.GetString("SQLCR_SequenceMoreThanOneMatch", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot serialize the DataTable. DataTable name is not set.. + /// Looks up a localized string similar to Sequence contains no matching element.. /// - internal static string DataTable_CanNotSerializeDataTableWithEmptyName { + internal static string SQLCR_SequenceNoMatch { get { - return ResourceManager.GetString("DataTable_CanNotSerializeDataTableWithEmptyName", resourceCulture); + return ResourceManager.GetString("SQLCR_SequenceNoMatch", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot have different remoting format property value for DataSet and DataTable.. + /// 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 DataTable_CanNotSetRemotingFormat { + internal static string SQLCR_TDSVersionNotPreserved { get { - return ResourceManager.GetString("DataTable_CanNotSetRemotingFormat", resourceCulture); + return ResourceManager.GetString("SQLCR_TDSVersionNotPreserved", resourceCulture); } } /// - /// Looks up a localized string similar to The name '{0}' is invalid. A DataTable cannot have the same name of the DataSet.. + /// Looks up a localized string similar to The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.. /// - internal static string DataTable_DatasetConflictingName { + internal static string SQLCR_UnrecoverableClient { get { - return ResourceManager.GetString("DataTable_DatasetConflictingName", resourceCulture); + return ResourceManager.GetString("SQLCR_UnrecoverableClient", resourceCulture); } } /// - /// Looks up a localized string similar to A DataTable named '{0}' already belongs to this DataSet.. + /// Looks up a localized string similar to The connection is broken and recovery is not possible. The connection is marked by the server as unrecoverable. No attempt was made to restore the connection.. /// - internal static string DataTable_DuplicateName { + internal static string SQLCR_UnrecoverableServer { get { - return ResourceManager.GetString("DataTable_DuplicateName", resourceCulture); + return ResourceManager.GetString("SQLCR_UnrecoverableServer", resourceCulture); } } /// - /// Looks up a localized string similar to A DataTable named '{0}' with the same Namespace '{1}' already belongs to this DataSet.. + /// Looks up a localized string similar to Failure while attempting to promote transaction.. /// - internal static string DataTable_DuplicateName2 { + internal static string SqlDelegatedTransaction_PromotionFailed { get { - return ResourceManager.GetString("DataTable_DuplicateName2", resourceCulture); + return ResourceManager.GetString("SqlDelegatedTransaction_PromotionFailed", resourceCulture); } } /// - /// Looks up a localized string similar to PrimaryKey columns do not belong to this table.. + /// Looks up a localized string similar to To add a command to existing dependency object.. /// - internal static string DataTable_ForeignPrimaryKey { + internal static string SqlDependency_AddCommandDependency { get { - return ResourceManager.GetString("DataTable_ForeignPrimaryKey", resourceCulture); + return ResourceManager.GetString("SqlDependency_AddCommandDependency", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove table {0}, because it referenced in ForeignKeyConstraint {1}. Remove the constraint first.. + /// Looks up a localized string similar to The SQL Server Service Broker for the current database is not enabled, and as a result query notifications are not supported. Please enable the Service Broker for this database if you wish to use notifications.. /// - internal static string DataTable_InConstraint { + internal static string SqlDependency_DatabaseBrokerDisabled { get { - return ResourceManager.GetString("DataTable_InConstraint", resourceCulture); + return ResourceManager.GetString("SqlDependency_DatabaseBrokerDisabled", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove a table that has existing relations. Remove relations first.. + /// Looks up a localized string similar to When using SqlDependency without providing an options value, SqlDependency.Start() must be called prior to execution of a command added to the SqlDependency instance.. /// - internal static string DataTable_InRelation { + internal static string SqlDependency_DefaultOptionsButNoStart { get { - return ResourceManager.GetString("DataTable_InRelation", resourceCulture); + return ResourceManager.GetString("SqlDependency_DefaultOptionsButNoStart", resourceCulture); } } /// - /// Looks up a localized string similar to {0} isn't a valid Sort string entry.. + /// Looks up a localized string similar to SqlDependency does not support calling Start() with different connection strings having the same server, user, and database in the same app domain.. /// - internal static string DataTable_InvalidSortString { + internal static string SqlDependency_DuplicateStart { get { - return ResourceManager.GetString("DataTable_InvalidSortString", resourceCulture); + return ResourceManager.GetString("SqlDependency_DuplicateStart", resourceCulture); } } /// - /// Looks up a localized string similar to Table doesn't have a primary key.. + /// Looks up a localized string similar to SqlDependency.OnChange does not support multiple event registrations for the same delegate.. /// - internal static string DataTable_MissingPrimaryKey { + internal static string SqlDependency_EventNoDuplicate { get { - return ResourceManager.GetString("DataTable_MissingPrimaryKey", resourceCulture); + return ResourceManager.GetString("SqlDependency_EventNoDuplicate", resourceCulture); } } /// - /// Looks up a localized string similar to DataTable already has a simple content column.. + /// Looks up a localized string similar to Property to indicate if this dependency is invalid.. /// - internal static string DataTable_MultipleSimpleContentColumns { + internal static string SqlDependency_HasChanges { get { - return ResourceManager.GetString("DataTable_MultipleSimpleContentColumns", resourceCulture); + return ResourceManager.GetString("SqlDependency_HasChanges", resourceCulture); } } /// - /// Looks up a localized string similar to TableName is required when it is part of a DataSet.. + /// Looks up a localized string similar to A string that uniquely identifies this dependency object.. /// - internal static string DataTable_NoName { + internal static string SqlDependency_Id { get { - return ResourceManager.GetString("DataTable_NoName", resourceCulture); + return ResourceManager.GetString("SqlDependency_Id", resourceCulture); } } /// - /// Looks up a localized string similar to Table {0} does not belong to this DataSet.. + /// Looks up a localized string similar to No SqlDependency exists for the key.. /// - internal static string DataTable_NotInTheDataSet { + internal static string SqlDependency_IdMismatch { get { - return ResourceManager.GetString("DataTable_NotInTheDataSet", resourceCulture); + return ResourceManager.GetString("SqlDependency_IdMismatch", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot find table {0}.. + /// Looks up a localized string similar to Timeout specified is invalid. Timeout cannot be < 0.. /// - internal static string DataTable_OutOfRange { + internal static string SqlDependency_InvalidTimeout { get { - return ResourceManager.GetString("DataTable_OutOfRange", resourceCulture); + return ResourceManager.GetString("SqlDependency_InvalidTimeout", resourceCulture); } } /// - /// Looks up a localized string similar to The table ({0}) cannot be the child table to itself in a nested relation: the DataSet name conflicts with the table name.. + /// Looks up a localized string similar to SqlDependency.Start has been called for the server the command is executing against more than once, but there is no matching server/user/database Start() call for current command.. /// - internal static string DataTable_SelfnestedDatasetConflictingName { + internal static string SqlDependency_NoMatchingServerDatabaseStart { get { - return ResourceManager.GetString("DataTable_SelfnestedDatasetConflictingName", resourceCulture); + return ResourceManager.GetString("SqlDependency_NoMatchingServerDatabaseStart", resourceCulture); } } /// - /// Looks up a localized string similar to DataTable '{0}' does not match to any DataTable in source.. + /// Looks up a localized string similar to When using SqlDependency without providing an options value, SqlDependency.Start() must be called for each server that is being executed against.. /// - internal static string DataTable_TableNotFound { + internal static string SqlDependency_NoMatchingServerStart { get { - return ResourceManager.GetString("DataTable_TableNotFound", resourceCulture); + return ResourceManager.GetString("SqlDependency_NoMatchingServerStart", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether comparing strings within the table is case sensitive.. + /// Looks up a localized string similar to Event that can be used to subscribe for change notifications.. /// - internal static string DataTableCaseSensitiveDescr { + internal static string SqlDependency_OnChange { get { - return ResourceManager.GetString("DataTableCaseSensitiveDescr", resourceCulture); + return ResourceManager.GetString("SqlDependency_OnChange", resourceCulture); } } /// - /// Looks up a localized string similar to Returns the child relations for this table.. + /// Looks up a localized string similar to Unexpected type detected on deserialize.. /// - internal static string DataTableChildRelationsDescr { + internal static string SqlDependency_UnexpectedValueOnDeserialize { get { - return ResourceManager.GetString("DataTableChildRelationsDescr", resourceCulture); + return ResourceManager.GetString("SqlDependency_UnexpectedValueOnDeserialize", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs when a value has been changed for this column.. + /// Looks up a localized string similar to The process cannot access the file specified because it has been opened in another transaction.. /// - internal static string DataTableColumnChangedDescr { + internal static string SqlFileStream_FileAlreadyInTransaction { get { - return ResourceManager.GetString("DataTableColumnChangedDescr", resourceCulture); + return ResourceManager.GetString("SqlFileStream_FileAlreadyInTransaction", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs when a value has been submitted for this column. The user can modify the proposed value and should throw an exception to cancel the edit.. + /// Looks up a localized string similar to An invalid parameter was passed to the function.. /// - internal static string DataTableColumnChangingDescr { + internal static string SqlFileStream_InvalidParameter { get { - return ResourceManager.GetString("DataTableColumnChangingDescr", resourceCulture); + return ResourceManager.GetString("SqlFileStream_InvalidParameter", resourceCulture); } } /// - /// Looks up a localized string similar to The collection that holds the columns for this table.. + /// Looks up a localized string similar to The path name is not valid.. /// - internal static string DataTableColumnsDescr { + internal static string SqlFileStream_InvalidPath { get { - return ResourceManager.GetString("DataTableColumnsDescr", resourceCulture); + return ResourceManager.GetString("SqlFileStream_InvalidPath", resourceCulture); } } /// - /// Looks up a localized string similar to The collection that holds the constraints for this table.. + /// Looks up a localized string similar to SqlFileStream is not supported on this platform.. /// - internal static string DataTableConstraintsDescr { + internal static string SqlFileStream_NotSupported { get { - return ResourceManager.GetString("DataTableConstraintsDescr", resourceCulture); + return ResourceManager.GetString("SqlFileStream_NotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the DataSet to which this table belongs.. + /// Looks up a localized string similar to The path name is invalid or does not point to a disk file.. /// - internal static string DataTableDataSetDescr { + internal static string SqlFileStream_PathNotValidDiskResource { get { - return ResourceManager.GetString("DataTableDataSetDescr", resourceCulture); + return ResourceManager.GetString("SqlFileStream_PathNotValidDiskResource", resourceCulture); } } /// - /// Looks up a localized string similar to This is the default DataView for the table.. + /// Looks up a localized string similar to The dbType {0} is invalid for this constructor.. /// - internal static string DataTableDefaultViewDescr { + internal static string SqlMetaData_InvalidSqlDbTypeForConstructorFormat { get { - return ResourceManager.GetString("DataTableDefaultViewDescr", resourceCulture); + return ResourceManager.GetString("SqlMetaData_InvalidSqlDbTypeForConstructorFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The expression used to compute the data-bound value of this row.. + /// Looks up a localized string similar to The name is too long.. /// - internal static string DataTableDisplayExpressionDescr { + internal static string SqlMetaData_NameTooLong { get { - return ResourceManager.GetString("DataTableDisplayExpressionDescr", resourceCulture); + return ResourceManager.GetString("SqlMetaData_NameTooLong", resourceCulture); } } /// - /// Looks up a localized string similar to Returns whether the table has errors.. + /// Looks up a localized string similar to The sort order and ordinal must either both be specified, or neither should be specified (SortOrder.Unspecified and -1). The values given were: order = {0}, ordinal = {1}.. /// - internal static string DataTableHasErrorsDescr { + internal static string SqlMetaData_SpecifyBothSortOrderAndOrdinal { get { - return ResourceManager.GetString("DataTableHasErrorsDescr", resourceCulture); + return ResourceManager.GetString("SqlMetaData_SpecifyBothSortOrderAndOrdinal", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates a locale under which to compare strings within the table.. + /// Looks up a localized string similar to Conversion overflows.. /// - internal static string DataTableLocaleDescr { + internal static string SqlMisc_ConversionOverflowMessage { get { - return ResourceManager.GetString("DataTableLocaleDescr", resourceCulture); + return ResourceManager.GetString("SqlMisc_ConversionOverflowMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Individual columns mappings when this table mapping is matched.. + /// Looks up a localized string similar to SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.. /// - internal static string DataTableMapping_ColumnMappings { + internal static string SqlMisc_DateTimeOverflowMessage { get { - return ResourceManager.GetString("DataTableMapping_ColumnMappings", resourceCulture); + return ResourceManager.GetString("SqlMisc_DateTimeOverflowMessage", resourceCulture); } } /// - /// Looks up a localized string similar to DataTable.TableName. + /// Looks up a localized string similar to Invalid array size.. /// - internal static string DataTableMapping_DataSetTable { + internal static string SqlMisc_InvalidArraySizeMessage { get { - return ResourceManager.GetString("DataTableMapping_DataSetTable", resourceCulture); + return ResourceManager.GetString("SqlMisc_InvalidArraySizeMessage", resourceCulture); } } /// - /// Looks up a localized string similar to The DataTableMapping source table name. This name is case sensitive.. + /// Looks up a localized string similar to Invalid numeric precision/scale.. /// - internal static string DataTableMapping_SourceTable { + internal static string SqlMisc_InvalidPrecScaleMessage { get { - return ResourceManager.GetString("DataTableMapping_SourceTable", resourceCulture); + return ResourceManager.GetString("SqlMisc_InvalidPrecScaleMessage", resourceCulture); } } /// - /// Looks up a localized string similar to The number of items in the collection. + /// Looks up a localized string similar to Null. /// - internal static string DataTableMappings_Count { + internal static string SqlMisc_NullString { get { - return ResourceManager.GetString("DataTableMappings_Count", resourceCulture); + return ResourceManager.GetString("SqlMisc_NullString", resourceCulture); } } /// - /// Looks up a localized string similar to The specified DataTableMapping object. + /// Looks up a localized string similar to An error occurred while reading.. /// - internal static string DataTableMappings_Item { + internal static string SqlMisc_StreamErrorMessage { get { - return ResourceManager.GetString("DataTableMappings_Item", resourceCulture); + return ResourceManager.GetString("SqlMisc_StreamErrorMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates an initial starting size for this table.. + /// Looks up a localized string similar to Data returned is larger than 2Gb in size. Use SequentialAccess command behavior in order to get all of the data.. /// - internal static string DataTableMinimumCapacityDescr { + internal static string SqlMisc_TruncationMaxDataMessage { get { - return ResourceManager.GetString("DataTableMinimumCapacityDescr", resourceCulture); + return ResourceManager.GetString("SqlMisc_TruncationMaxDataMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the XML uri namespace for the elements contained in this table.. + /// Looks up a localized string similar to Connecting to a mirrored SQL Server instance using the MultiSubnetFailover connection option is not supported.. /// - internal static string DataTableNamespaceDescr { + internal static string SQLMSF_FailoverPartnerNotSupported { get { - return ResourceManager.GetString("DataTableNamespaceDescr", resourceCulture); + return ResourceManager.GetString("SQLMSF_FailoverPartnerNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Returns the parent relations for this table.. + /// Looks up a localized string similar to This SqlCommand object is already associated with another SqlDependency object.. /// - internal static string DataTableParentRelationsDescr { + internal static string SQLNotify_AlreadyHasCommand { get { - return ResourceManager.GetString("DataTableParentRelationsDescr", resourceCulture); + return ResourceManager.GetString("SQLNotify_AlreadyHasCommand", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the Prefix of the namespace used for this table in XML representation.. + /// Looks up a localized string similar to DBNull value for parameter '{0}' is not supported. Table-valued parameters cannot be DBNull.. /// - internal static string DataTablePrefixDescr { + internal static string SqlParameter_DBNullNotSupportedForTVP { get { - return ResourceManager.GetString("DataTablePrefixDescr", resourceCulture); + return ResourceManager.GetString("SqlParameter_DBNullNotSupportedForTVP", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the column(s) that represent the primary key for this table.. + /// Looks up a localized string similar to Precision '{0}' required to send all values in column '{1}' exceeds the maximum supported precision '{2}'. The values must all fit in a single precision.. /// - internal static string DataTablePrimaryKeyDescr { + internal static string SqlParameter_InvalidTableDerivedPrecisionForTvp { get { - return ResourceManager.GetString("DataTablePrimaryKeyDescr", resourceCulture); + return ResourceManager.GetString("SqlParameter_InvalidTableDerivedPrecisionForTvp", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create DataTableReader. Arguments contain null value.. + /// Looks up a localized string similar to When used by DataAdapter.Update, the parameter value is changed from DBNull.Value into (Int32)1 or (Int32)0 if non-null.. /// - internal static string DataTableReader_ArgumentContainsNullValue { + internal static string SqlParameter_SourceColumnNullMapping { get { - return ResourceManager.GetString("DataTableReader_ArgumentContainsNullValue", resourceCulture); + return ResourceManager.GetString("SqlParameter_SourceColumnNullMapping", resourceCulture); } } /// - /// Looks up a localized string similar to DataTableReader Cannot be created. There is no DataTable in DataSet.. + /// Looks up a localized string similar to TypeName specified for parameter '{0}'. TypeName must only be set for Structured parameters.. /// - internal static string DataTableReader_CannotCreateDataReaderOnEmptyDataSet { + internal static string SqlParameter_UnexpectedTypeNameForNonStruct { get { - return ResourceManager.GetString("DataTableReader_CannotCreateDataReaderOnEmptyDataSet", resourceCulture); + return ResourceManager.GetString("SqlParameter_UnexpectedTypeNameForNonStruct", resourceCulture); } } /// - /// Looks up a localized string similar to Current DataTable '{0}' is empty. There is no DataRow in DataTable.. + /// Looks up a localized string similar to ParameterDirection '{0}' specified for parameter '{1}' is not supported. Table-valued parameters only support ParameterDirection.Input.. /// - internal static string DataTableReader_DataTableCleared { + internal static string SqlParameter_UnsupportedTVPOutputParameter { get { - return ResourceManager.GetString("DataTableReader_DataTableCleared", resourceCulture); + return ResourceManager.GetString("SqlParameter_UnsupportedTVPOutputParameter", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot create DataTableReader. Argument is Empty.. + /// Looks up a localized string similar to The sort ordinal {0} was specified twice.. /// - internal static string DataTableReader_DataTableReaderArgumentIsEmpty { + internal static string SqlProvider_DuplicateSortOrdinal { get { - return ResourceManager.GetString("DataTableReader_DataTableReaderArgumentIsEmpty", resourceCulture); + return ResourceManager.GetString("SqlProvider_DuplicateSortOrdinal", resourceCulture); } } /// - /// Looks up a localized string similar to DataTableReader is invalid for current DataTable '{0}'.. + /// Looks up a localized string similar to The size of column '{0}' is not supported. The size is {1}.. /// - internal static string DataTableReader_InvalidDataTableReader { + internal static string SqlProvider_InvalidDataColumnMaxLength { get { - return ResourceManager.GetString("DataTableReader_InvalidDataTableReader", resourceCulture); + return ResourceManager.GetString("SqlProvider_InvalidDataColumnMaxLength", resourceCulture); } } /// - /// Looks up a localized string similar to Current DataRow is either in Deleted or Detached state.. + /// Looks up a localized string similar to The type of column '{0}' is not supported. The type is '{1}'. /// - internal static string DataTableReader_InvalidRowInDataTableReader { + internal static string SqlProvider_InvalidDataColumnType { get { - return ResourceManager.GetString("DataTableReader_InvalidRowInDataTableReader", resourceCulture); + return ResourceManager.GetString("SqlProvider_InvalidDataColumnType", resourceCulture); } } /// - /// Looks up a localized string similar to Schema of current DataTable '{0}' in DataTableReader has changed, DataTableReader is invalid.. + /// Looks up a localized string similar to The sort ordinal {0} was not specified.. /// - internal static string DataTableReader_SchemaInvalidDataTableReader { + internal static string SqlProvider_MissingSortOrdinal { get { - return ResourceManager.GetString("DataTableReader_SchemaInvalidDataTableReader", resourceCulture); + return ResourceManager.GetString("SqlProvider_MissingSortOrdinal", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs after a row in the table has been successfully edited.. + /// Looks up a localized string similar to There are not enough fields in the Structured type. Structured types must have at least one field.. /// - internal static string DataTableRowChangedDescr { + internal static string SqlProvider_NotEnoughColumnsInStructuredType { get { - return ResourceManager.GetString("DataTableRowChangedDescr", resourceCulture); + return ResourceManager.GetString("SqlProvider_NotEnoughColumnsInStructuredType", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs when the row is being changed so that the event handler can modify or cancel the change. The user can modify values in the row and should throw an exception to cancel the edit.. + /// Looks up a localized string similar to The sort ordinal {0} on field {1} exceeds the total number of fields.. /// - internal static string DataTableRowChangingDescr { + internal static string SqlProvider_SortOrdinalGreaterThanFieldCount { get { - return ResourceManager.GetString("DataTableRowChangingDescr", resourceCulture); + return ResourceManager.GetString("SqlProvider_SortOrdinalGreaterThanFieldCount", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs after a row in the table has been successfully deleted.. + /// Looks up a localized string similar to '{0}' is not less than '{1}'; '{2}' cannot be greater than '{3}'.. /// - internal static string DataTableRowDeletedDescr { + internal static string SqlRetryLogic_InvalidMinMaxPair { get { - return ResourceManager.GetString("DataTableRowDeletedDescr", resourceCulture); + return ResourceManager.GetString("SqlRetryLogic_InvalidMinMaxPair", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs when a row in the table marked for deletion. Throw an exception to cancel the deletion.. + /// Looks up a localized string similar to Value '{0}' is out of range. Must be between {1} and {2}.. /// - internal static string DataTableRowDeletingDescr { + internal static string SqlRetryLogic_InvalidRange { get { - return ResourceManager.GetString("DataTableRowDeletingDescr", resourceCulture); + return ResourceManager.GetString("SqlRetryLogic_InvalidRange", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs after all rows in the table has been successfully cleared.. + /// Looks up a localized string similar to The retry has been canceled at attempt {0}.. /// - internal static string DataTableRowsClearedDescr { + internal static string SqlRetryLogic_RetryCanceled { get { - return ResourceManager.GetString("DataTableRowsClearedDescr", resourceCulture); + return ResourceManager.GetString("SqlRetryLogic_RetryCanceled", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs prior to clearing all rows from the table.. + /// Looks up a localized string similar to The number of retries has exceeded the maximum of {0} attempt(s).. /// - internal static string DataTableRowsClearingDescr { + internal static string SqlRetryLogic_RetryExceeded { get { - return ResourceManager.GetString("DataTableRowsClearingDescr", resourceCulture); + return ResourceManager.GetString("SqlRetryLogic_RetryExceeded", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the collection that holds the rows of data for this table.. + /// Looks up a localized string similar to Connecting to a mirrored SQL Server instance using the ApplicationIntent ReadOnly connection option is not supported.. /// - internal static string DataTableRowsDescr { + internal static string SQLROR_FailoverNotSupported { get { - return ResourceManager.GetString("DataTableRowsDescr", resourceCulture); + return ResourceManager.GetString("SQLROR_FailoverNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Occurs after a new DataRow has been instantiated.. + /// Looks up a localized string similar to Invalid enhanced routing information received.. /// - internal static string DataTableRowsNewRowDescr { + internal static string SQLROR_InvalidEnhancedRoutingInfo { get { - return ResourceManager.GetString("DataTableRowsNewRowDescr", resourceCulture); + return ResourceManager.GetString("SQLROR_InvalidEnhancedRoutingInfo", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the name used to look up this table in the Tables collection of a DataSet.. + /// Looks up a localized string similar to Invalid routing information received.. /// - internal static string DataTableTableNameDescr { + internal static string SQLROR_InvalidRoutingInfo { get { - return ResourceManager.GetString("DataTableTableNameDescr", resourceCulture); + return ResourceManager.GetString("SQLROR_InvalidRoutingInfo", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot add external objects to this list.. + /// Looks up a localized string similar to Too many redirections have occurred. Only {0} redirections per login is allowed.. /// - internal static string DataView_AddExternalObject { + internal static string SQLROR_RecursiveRoutingNotSupported { get { - return ResourceManager.GetString("DataView_AddExternalObject", resourceCulture); + return ResourceManager.GetString("SQLROR_RecursiveRoutingNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot call AddNew on a DataView where AllowNew is false.. + /// Looks up a localized string similar to Server provided routing information, but timeout already expired.. /// - internal static string DataView_AddNewNotAllowNull { + internal static string SQLROR_TimeoutAfterRoutingInfo { get { - return ResourceManager.GetString("DataView_AddNewNotAllowNull", resourceCulture); + return ResourceManager.GetString("SQLROR_TimeoutAfterRoutingInfo", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot bind to DataTable with no name.. + /// Looks up a localized string similar to UDT size must be less than {1}, size: {0}. /// - internal static string DataView_CanNotBindTable { + internal static string SQLUDT_InvalidSize { get { - return ResourceManager.GetString("DataView_CanNotBindTable", resourceCulture); + return ResourceManager.GetString("SQLUDT_InvalidSize", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot clear this list.. + /// Looks up a localized string similar to Specified type is not registered on the target server.{0}.. /// - internal static string DataView_CanNotClear { + internal static string SQLUDT_InvalidSqlType { get { - return ResourceManager.GetString("DataView_CanNotClear", resourceCulture); + return ResourceManager.GetString("SQLUDT_InvalidSqlType", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot delete on a DataSource where AllowDelete is false.. + /// Looks up a localized string similar to UdtTypeName property must be set for UDT parameters.. /// - internal static string DataView_CanNotDelete { + internal static string SQLUDT_InvalidUdtTypeName { get { - return ResourceManager.GetString("DataView_CanNotDelete", resourceCulture); + return ResourceManager.GetString("SQLUDT_InvalidUdtTypeName", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot edit on a DataSource where AllowEdit is false.. + /// Looks up a localized string similar to unexpected error encountered in SqlClient data provider. {0}. /// - internal static string DataView_CanNotEdit { + internal static string SQLUDT_Unexpected { get { - return ResourceManager.GetString("DataView_CanNotEdit", resourceCulture); + return ResourceManager.GetString("SQLUDT_Unexpected", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change DataSet property once it is set.. + /// Looks up a localized string similar to UdtTypeName property must be set only for UDT parameters.. /// - internal static string DataView_CanNotSetDataSet { + internal static string SQLUDT_UnexpectedUdtTypeName { get { - return ResourceManager.GetString("DataView_CanNotSetDataSet", resourceCulture); + return ResourceManager.GetString("SQLUDT_UnexpectedUdtTypeName", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change Table property once it is set.. + /// Looks up a localized string similar to no UDT attribute. /// - internal static string DataView_CanNotSetTable { + internal static string SqlUdtReason_NoUdtAttribute { get { - return ResourceManager.GetString("DataView_CanNotSetTable", resourceCulture); + return ResourceManager.GetString("SqlUdtReason_NoUdtAttribute", resourceCulture); } } /// - /// Looks up a localized string similar to DataTable must be set prior to using DataView.. + /// Looks up a localized string similar to Attestation information was not returned by SQL Server. Enclave type is '{0}' and enclave attestation URL is '{1}'.. /// - internal static string DataView_CanNotUse { + internal static string TCE_AttestationInfoNotReturnedFromSQLServer { get { - return ResourceManager.GetString("DataView_CanNotUse", resourceCulture); + return ResourceManager.GetString("TCE_AttestationInfoNotReturnedFromSQLServer", resourceCulture); } } /// - /// Looks up a localized string similar to DataSet must be set prior to using DataViewManager.. + /// Looks up a localized string similar to Error occurred when generating enclave package. Attestation Protocol has not been specified in the connection string, but the query requires enclave computations.. /// - internal static string DataView_CanNotUseDataViewManager { + internal static string TCE_AttestationProtocolNotSpecifiedForGeneratingEnclavePackage { get { - return ResourceManager.GetString("DataView_CanNotUseDataViewManager", resourceCulture); + return ResourceManager.GetString("TCE_AttestationProtocolNotSpecifiedForGeneratingEnclavePackage", resourceCulture); } } /// - /// Looks up a localized string similar to The relation is not parented to the table to which this DataView points.. + /// Looks up a localized string similar to You have specified the attestation protocol in the connection string, but the SQL Server in use does not support enclave based computations - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details.. /// - internal static string DataView_CreateChildView { + internal static string TCE_AttestationProtocolNotSupported { get { - return ResourceManager.GetString("DataView_CreateChildView", resourceCulture); + return ResourceManager.GetString("TCE_AttestationProtocolNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to Index {0} is either negative or above rows count.. + /// Looks up a localized string similar to Failed to initialize connection. The attestation protocol '{0}' does not support the enclave type '{1}'.. /// - internal static string DataView_GetElementIndex { + internal static string TCE_AttestationProtocolNotSupportEnclaveType { get { - return ResourceManager.GetString("DataView_GetElementIndex", resourceCulture); + return ResourceManager.GetString("TCE_AttestationProtocolNotSupportEnclaveType", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot insert external objects to this list.. + /// Looks up a localized string similar to You have specified the enclave attestation URL in the connection string, but the SQL Server in use does not support enclave based computations - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details.. /// - internal static string DataView_InsertExternalObject { + internal static string TCE_AttestationURLNotSupported { get { - return ResourceManager.GetString("DataView_InsertExternalObject", resourceCulture); + return ResourceManager.GetString("TCE_AttestationURLNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to DataView is not open.. + /// Looks up a localized string similar to {0} should be identical on all commands ({1}, {2}, {3}, {4}) when doing batch updates.. /// - internal static string DataView_NotOpen { + internal static string TCE_BatchedUpdateColumnEncryptionSettingMismatch { get { - return ResourceManager.GetString("DataView_NotOpen", resourceCulture); + return ResourceManager.GetString("TCE_BatchedUpdateColumnEncryptionSettingMismatch", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove objects not in the list.. + /// Looks up a localized string similar to Key store providers cannot be set more than once.. /// - internal static string DataView_RemoveExternalObject { + internal static string TCE_CanOnlyCallOnce { get { - return ResourceManager.GetString("DataView_RemoveExternalObject", resourceCulture); + return ResourceManager.GetString("TCE_CanOnlyCallOnce", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change DataSet on a DataViewManager that's already the default view for a DataSet.. + /// Looks up a localized string similar to Certificate with thumbprint '{0}' not found in certificate store '{1}' in certificate location '{2}'.. /// - internal static string DataView_SetDataSetFailed { + internal static string TCE_CertificateNotFound { get { - return ResourceManager.GetString("DataView_SetDataSetFailed", resourceCulture); + return ResourceManager.GetString("TCE_CertificateNotFound", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set {0}.. + /// Looks up a localized string similar to Certificate with thumbprint '{0}' not found in certificate store '{1}' in certificate location '{2}'. 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.. /// - internal static string DataView_SetFailed { + internal static string TCE_CertificateNotFoundSysErr { get { - return ResourceManager.GetString("DataView_SetFailed", resourceCulture); + return ResourceManager.GetString("TCE_CertificateNotFoundSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot set an object into this list.. + /// Looks up a localized string similar to Certificate specified in key path '{0}' does not have a private key to encrypt a column encryption key. Verify the certificate is imported correctly.. /// - internal static string DataView_SetIListObject { + internal static string TCE_CertificateWithNoPrivateKey { get { - return ResourceManager.GetString("DataView_SetIListObject", resourceCulture); + return ResourceManager.GetString("TCE_CertificateWithNoPrivateKey", resourceCulture); } } /// - /// Looks up a localized string similar to RowStateFilter cannot show ModifiedOriginals and ModifiedCurrents at the same time.. + /// Looks up a localized string similar to Certificate specified in key path '{0}' does not have a private key to decrypt a column encryption key. Verify the certificate is imported correctly.. /// - internal static string DataView_SetRowStateFilter { + internal static string TCE_CertificateWithNoPrivateKeySysErr { get { - return ResourceManager.GetString("DataView_SetRowStateFilter", resourceCulture); + return ResourceManager.GetString("TCE_CertificateWithNoPrivateKeySysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot change Table property on a DefaultView or a DataView coming from a DataViewManager.. + /// Looks up a localized string similar to Failed to decrypt column '{0}'.. /// - internal static string DataView_SetTable { + internal static string TCE_ColumnDecryptionFailed { get { - return ResourceManager.GetString("DataView_SetTable", resourceCulture); + return ResourceManager.GetString("TCE_ColumnDecryptionFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether this DataView and the user interface associated with it allows deletes.. + /// Looks up a localized string similar to Internal Error. Encrypted column encryption keys not found when trying to send the keys to the enclave.. /// - internal static string DataViewAllowDeleteDescr { + internal static string TCE_ColumnEncryptionKeysNotFound { get { - return ResourceManager.GetString("DataViewAllowDeleteDescr", resourceCulture); + return ResourceManager.GetString("TCE_ColumnEncryptionKeysNotFound", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether this DataView and the user interface associated with it allows edits.. + /// Looks up a localized string similar to Internal error. The signature returned by SQL Server for enclave-enabled column master key, specified at key path '{0}', cannot be null or empty.. /// - internal static string DataViewAllowEditDescr { + internal static string TCE_ColumnMasterKeySignatureNotFound { get { - return ResourceManager.GetString("DataViewAllowEditDescr", resourceCulture); + return ResourceManager.GetString("TCE_ColumnMasterKeySignatureNotFound", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether this DataView and the user interface associated with it allows new rows to be added.. + /// Looks up a localized string similar to The signature returned by SQL Server for the column master key, specified in key path '{0}', is invalid (does not match the computed signature). Recreate column master key metadata, making sure the signature inside the metadata is computed using the column master key being referenced in the metadata. If the error persists, please contact Microsoft for assistance.. /// - internal static string DataViewAllowNewDescr { + internal static string TCE_ColumnMasterKeySignatureVerificationFailed { get { - return ResourceManager.GetString("DataViewAllowNewDescr", resourceCulture); + return ResourceManager.GetString("TCE_ColumnMasterKeySignatureVerificationFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether to use the default sort if the Sort property is not set.. + /// Looks up a localized string similar to Specifies an attestation protocol for its corresponding enclave attestation service.. /// - internal static string DataViewApplyDefaultSortDescr { + internal static string TCE_DbConnectionString_AttestationProtocol { get { - return ResourceManager.GetString("DataViewApplyDefaultSortDescr", resourceCulture); + return ResourceManager.GetString("TCE_DbConnectionString_AttestationProtocol", resourceCulture); } } /// - /// Looks up a localized string similar to Returns the number of items currently in this view.. + /// Looks up a localized string similar to Default column encryption setting for all the commands on the connection.. /// - internal static string DataViewCountDescr { + internal static string TCE_DbConnectionString_ColumnEncryptionSetting { get { - return ResourceManager.GetString("DataViewCountDescr", resourceCulture); + return ResourceManager.GetString("TCE_DbConnectionString_ColumnEncryptionSetting", resourceCulture); } } /// - /// Looks up a localized string similar to This returns a pointer to back to the DataViewManager that owns this DataSet (if any).. + /// Looks up a localized string similar to Specifies an endpoint of an enclave attestation service, which will be used to verify whether the enclave, configured in the SQL Server instance for computations on database columns encrypted using Always Encrypted, is valid and secure.. /// - internal static string DataViewDataViewManagerDescr { + internal static string TCE_DbConnectionString_EnclaveAttestationUrl { get { - return ResourceManager.GetString("DataViewDataViewManagerDescr", resourceCulture); + return ResourceManager.GetString("TCE_DbConnectionString_EnclaveAttestationUrl", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates whether the view is open.. + /// Looks up a localized string similar to Specifies an IP address preference when connecting to SQL instances.. /// - internal static string DataViewIsOpenDescr { + internal static string TCE_DbConnectionString_IPAddressPreference { get { - return ResourceManager.GetString("DataViewIsOpenDescr", resourceCulture); + return ResourceManager.GetString("TCE_DbConnectionString_IPAddressPreference", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates that the data returned by this DataView has somehow changed.. + /// Looks up a localized string similar to Decryption failed. The last 10 bytes of the encrypted column encryption key are: '{0}'. The first 10 bytes of ciphertext are: '{1}'.. /// - internal static string DataViewListChangedDescr { + internal static string TCE_DecryptionFailed { get { - return ResourceManager.GetString("DataViewListChangedDescr", resourceCulture); + return ResourceManager.GetString("TCE_DecryptionFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the source of data for this DataViewManager.. + /// Looks up a localized string similar to Internal Error. Empty argument '{0}' specified when constructing an object of type '{1}'. '{0}' cannot be empty.. /// - internal static string DataViewManagerDataSetDescr { + internal static string TCE_EmptyArgumentInConstructorInternal { get { - return ResourceManager.GetString("DataViewManagerDataSetDescr", resourceCulture); + return ResourceManager.GetString("TCE_EmptyArgumentInConstructorInternal", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the sorting/filtering/state settings for any table in the corresponding DataSet.. + /// Looks up a localized string similar to Internal Error. Argument '{0}' cannot be empty when executing method '{1}.{2}'.. /// - internal static string DataViewManagerTableSettingsDescr { + internal static string TCE_EmptyArgumentInternal { get { - return ResourceManager.GetString("DataViewManagerTableSettingsDescr", resourceCulture); + return ResourceManager.GetString("TCE_EmptyArgumentInternal", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates an expression used to filter the data returned by this DataView.. + /// Looks up a localized string similar to Empty certificate thumbprint specified in certificate path '{0}'.. /// - internal static string DataViewRowFilterDescr { + internal static string TCE_EmptyCertificateThumbprint { get { - return ResourceManager.GetString("DataViewRowFilterDescr", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCertificateThumbprint", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the versions of data returned by this DataView.. + /// Looks up a localized string similar to Internal error. Empty certificate thumbprint specified in certificate path '{0}'.. /// - internal static string DataViewRowStateFilterDescr { + internal static string TCE_EmptyCertificateThumbprintSysErr { get { - return ResourceManager.GetString("DataViewRowStateFilterDescr", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCertificateThumbprintSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the names of the column and the order in which data is returned by this DataView.. + /// Looks up a localized string similar to Empty key identifier specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. /// - internal static string DataViewSortDescr { + internal static string TCE_EmptyCngKeyId { get { - return ResourceManager.GetString("DataViewSortDescr", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCngKeyId", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the table this DataView uses to get data.. + /// Looks up a localized string similar to Internal error. Empty key identifier specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. /// - internal static string DataViewTableDescr { + internal static string TCE_EmptyCngKeyIdSysErr { get { - return ResourceManager.GetString("DataViewTableDescr", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCngKeyIdSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Command text to execute.. + /// Looks up a localized string similar to Empty Microsoft Cryptography API: Next Generation (CNG) provider name specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. /// - internal static string DbCommand_CommandText { + internal static string TCE_EmptyCngName { get { - return ResourceManager.GetString("DbCommand_CommandText", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCngName", resourceCulture); } } /// - /// Looks up a localized string similar to Time to wait for command to execute.. + /// Looks up a localized string similar to Internal error. Empty Microsoft Cryptography API: Next Generation (CNG) provider name specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. /// - internal static string DbCommand_CommandTimeout { + internal static string TCE_EmptyCngNameSysErr { get { - return ResourceManager.GetString("DbCommand_CommandTimeout", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCngNameSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to How to interpret the CommandText.. + /// Looks up a localized string similar to Empty column encryption key specified.. /// - internal static string DbCommand_CommandType { + internal static string TCE_EmptyColumnEncryptionKey { get { - return ResourceManager.GetString("DbCommand_CommandType", resourceCulture); + return ResourceManager.GetString("TCE_EmptyColumnEncryptionKey", resourceCulture); } } /// - /// Looks up a localized string similar to Connection used by the command.. + /// Looks up a localized string similar to Empty key identifier specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. /// - internal static string DbCommand_Connection { + internal static string TCE_EmptyCspKeyId { get { - return ResourceManager.GetString("DbCommand_Connection", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCspKeyId", resourceCulture); } } /// - /// Looks up a localized string similar to The parameters collection.. + /// Looks up a localized string similar to Internal error. Empty key identifier specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. /// - internal static string DbCommand_Parameters { + internal static string TCE_EmptyCspKeyIdSysErr { get { - return ResourceManager.GetString("DbCommand_Parameters", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCspKeyIdSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to When records are affected by a given statement by the execution of the command.. + /// Looks up a localized string similar to Empty Microsoft cryptographic service provider (CSP) name specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. /// - internal static string DbCommand_StatementCompleted { + internal static string TCE_EmptyCspName { get { - return ResourceManager.GetString("DbCommand_StatementCompleted", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCspName", resourceCulture); } } /// - /// Looks up a localized string similar to The transaction used by the command.. + /// Looks up a localized string similar to Internal error. Empty Microsoft cryptographic service provider (CSP) name specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. /// - internal static string DbCommand_Transaction { + internal static string TCE_EmptyCspNameSysErr { get { - return ResourceManager.GetString("DbCommand_Transaction", resourceCulture); + return ResourceManager.GetString("TCE_EmptyCspNameSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to When used by a DataAdapter.Update, how command results are applied to the current DataRow.. + /// Looks up a localized string similar to Internal error. Empty encrypted column encryption key specified.. /// - internal static string DbCommand_UpdatedRowSource { + internal static string TCE_EmptyEncryptedColumnEncryptionKey { get { - return ResourceManager.GetString("DbCommand_UpdatedRowSource", resourceCulture); + return ResourceManager.GetString("TCE_EmptyEncryptedColumnEncryptionKey", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the position of the catalog name in a qualified table name in a text command.. + /// Looks up a localized string similar to Invalid key store provider name specified. Key store provider names cannot be null or empty.. /// - internal static string DbCommandBuilder_CatalogLocation { + internal static string TCE_EmptyProviderName { get { - return ResourceManager.GetString("DbCommandBuilder_CatalogLocation", resourceCulture); + return ResourceManager.GetString("TCE_EmptyProviderName", resourceCulture); } } /// - /// Looks up a localized string similar to The character that separates the catalog name from the rest of the identifier in a text command.. + /// Looks up a localized string similar to You have specified the enclave attestation URL and attestation protocol in the connection string, but the SQL Server in use does not support enclave based computations - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details.. /// - internal static string DbCommandBuilder_CatalogSeparator { + internal static string TCE_EnclaveComputationsNotSupported { get { - return ResourceManager.GetString("DbCommandBuilder_CatalogSeparator", resourceCulture); + return ResourceManager.GetString("TCE_EnclaveComputationsNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to How the where clause is auto-generated for the Update and Delete commands when not specified by the user.. + /// Looks up a localized string similar to No enclave provider found for enclave type '{0}' and attestation protocol '{1}'. Please specify the correct attestation protocol in the connection string. . /// - internal static string DbCommandBuilder_ConflictOption { + internal static string TCE_EnclaveProviderNotFound { get { - return ResourceManager.GetString("DbCommandBuilder_ConflictOption", resourceCulture); + return ResourceManager.GetString("TCE_EnclaveProviderNotFound", resourceCulture); } } /// - /// Looks up a localized string similar to The DataAdapter for which to automatically generate Commands.. + /// Looks up a localized string similar to You have specified the enclave attestation URL in the connection string, but the SQL Server did not return an enclave type. Please make sure the enclave type is correctly configured in your instance - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details.. /// - internal static string DbCommandBuilder_DataAdapter { + internal static string TCE_EnclaveTypeNotReturned { get { - return ResourceManager.GetString("DbCommandBuilder_DataAdapter", resourceCulture); + return ResourceManager.GetString("TCE_EnclaveTypeNotReturned", resourceCulture); } } /// - /// Looks up a localized string similar to The prefix string wrapped around sql objects.. + /// Looks up a localized string similar to The enclave type '{0}' returned from the server is not supported.. /// - internal static string DbCommandBuilder_QuotePrefix { + internal static string TCE_EnclaveTypeNotSupported { get { - return ResourceManager.GetString("DbCommandBuilder_QuotePrefix", resourceCulture); + return ResourceManager.GetString("TCE_EnclaveTypeNotSupported", resourceCulture); } } /// - /// Looks up a localized string similar to The suffix string wrapped around sql objects.. + /// Looks up a localized string similar to Internal Error. Enclave type received from SQL Server is null or empty when executing a query requiring enclave computations.. /// - internal static string DbCommandBuilder_QuoteSuffix { + internal static string TCE_EnclaveTypeNullForEnclaveBasedQuery { get { - return ResourceManager.GetString("DbCommandBuilder_QuoteSuffix", resourceCulture); + return ResourceManager.GetString("TCE_EnclaveTypeNullForEnclaveBasedQuery", resourceCulture); } } /// - /// Looks up a localized string similar to Use schema from DataTable or the SelectCommand.. + /// Looks up a localized string similar to Error encountered while generating package to be sent to enclave. Error message: {0}. /// - internal static string DbCommandBuilder_SchemaLocation { + internal static string TCE_ExceptionWhenGeneratingEnclavePackage { get { - return ResourceManager.GetString("DbCommandBuilder_SchemaLocation", resourceCulture); + return ResourceManager.GetString("TCE_ExceptionWhenGeneratingEnclavePackage", resourceCulture); } } /// - /// Looks up a localized string similar to The character that separates the schema name from the rest of the identifier in a text command.. + /// Looks up a localized string similar to Internal Error. Failed to encrypt byte package to be sent to the enclave. Error Message: {0} . /// - internal static string DbCommandBuilder_SchemaSeparator { + internal static string TCE_FailedToEncryptRegisterRulesBytePackage { get { - return ResourceManager.GetString("DbCommandBuilder_SchemaSeparator", resourceCulture); + return ResourceManager.GetString("TCE_FailedToEncryptRegisterRulesBytePackage", resourceCulture); } } /// - /// Looks up a localized string similar to How the set clause is auto-generated for the Update command when not specified by the user.. + /// Looks up a localized string similar to Internal Error. The buffer specified by argument '{0}' for method '{1}.{2}' has insufficient space.. /// - internal static string DbCommandBuilder_SetAllValues { + internal static string TCE_InsufficientBuffer { get { - return ResourceManager.GetString("DbCommandBuilder_SetAllValues", resourceCulture); + return ResourceManager.GetString("TCE_InsufficientBuffer", resourceCulture); } } /// - /// Looks up a localized string similar to Event triggered when messages arrive from the DataSource.. + /// Looks up a localized string similar to The specified ciphertext's encryption algorithm version '{0}' does not match the expected encryption algorithm version '{1}'.. /// - internal static string DbConnection_InfoMessage { + internal static string TCE_InvalidAlgorithmVersion { get { - return ResourceManager.GetString("DbConnection_InfoMessage", resourceCulture); + return ResourceManager.GetString("TCE_InvalidAlgorithmVersion", resourceCulture); } } /// - /// Looks up a localized string similar to The ConnectionState indicating whether the connection is open or closed.. + /// Looks up a localized string similar to Specified encrypted column encryption key contains an invalid encryption algorithm version '{0}'. Expected version is '{1}'.. /// - internal static string DbConnection_State { + internal static string TCE_InvalidAlgorithmVersionInEncryptedCEK { get { - return ResourceManager.GetString("DbConnection_State", resourceCulture); + return ResourceManager.GetString("TCE_InvalidAlgorithmVersionInEncryptedCEK", resourceCulture); } } /// - /// Looks up a localized string similar to Event triggered when the connection changes state.. + /// Looks up a localized string similar to Invalid attestation parameters specified by the enclave provider for enclave type '{0}'. Error occurred when converting the value '{1}' of parameter '{2}' to unsigned int. Error Message: {3}. /// - internal static string DbConnection_StateChange { + internal static string TCE_InvalidAttestationParameterUnableToConvertToUnsignedInt { get { - return ResourceManager.GetString("DbConnection_StateChange", resourceCulture); + return ResourceManager.GetString("TCE_InvalidAttestationParameterUnableToConvertToUnsignedInt", resourceCulture); } } /// - /// Looks up a localized string similar to When true, indicates that managed connection pooling should be used.. + /// Looks up a localized string similar to Specified ciphertext has an invalid authentication tag.. /// - internal static string DbConnectionString_AdoNetPooler { + internal static string TCE_InvalidAuthenticationTag { get { - return ResourceManager.GetString("DbConnectionString_AdoNetPooler", resourceCulture); + return ResourceManager.GetString("TCE_InvalidAuthenticationTag", resourceCulture); } } /// - /// Looks up a localized string similar to Declares the application workload type when connecting to a server.. + /// Looks up a localized string similar to Invalid certificate location '{0}' in certificate path '{1}'. Use the following format: <certificate location>{4}<certificate store>{4}<certificate thumbprint>, where <certificate location> is either '{2}' or '{3}'.. /// - internal static string DbConnectionString_ApplicationIntent { + internal static string TCE_InvalidCertificateLocation { get { - return ResourceManager.GetString("DbConnectionString_ApplicationIntent", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificateLocation", resourceCulture); } } /// - /// Looks up a localized string similar to The name of the application.. + /// Looks up a localized string similar to Invalid certificate location '{0}' in certificate path '{1}'. Use the following format: <certificate location>{3}<certificate store>{3}<certificate thumbprint>, where <certificate location> is '{2}'.. /// - internal static string DbConnectionString_ApplicationName { + internal static string TCE_InvalidCertificateLocation_Unix { get { - return ResourceManager.GetString("DbConnectionString_ApplicationName", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificateLocation_Unix", resourceCulture); } } /// - /// Looks up a localized string similar to When true, enables usage of the Asynchronous functionality in the .NET Framework Data Provider.. + /// Looks up a localized string similar to Internal error. Invalid certificate location '{0}' in certificate path '{1}'. Use the following format: <certificate location>{4}<certificate store>{4}<certificate thumbprint>, where <certificate location> is either '{2}' or '{3}'.. /// - internal static string DbConnectionString_AsynchronousProcessing { + internal static string TCE_InvalidCertificateLocationSysErr { get { - return ResourceManager.GetString("DbConnectionString_AsynchronousProcessing", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificateLocationSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to The name of the primary file, including the full path name, of an attachable database.. + /// Looks up a localized string similar to Internal error. Invalid certificate location '{0}' in certificate path '{1}'. Use the following format: <certificate location>{3}<certificate store>{3}<certificate thumbprint>, where <certificate location> is '{2}'.. /// - internal static string DbConnectionString_AttachDBFilename { + internal static string TCE_InvalidCertificateLocationSysErr_Unix { get { - return ResourceManager.GetString("DbConnectionString_AttachDBFilename", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificateLocationSysErr_Unix", resourceCulture); } } /// - /// Looks up a localized string similar to Specifies the method of authenticating with SQL Server.. + /// Looks up a localized string similar to Invalid certificate path: '{0}'. Use the following format: <certificate location>{3}<certificate store>{3}<certificate thumbprint>, where <certificate location> is either '{1}' or '{2}'.. /// - internal static string DbConnectionString_Authentication { + internal static string TCE_InvalidCertificatePath { get { - return ResourceManager.GetString("DbConnectionString_Authentication", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificatePath", resourceCulture); } } /// - /// Looks up a localized string similar to Specified client certificate for authenticating with SQL Server. . + /// Looks up a localized string similar to Invalid certificate path: '{0}'. Use the following format: <certificate location>{2}<certificate store>{2}<certificate thumbprint>, where <certificate location> is '{1}'.. /// - internal static string DbConnectionString_Certificate { + internal static string TCE_InvalidCertificatePath_Unix { get { - return ResourceManager.GetString("DbConnectionString_Certificate", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificatePath_Unix", resourceCulture); } } /// - /// Looks up a localized string similar to When true, indicates the connection state is reset when removed from the pool.. + /// Looks up a localized string similar to Internal error. Invalid certificate path: '{0}'. Use the following format: <certificate location>{3}<certificate store>{3}<certificate thumbprint>, where <certificate location> is either '{1}' or '{2}'.. /// - internal static string DbConnectionString_ConnectionReset { + internal static string TCE_InvalidCertificatePathSysErr { get { - return ResourceManager.GetString("DbConnectionString_ConnectionReset", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificatePathSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to The connection string used to connect to the Data Source.. + /// Looks up a localized string similar to Internal error. Invalid certificate path: '{0}'. Use the following format: <certificate location>{2}<certificate store>{2}<certificate thumbprint>, where <certificate location> is '{1}'.. /// - internal static string DbConnectionString_ConnectionString { + internal static string TCE_InvalidCertificatePathSysErr_Unix { get { - return ResourceManager.GetString("DbConnectionString_ConnectionString", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificatePathSysErr_Unix", resourceCulture); } } /// - /// Looks up a localized string similar to Number of attempts to restore connection.. + /// Looks up a localized string similar to Invalid certificate store '{0}' specified in certificate path '{1}'. Expected value: '{2}'.. /// - internal static string DbConnectionString_ConnectRetryCount { + internal static string TCE_InvalidCertificateStore { get { - return ResourceManager.GetString("DbConnectionString_ConnectRetryCount", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificateStore", resourceCulture); } } /// - /// Looks up a localized string similar to Delay between attempts to restore connection.. + /// Looks up a localized string similar to Internal error. Invalid certificate store '{0}' specified in certificate path '{1}'. Expected value: '{2}'.. /// - internal static string DbConnectionString_ConnectRetryInterval { + internal static string TCE_InvalidCertificateStoreSysErr { get { - return ResourceManager.GetString("DbConnectionString_ConnectRetryInterval", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCertificateStoreSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.. + /// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.. /// - internal static string DbConnectionString_ConnectTimeout { + internal static string TCE_InvalidCiphertextLengthInEncryptedCEK { get { - return ResourceManager.GetString("DbConnectionString_ConnectTimeout", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCiphertextLengthInEncryptedCEK", resourceCulture); } } /// - /// Looks up a localized string similar to When true, indicates the connection should be from the Sql Server context. Available only when running in the Sql Server process.. + /// Looks up a localized string similar to Specified ciphertext has an invalid size of {0} bytes, which is below the minimum {1} bytes required for decryption.. /// - internal static string DbConnectionString_ContextConnection { + internal static string TCE_InvalidCipherTextSize { get { - return ResourceManager.GetString("DbConnectionString_ContextConnection", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCipherTextSize", resourceCulture); } } /// - /// Looks up a localized string similar to The SQL Server Language record name.. + /// Looks up a localized string similar to An error occurred while opening the Microsoft Cryptography API: Next Generation (CNG) key: '{0}'. Verify that the CNG provider name '{1}' is valid, installed on the machine, and the key '{2}' exists.. /// - internal static string DbConnectionString_CurrentLanguage { + internal static string TCE_InvalidCngKey { get { - return ResourceManager.GetString("DbConnectionString_CurrentLanguage", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCngKey", resourceCulture); } } /// - /// Looks up a localized string similar to Indicates the name of the data source to connect to.. + /// Looks up a localized string similar to Internal error. An error occurred while opening the Microsoft Cryptography API: Next Generation (CNG) key: '{0}'. Verify that the CNG provider name '{1}' is valid, installed on the machine, and the key '{2}' exists.. /// - internal static string DbConnectionString_DataSource { + internal static string TCE_InvalidCngKeySysErr { get { - return ResourceManager.GetString("DbConnectionString_DataSource", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCngKeySysErr", resourceCulture); } } /// - /// Looks up a localized string similar to The name of the ODBC Driver to use when connecting to the Data Source.. + /// Looks up a localized string similar to Invalid column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. /// - internal static string DbConnectionString_Driver { + internal static string TCE_InvalidCngPath { get { - return ResourceManager.GetString("DbConnectionString_Driver", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCngPath", resourceCulture); } } /// - /// Looks up a localized string similar to The DSN to use when connecting to the Data Source.. + /// Looks up a localized string similar to Internal error. Invalid column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. /// - internal static string DbConnectionString_DSN { + internal static string TCE_InvalidCngPathSysErr { get { - return ResourceManager.GetString("DbConnectionString_DSN", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCngPathSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to When true, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.. + /// Looks up a localized string similar to Invalid key identifier: '{0}'. Verify that the key identifier in column master key path: '{1}' is valid and exists in the CSP.. /// - internal static string DbConnectionString_Encrypt { + internal static string TCE_InvalidCspKeyId { get { - return ResourceManager.GetString("DbConnectionString_Encrypt", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCspKeyId", resourceCulture); } } /// - /// Looks up a localized string similar to Sessions in a Component Services (or MTS, if you are using Microsoft Windows NT) environment should automatically be enlisted in a global transaction where required.. + /// Looks up a localized string similar to Internal error. Invalid key identifier: '{0}'. Verify that the key identifier in column master key path: '{1}' is valid and exists in the CSP.. /// - internal static string DbConnectionString_Enlist { + internal static string TCE_InvalidCspKeyIdSysErr { get { - return ResourceManager.GetString("DbConnectionString_Enlist", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCspKeyIdSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to The name or network address of the instance of SQL Server that acts as a failover partner.. + /// Looks up a localized string similar to Invalid Microsoft cryptographic service provider (CSP) name: '{0}'. Verify that the CSP provider name in column master key path: '{1}' is valid and installed on the machine.. /// - internal static string DbConnectionString_FailoverPartner { + internal static string TCE_InvalidCspName { get { - return ResourceManager.GetString("DbConnectionString_FailoverPartner", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCspName", resourceCulture); } } /// - /// Looks up a localized string similar to The service principal name (SPN) of the failover partner.. + /// Looks up a localized string similar to Internal error. Invalid Microsoft cryptographic service provider (CSP) name: '{0}'. Verify that the CSP provider name in column master key path: '{1}' is valid and installed on the machine.. /// - internal static string DbConnectionString_FailoverPartnerSPN { + internal static string TCE_InvalidCspNameSysErr { get { - return ResourceManager.GetString("DbConnectionString_FailoverPartnerSPN", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCspNameSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to The UDL file to use when connecting to the Data Source.. + /// Looks up a localized string similar to Invalid column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. /// - internal static string DbConnectionString_FileName { + internal static string TCE_InvalidCspPath { get { - return ResourceManager.GetString("DbConnectionString_FileName", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCspPath", resourceCulture); } } /// - /// Looks up a localized string similar to The hostname to be expected in the server's certificate when encryption is negotiated, if it's different from the default value derived from Addr/Address/Server.. + /// Looks up a localized string similar to Internal error. Invalid column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. /// - internal static string DbConnectionString_HostNameInCertificate { + internal static string TCE_InvalidCspPathSysErr { get { - return ResourceManager.GetString("DbConnectionString_HostNameInCertificate", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCspPathSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to The name of the initial catalog or database in the data source.. + /// Looks up a localized string similar to Invalid key store provider name '{0}'. '{1}' prefix is reserved for system key store providers.. /// - internal static string DbConnectionString_InitialCatalog { + internal static string TCE_InvalidCustomKeyStoreProviderName { get { - return ResourceManager.GetString("DbConnectionString_InitialCatalog", resourceCulture); + return ResourceManager.GetString("TCE_InvalidCustomKeyStoreProviderName", resourceCulture); } } /// - /// Looks up a localized string similar to Whether the connection is to be a secure connection or not.. + /// Looks up a localized string similar to Internal Error. The given database id '{0}' is not valid. Error occurred when converting the database id to unsigned int. Error Message: {1}. /// - internal static string DbConnectionString_IntegratedSecurity { + internal static string TCE_InvalidDatabaseIdUnableToCastToUnsignedInt { get { - return ResourceManager.GetString("DbConnectionString_IntegratedSecurity", resourceCulture); + return ResourceManager.GetString("TCE_InvalidDatabaseIdUnableToCastToUnsignedInt", resourceCulture); } } /// - /// Looks up a localized string similar to The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed.. + /// Looks up a localized string similar to Internal error. Error occurred when populating enclave metadata. The referenced column encryption key ordinal '{0}' is missing in the encryption metadata returned by SQL Server. Max ordinal is '{1}'. . /// - internal static string DbConnectionString_LoadBalanceTimeout { + internal static string TCE_InvalidEncryptionKeyOrdinalEnclaveMetadata { get { - return ResourceManager.GetString("DbConnectionString_LoadBalanceTimeout", resourceCulture); + return ResourceManager.GetString("TCE_InvalidEncryptionKeyOrdinalEnclaveMetadata", resourceCulture); } } /// - /// Looks up a localized string similar to The maximum number of connections allowed in the pool.. + /// Looks up a localized string similar to Internal error. Error occurred when populating parameter metadata. The referenced column encryption key ordinal '{0}' is missing in the encryption metadata returned by SQL Server. Max ordinal is '{1}'. . /// - internal static string DbConnectionString_MaxPoolSize { + internal static string TCE_InvalidEncryptionKeyOrdinalParameterMetadata { get { - return ResourceManager.GetString("DbConnectionString_MaxPoolSize", resourceCulture); + return ResourceManager.GetString("TCE_InvalidEncryptionKeyOrdinalParameterMetadata", resourceCulture); } } /// - /// Looks up a localized string similar to The minimum number of connections allowed in the pool.. + /// Looks up a localized string similar to Encryption type '{1}' specified for the column in the database is either invalid or corrupted. Valid encryption types for algorithm '{0}' are: {2}.. /// - internal static string DbConnectionString_MinPoolSize { + internal static string TCE_InvalidEncryptionType { get { - return ResourceManager.GetString("DbConnectionString_MinPoolSize", resourceCulture); + return ResourceManager.GetString("TCE_InvalidEncryptionType", resourceCulture); } } /// - /// Looks up a localized string similar to When true, multiple result sets can be returned and read from one connection.. + /// Looks up a localized string similar to Invalid key encryption algorithm specified: '{0}'. Expected value: '{1}'.. /// - internal static string DbConnectionString_MultipleActiveResultSets { + internal static string TCE_InvalidKeyEncryptionAlgorithm { get { - return ResourceManager.GetString("DbConnectionString_MultipleActiveResultSets", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to If your application is connecting to a high-availability, disaster recovery (AlwaysOn) availability group (AG) on different subnets, MultiSubnetFailover=Yes configures SqlConnection to provide faster detection of and connection to the (currently) active server.. - /// - internal static string DbConnectionString_MultiSubnetFailover { - get { - return ResourceManager.GetString("DbConnectionString_MultiSubnetFailover", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The network library used to establish a connection to an instance of SQL Server.. - /// - internal static string DbConnectionString_NetworkLibrary { - get { - return ResourceManager.GetString("DbConnectionString_NetworkLibrary", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specifies which OLE DB Services to enable or disable with the OleDb Provider.. - /// - internal static string DbConnectionString_OleDbServices { - get { - return ResourceManager.GetString("DbConnectionString_OleDbServices", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Size in bytes of the network packets used to communicate with an instance of SQL Server.. - /// - internal static string DbConnectionString_PacketSize { - get { - return ResourceManager.GetString("DbConnectionString_PacketSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates the password to be used when connecting to the data source.. - /// - internal static string DbConnectionString_Password { - get { - return ResourceManager.GetString("DbConnectionString_Password", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When false, security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state.. - /// - internal static string DbConnectionString_PersistSecurityInfo { - get { - return ResourceManager.GetString("DbConnectionString_PersistSecurityInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Defines the blocking period behavior for a connection pool.. - /// - internal static string DbConnectionString_PoolBlockingPeriod { - get { - return ResourceManager.GetString("DbConnectionString_PoolBlockingPeriod", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When true, the connection object is drawn from the appropriate pool, or if necessary, is created and added to the appropriate pool.. - /// - internal static string DbConnectionString_Pooling { - get { - return ResourceManager.GetString("DbConnectionString_Pooling", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The name of the OLE DB Provider to use when connecting to the Data Source.. - /// - internal static string DbConnectionString_Provider { - get { - return ResourceManager.GetString("DbConnectionString_Provider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used by SQL Server in Replication.. - /// - internal static string DbConnectionString_Replication { - get { - return ResourceManager.GetString("DbConnectionString_Replication", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The path to a certificate file to match against the SQL Server TLS/SSL certificate.. - /// - internal static string DbConnectionString_ServerCertificate { - get { - return ResourceManager.GetString("DbConnectionString_ServerCertificate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The service principal name (SPN) of the server.. - /// - internal static string DbConnectionString_ServerSPN { - get { - return ResourceManager.GetString("DbConnectionString_ServerSPN", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates binding behavior of connection to a System.Transactions Transaction when enlisted.. - /// - internal static string DbConnectionString_TransactionBinding { - get { - return ResourceManager.GetString("DbConnectionString_TransactionBinding", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to If your application connects to different networks, TransparentNetworkIPResolution=Yes configures SqlConnection to provide transparent connection resolution to the currently active server, independently of the network IP topology.. - /// - internal static string DbConnectionString_TransparentNetworkIPResolution { - get { - return ResourceManager.GetString("DbConnectionString_TransparentNetworkIPResolution", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate.. - /// - internal static string DbConnectionString_TrustServerCertificate { - get { - return ResourceManager.GetString("DbConnectionString_TrustServerCertificate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates which server type system the provider will expose through the DataReader.. - /// - internal static string DbConnectionString_TypeSystemVersion { - get { - return ResourceManager.GetString("DbConnectionString_TypeSystemVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates the user ID to be used when connecting to the data source.. - /// - internal static string DbConnectionString_UserID { - get { - return ResourceManager.GetString("DbConnectionString_UserID", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates whether the connection will be re-directed to connect to an instance of SQL Server running under the user's account.. - /// - internal static string DbConnectionString_UserInstance { - get { - return ResourceManager.GetString("DbConnectionString_UserInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The name of the workstation connecting to SQL Server.. - /// - internal static string DbConnectionString_WorkstationID { - get { - return ResourceManager.GetString("DbConnectionString_WorkstationID", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used during Update for deleted rows in DataSet.. - /// - internal static string DbDataAdapter_DeleteCommand { - get { - return ResourceManager.GetString("DbDataAdapter_DeleteCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used during Update for new rows in DataSet.. - /// - internal static string DbDataAdapter_InsertCommand { - get { - return ResourceManager.GetString("DbDataAdapter_InsertCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Event triggered before every DataRow during Update.. - /// - internal static string DbDataAdapter_RowUpdated { - get { - return ResourceManager.GetString("DbDataAdapter_RowUpdated", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Event triggered after every DataRow during Update.. - /// - internal static string DbDataAdapter_RowUpdating { - get { - return ResourceManager.GetString("DbDataAdapter_RowUpdating", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used during Fill/FillSchema.. - /// - internal static string DbDataAdapter_SelectCommand { - get { - return ResourceManager.GetString("DbDataAdapter_SelectCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Number of rows to batch together before executing against the data source.. - /// - internal static string DbDataAdapter_UpdateBatchSize { - get { - return ResourceManager.GetString("DbDataAdapter_UpdateBatchSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used during Update for modified rows in DataSet.. - /// - internal static string DbDataAdapter_UpdateCommand { - get { - return ResourceManager.GetString("DbDataAdapter_UpdateCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Only necessary to set for decimal and numeric parameters when using with Prepare, FillSchema and CommandBuilder scenarios.. - /// - internal static string DbDataParameter_Precision { - get { - return ResourceManager.GetString("DbDataParameter_Precision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Only necessary to set for decimal and numeric parameters when using with Prepare, FillSchema and CommandBuilder scenarios.. - /// - internal static string DbDataParameter_Scale { - get { - return ResourceManager.GetString("DbDataParameter_Scale", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter generic type.. - /// - internal static string DbParameter_DbType { - get { - return ResourceManager.GetString("DbParameter_DbType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Input, output, or bidirectional parameter.. - /// - internal static string DbParameter_Direction { - get { - return ResourceManager.GetString("DbParameter_Direction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to a design-time property used for strongly typed code-generation.. - /// - internal static string DbParameter_IsNullable { - get { - return ResourceManager.GetString("DbParameter_IsNullable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Offset in variable length data types.. - /// - internal static string DbParameter_Offset { - get { - return ResourceManager.GetString("DbParameter_Offset", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Name of the parameter.. - /// - internal static string DbParameter_ParameterName { - get { - return ResourceManager.GetString("DbParameter_ParameterName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Size of variable length data types (string & arrays).. - /// - internal static string DbParameter_Size { - get { - return ResourceManager.GetString("DbParameter_Size", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When used by a DataAdapter.Update, the source column name that is used to find the DataSetColumn name in the ColumnMappings. This is to copy a value between the parameter and a data row.. - /// - internal static string DbParameter_SourceColumn { - get { - return ResourceManager.GetString("DbParameter_SourceColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When used by DataAdapter.Update, the parameter value is changed from DBNull.Value into (Int32)1 or (Int32)0 if non-null.. - /// - internal static string DbParameter_SourceColumnNullMapping { - get { - return ResourceManager.GetString("DbParameter_SourceColumnNullMapping", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When used by a DataAdapter.Update (UpdateCommand only), the version of the DataRow value that is used to update the data source.. - /// - internal static string DbParameter_SourceVersion { - get { - return ResourceManager.GetString("DbParameter_SourceVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value of the parameter.. - /// - internal static string DbParameter_Value { - get { - return ResourceManager.GetString("DbParameter_Value", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to How are the Insert/Update/DeleteCommands generated when not set by the user.. - /// - internal static string DbTable_ConflictDetection { - get { - return ResourceManager.GetString("DbTable_ConflictDetection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection used if the the Select/Insert/Update/DeleteCommands do not already have a connection.. - /// - internal static string DbTable_Connection { - get { - return ResourceManager.GetString("DbTable_Connection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used during Update for deleted rows in the DataTable.. - /// - internal static string DbTable_DeleteCommand { - get { - return ResourceManager.GetString("DbTable_DeleteCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used during Update for new rows in the DataTable.. - /// - internal static string DbTable_InsertCommand { - get { - return ResourceManager.GetString("DbTable_InsertCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Should Fill return provider specific values or common CLSCompliant values.. - /// - internal static string DbTable_ReturnProviderSpecificTypes { - get { - return ResourceManager.GetString("DbTable_ReturnProviderSpecificTypes", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used during Fill.. - /// - internal static string DbTable_SelectCommand { - get { - return ResourceManager.GetString("DbTable_SelectCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to How to map source table to DataTable.. - /// - internal static string DbTable_TableMapping { - get { - return ResourceManager.GetString("DbTable_TableMapping", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Number of rows to batch together before executing against the data source.. - /// - internal static string DbTable_UpdateBatchSize { - get { - return ResourceManager.GetString("DbTable_UpdateBatchSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Used during Update for modified rows in the DataTable.. - /// - internal static string DbTable_UpdateCommand { - get { - return ResourceManager.GetString("DbTable_UpdateCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error occurred when retrying the download of the HGS root certificate after the initial request failed. Contact Customer Support Services.. - /// - internal static string EnclaveRetrySleepInSecondsValueException { - get { - return ResourceManager.GetString("EnclaveRetrySleepInSecondsValueException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Unable to invalidate the requested enclave session, because it does not exist in the cache. Contact Customer Support Services.. - /// - internal static string EnclaveSessionInvalidationFailed { - get { - return ResourceManager.GetString("EnclaveSessionInvalidationFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} returned {1}.. - /// - internal static string event_OperationReturnedSomething { - get { - return ResourceManager.GetString("event_OperationReturnedSomething", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of an attestation token failed. The token received from SQL Server is expired. Contact Customer Support Services.. - /// - internal static string ExpiredAttestationToken { - get { - return ResourceManager.GetString("ExpiredAttestationToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error in aggregate argument: Expecting a single column argument with possible 'Child' qualifier.. - /// - internal static string Expr_AggregateArgument { - get { - return ResourceManager.GetString("Expr_AggregateArgument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unbound reference in the aggregate expression '{0}'.. - /// - internal static string Expr_AggregateUnbound { - get { - return ResourceManager.GetString("Expr_AggregateUnbound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Operator '{0}' is ambiguous on operands of type '{1}' and '{2}'. Cannot mix signed and unsigned types. Please use explicit Convert() function.. - /// - internal static string Expr_AmbiguousBinop { - get { - return ResourceManager.GetString("Expr_AmbiguousBinop", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}() argument is out of range.. - /// - internal static string Expr_ArgumentOutofRange { - get { - return ResourceManager.GetString("Expr_ArgumentOutofRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type mismatch in function argument: {0}(), argument {1}, expected {2}.. - /// - internal static string Expr_ArgumentType { - get { - return ResourceManager.GetString("Expr_ArgumentType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type mismatch in function argument: {0}(), argument {1}, expected one of the Integer types.. - /// - internal static string Expr_ArgumentTypeInteger { - get { - return ResourceManager.GetString("Expr_ArgumentTypeInteger", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find the parent relation '{0}'.. - /// - internal static string Expr_BindFailure { - get { - return ResourceManager.GetString("Expr_BindFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot evaluate. Expression '{0}' is not an aggregate.. - /// - internal static string Expr_ComputeNotAggregate { - get { - return ResourceManager.GetString("Expr_ComputeNotAggregate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot convert from {0} to {1}.. - /// - internal static string Expr_DatatypeConversion { - get { - return ResourceManager.GetString("Expr_DatatypeConversion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot convert value '{0}' to Type: {1}.. - /// - internal static string Expr_DatavalueConversion { - get { - return ResourceManager.GetString("Expr_DatavalueConversion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Divide by zero error encountered.. - /// - internal static string Expr_DivideByZero { - get { - return ResourceManager.GetString("Expr_DivideByZero", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot evaluate non-constant expression without current row.. - /// - internal static string Expr_EvalNoContext { - get { - return ResourceManager.GetString("Expr_EvalNoContext", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expression is too complex.. - /// - internal static string Expr_ExpressionTooComplex { - get { - return ResourceManager.GetString("Expr_ExpressionTooComplex", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unbound reference in the expression '{0}'.. - /// - internal static string Expr_ExpressionUnbound { - get { - return ResourceManager.GetString("Expr_ExpressionUnbound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Filter expression '{0}' does not evaluate to a Boolean term.. - /// - internal static string Expr_FilterConversion { - get { - return ResourceManager.GetString("Expr_FilterConversion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid number of arguments: function {0}().. - /// - internal static string Expr_FunctionArgumentCount { - get { - return ResourceManager.GetString("Expr_FunctionArgumentCount", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expression contains invalid date constant '{0}'.. - /// - internal static string Expr_InvalidDate { - get { - return ResourceManager.GetString("Expr_InvalidDate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 'hours' argument is out of range. Value must be between -14 and +14.. - /// - internal static string Expr_InvalidHoursArgument { - get { - return ResourceManager.GetString("Expr_InvalidHoursArgument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 'minutes' argument is out of range. Value must be between -59 and +59.. - /// - internal static string Expr_InvalidMinutesArgument { - get { - return ResourceManager.GetString("Expr_InvalidMinutesArgument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid column name [{0}].. - /// - internal static string Expr_InvalidName { - get { - return ResourceManager.GetString("Expr_InvalidName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expression contains invalid name: '{0}'.. - /// - internal static string Expr_InvalidNameBracketing { - get { - return ResourceManager.GetString("Expr_InvalidNameBracketing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error in Like operator: the string pattern '{0}' is invalid.. - /// - internal static string Expr_InvalidPattern { - get { - return ResourceManager.GetString("Expr_InvalidPattern", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expression contains an invalid string constant: {0}.. - /// - internal static string Expr_InvalidString { - get { - return ResourceManager.GetString("Expr_InvalidString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Provided range for time one exceeds total of 14 hours.. - /// - internal static string Expr_InvalidTimeZoneRange { - get { - return ResourceManager.GetString("Expr_InvalidTimeZoneRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid type name '{0}'.. - /// - internal static string Expr_InvalidType { - get { - return ResourceManager.GetString("Expr_InvalidType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Need a row or a table to Invoke DataFilter.. - /// - internal static string Expr_InvokeArgument { - get { - return ResourceManager.GetString("Expr_InvokeArgument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error: The IN keyword must be followed by a non-empty list of expressions separated by commas, and also must be enclosed in parentheses.. - /// - internal static string Expr_InWithoutList { - get { - return ResourceManager.GetString("Expr_InWithoutList", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error: The items following the IN keyword must be separated by commas and be enclosed in parentheses.. - /// - internal static string Expr_InWithoutParentheses { - get { - return ResourceManager.GetString("Expr_InWithoutParentheses", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error: Invalid usage of 'Is' operator. Correct syntax: <expression> Is [Not] Null.. - /// - internal static string Expr_IsSyntax { - get { - return ResourceManager.GetString("Expr_IsSyntax", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error in Lookup expression: Expecting keyword 'Parent' followed by a single column argument with possible relation qualifier: Parent[(<relation_name>)].<column_name>.. - /// - internal static string Expr_LookupArgument { - get { - return ResourceManager.GetString("Expr_LookupArgument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Kind property of provided DateTime argument, does not match 'hours' and 'minutes' arguments.. - /// - internal static string Expr_MismatchKindandTimeSpan { - get { - return ResourceManager.GetString("Expr_MismatchKindandTimeSpan", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error: Missing operand after '{0}' operator.. - /// - internal static string Expr_MissingOperand { - get { - return ResourceManager.GetString("Expr_MissingOperand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error: Missing operand before '{0}' operator.. - /// - internal static string Expr_MissingOperandBefore { - get { - return ResourceManager.GetString("Expr_MissingOperandBefore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expression is missing the closing parenthesis.. - /// - internal static string Expr_MissingRightParen { - get { - return ResourceManager.GetString("Expr_MissingRightParen", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Only constant expressions are allowed in the expression list for the IN operator.. - /// - internal static string Expr_NonConstantArgument { - get { - return ResourceManager.GetString("Expr_NonConstantArgument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The feature not implemented. {0}.. - /// - internal static string Expr_NYI { - get { - return ResourceManager.GetString("Expr_NYI", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value is either too large or too small for Type '{0}'.. - /// - internal static string Expr_Overflow { - get { - return ResourceManager.GetString("Expr_Overflow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error in the expression.. - /// - internal static string Expr_Syntax { - get { - return ResourceManager.GetString("Expr_Syntax", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expression has too many closing parentheses.. - /// - internal static string Expr_TooManyRightParentheses { - get { - return ResourceManager.GetString("Expr_TooManyRightParentheses", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type mismatch in expression '{0}'.. - /// - internal static string Expr_TypeMismatch { - get { - return ResourceManager.GetString("Expr_TypeMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot perform '{0}' operation on {1} and {2}.. - /// - internal static string Expr_TypeMismatchInBinop { - get { - return ResourceManager.GetString("Expr_TypeMismatchInBinop", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find column [{0}].. - /// - internal static string Expr_UnboundName { - get { - return ResourceManager.GetString("Expr_UnboundName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expression contains undefined function call {0}().. - /// - internal static string Expr_UndefinedFunction { - get { - return ResourceManager.GetString("Expr_UndefinedFunction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot interpret token '{0}' at position {1}.. - /// - internal static string Expr_UnknownToken { - get { - return ResourceManager.GetString("Expr_UnknownToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expected {0}, but actual token at the position {2} is {1}.. - /// - internal static string Expr_UnknownToken1 { - get { - return ResourceManager.GetString("Expr_UnknownToken1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The table [{0}] involved in more than one relation. You must explicitly mention a relation name in the expression '{1}'.. - /// - internal static string Expr_UnresolvedRelation { - get { - return ResourceManager.GetString("Expr_UnresolvedRelation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The expression contains unsupported operator '{0}'.. - /// - internal static string Expr_UnsupportedOperator { - get { - return ResourceManager.GetString("Expr_UnsupportedOperator", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A DataColumn of type '{0}' does not support expression.. - /// - internal static string Expr_UnsupportedType { - get { - return ResourceManager.GetString("Expr_UnsupportedType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The collection that holds custom user information.. - /// - internal static string ExtendedPropertiesDescr { - get { - return ResourceManager.GetString("ExtendedPropertiesDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to create enclave session as attestation server is busy.. - /// - internal static string FailToCreateEnclaveSession { - get { - return ResourceManager.GetString("FailToCreateEnclaveSession", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of an attestation information failed. The attestation information has an invalid format. Contact Customer Support Services. Error details: '{0}'.. - /// - internal static string FailToParseAttestationInfo { - get { - return ResourceManager.GetString("FailToParseAttestationInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of an attestation token failed. The token has an invalid format. Contact Customer Support Services. Error details: '{0}'.. - /// - internal static string FailToParseAttestationToken { - get { - return ResourceManager.GetString("FailToParseAttestationToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to For accept and reject changes, indicates what kind of cascading should take place across this relation.. - /// - internal static string ForeignKeyConstraintAcceptRejectRuleDescr { - get { - return ResourceManager.GetString("ForeignKeyConstraintAcceptRejectRuleDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates the child columns of this constraint.. - /// - internal static string ForeignKeyConstraintChildColumnsDescr { - get { - return ResourceManager.GetString("ForeignKeyConstraintChildColumnsDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to For deletions, indicates what kind of cascading should take place across this relation.. - /// - internal static string ForeignKeyConstraintDeleteRuleDescr { - get { - return ResourceManager.GetString("ForeignKeyConstraintDeleteRuleDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates the parent columns of this constraint.. - /// - internal static string ForeignKeyConstraintParentColumnsDescr { - get { - return ResourceManager.GetString("ForeignKeyConstraintParentColumnsDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to For updates, indicates what kind of cascading should take place across this relation.. - /// - internal static string ForeignKeyConstraintUpdateRuleDescr { - get { - return ResourceManager.GetString("ForeignKeyConstraintUpdateRuleDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates the child table of this constraint.. - /// - internal static string ForeignKeyRelatedTableDescr { - get { - return ResourceManager.GetString("ForeignKeyRelatedTableDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The attestation service returned an expired HGS root certificate for attestation URL '{0}'. Check the HGS root certificate configured for your HGS instance - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details.. - /// - internal static string GetAttestationSigningCertificateFailedInvalidCertificate { - get { - return ResourceManager.GetString("GetAttestationSigningCertificateFailedInvalidCertificate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The obtained HGS root certificate for attestation URL '{0}' has an invalid format. Verify the attestation URL is correct and the HGS server is online and fully initialized - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details. For additional support contact Customer Support Services. Error details: '{1}'.. - /// - internal static string GetAttestationSigningCertificateRequestFailedFormat { - get { - return ResourceManager.GetString("GetAttestationSigningCertificateRequestFailedFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of an attestation token failed. Cannot retrieve a public key from the attestation public key endpoint, or the retrieved key has an invalid format. Error details: '{0}'.. - /// - internal static string GetAttestationTokenSigningKeysFailed { - get { - return ResourceManager.GetString("GetAttestationTokenSigningKeysFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Signature verification of the enclave's Diffie-Hellman key failed. Contact Customer Support Services.. - /// - internal static string GetSharedSecretFailed { - get { - return ResourceManager.GetString("GetSharedSecretFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Global Transactions are not enabled for this Azure SQL Database. Please contact Azure SQL Database support for assistance.. - /// - internal static string GT_Disabled { - get { - return ResourceManager.GetString("GT_Disabled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The currently loaded System.Transactions.dll does not support Global Transactions. Please upgrade to .NET Framework 4.6.2 or later.. - /// - internal static string GT_UnsupportedSysTxVersion { - get { - return ResourceManager.GetString("GT_UnsupportedSysTxVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There are no records in the SqlDataRecord enumeration. To send a table-valued parameter with no rows, use a null reference for the value instead.. - /// - internal static string IEnumerableOfSqlDataRecordHasNoRows { - get { - return ResourceManager.GetString("IEnumerableOfSqlDataRecordHasNoRows", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of an attestation token failed due to an error while decoding the enclave public key obtained from SQL Server. Contact Customer Support Services.. - /// - internal static string InvalidArgumentToBase64UrlDecoder { - get { - return ResourceManager.GetString("InvalidArgumentToBase64UrlDecoder", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of an attestation token failed due to an error while computing a hash of the enclave public key obtained from SQL Server. Contact Customer Support Services.. - /// - internal static string InvalidArgumentToSHA256 { - get { - return ResourceManager.GetString("InvalidArgumentToSHA256", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of the attestation token has failed during signature validation. Exception: '{0}'.. - /// - internal static string InvalidAttestationToken { - get { - return ResourceManager.GetString("InvalidAttestationToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of an attestation token failed. Claim '{0}' in the token has an invalid value of '{1}'. Verify the attestation policy - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details. If the policy is correct, contact Customer Support Services.. - /// - internal static string InvalidClaimInAttestationToken { - get { - return ResourceManager.GetString("InvalidClaimInAttestationToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid column ordinals in schema table. ColumnOrdinals, if present, must not have duplicates or gaps.. - /// - internal static string InvalidSchemaTableOrdinals { - get { - return ResourceManager.GetString("InvalidSchemaTableOrdinals", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates the columns of this constraint.. - /// - internal static string KeyConstraintColumnsDescr { - get { - return ResourceManager.GetString("KeyConstraintColumnsDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indicates if this constraint is a primary key.. - /// - internal static string KeyConstraintIsPrimaryKeyDescr { - get { - return ResourceManager.GetString("KeyConstraintIsPrimaryKeyDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ReadOnly Data is Modified.. - /// - internal static string Load_ReadOnlyDataModified { - get { - return ResourceManager.GetString("Load_ReadOnlyDataModified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Local Database Runtime: system.data.localdb configuration file section is of unknown type.. - /// - internal static string LocalDB_BadConfigSectionType { - get { - return ResourceManager.GetString("LocalDB_BadConfigSectionType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Local Database Runtime: Cannot create named instance.. - /// - internal static string LocalDB_CreateFailed { - get { - return ResourceManager.GetString("LocalDB_CreateFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Local Database Runtime: Cannot load SQLUserInstance.dll.. - /// - internal static string LocalDB_FailedGetDLLHandle { - get { - return ResourceManager.GetString("LocalDB_FailedGetDLLHandle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Local Database Runtime: Invalid instance version specification found in the configuration file.. - /// - internal static string LocalDB_InvalidVersion { - get { - return ResourceManager.GetString("LocalDB_InvalidVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid SQLUserInstance.dll found at the location specified in the registry. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. - /// - internal static string LocalDB_MethodNotFound { - get { - return ResourceManager.GetString("LocalDB_MethodNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot obtain Local Database Runtime error message. - /// - internal static string LocalDB_UnobtainableMessage { - get { - return ResourceManager.GetString("LocalDB_UnobtainableMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to LocalDB is not supported on this platform.. - /// - internal static string LocalDBNotSupported { - get { - return ResourceManager.GetString("LocalDBNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The collection name '{0}' matches at least two collections with the same name but with different case, but does not match any of them exactly.. - /// - internal static string MDF_AmbiguousCollectionName { - get { - return ResourceManager.GetString("MDF_AmbiguousCollectionName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There are multiple collections named '{0}'.. - /// - internal static string MDF_CollectionNameISNotUnique { - get { - return ResourceManager.GetString("MDF_CollectionNameISNotUnique", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The collection '{0}' is missing from the metadata XML.. - /// - internal static string MDF_DataTableDoesNotExist { - get { - return ResourceManager.GetString("MDF_DataTableDoesNotExist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The DataSourceInformation table must contain exactly one row.. - /// - internal static string MDF_IncorrectNumberOfDataSourceInformationRows { - get { - return ResourceManager.GetString("MDF_IncorrectNumberOfDataSourceInformationRows", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{2}' is not a valid value for the '{1}' restriction of the '{0}' schema collection.. - /// - internal static string MDF_InvalidRestrictionValue { - get { - return ResourceManager.GetString("MDF_InvalidRestrictionValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The metadata XML is invalid.. - /// - internal static string MDF_InvalidXml { - get { - return ResourceManager.GetString("MDF_InvalidXml", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The metadata XML is invalid. The {1} column of the {0} collection must contain a non-empty string.. - /// - internal static string MDF_InvalidXmlInvalidValue { - get { - return ResourceManager.GetString("MDF_InvalidXmlInvalidValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The metadata XML is invalid. The {0} collection must contain a {1} column and it must be a string column.. - /// - internal static string MDF_InvalidXmlMissingColumn { - get { - return ResourceManager.GetString("MDF_InvalidXmlMissingColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to One of the required DataSourceInformation tables columns is missing.. - /// - internal static string MDF_MissingDataSourceInformationColumn { - get { - return ResourceManager.GetString("MDF_MissingDataSourceInformationColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to One or more of the required columns of the restrictions collection is missing.. - /// - internal static string MDF_MissingRestrictionColumn { - get { - return ResourceManager.GetString("MDF_MissingRestrictionColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A restriction exists for which there is no matching row in the restrictions collection.. - /// - internal static string MDF_MissingRestrictionRow { - get { - return ResourceManager.GetString("MDF_MissingRestrictionRow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The schema table contains no columns.. - /// - internal static string MDF_NoColumns { - get { - return ResourceManager.GetString("MDF_NoColumns", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to build the '{0}' collection because execution of the SQL query failed. See the inner exception for details.. - /// - internal static string MDF_QueryFailed { - get { - return ResourceManager.GetString("MDF_QueryFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to More restrictions were provided than the requested schema ('{0}') supports.. - /// - internal static string MDF_TooManyRestrictions { - get { - return ResourceManager.GetString("MDF_TooManyRestrictions", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to build schema collection '{0}';. - /// - internal static string MDF_UnableToBuildCollection { - get { - return ResourceManager.GetString("MDF_UnableToBuildCollection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The requested collection ({0}) is not defined.. - /// - internal static string MDF_UndefinedCollection { - get { - return ResourceManager.GetString("MDF_UndefinedCollection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The population mechanism '{0}' is not defined.. - /// - internal static string MDF_UndefinedPopulationMechanism { - get { - return ResourceManager.GetString("MDF_UndefinedPopulationMechanism", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The requested collection ({0}) is not supported by this version of the provider.. - /// - internal static string MDF_UnsupportedVersion { - get { - return ResourceManager.GetString("MDF_UnsupportedVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlDbType.Structured type is only supported for multiple valued types.. - /// - internal static string MetaType_SingleValuedStructNotSupported { - get { - return ResourceManager.GetString("MetaType_SingleValuedStructNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The validation of the attestation token failed. Claim '{0}' is missing in the token. Verify the attestation policy - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details. If the policy is correct, contact Customer Support Services.. - /// - internal static string MissingClaimInAttestationToken { - get { - return ResourceManager.GetString("MissingClaimInAttestationToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Simple type '{0}' has already be declared with different '{1}'.. - /// - internal static string NamedSimpleType_InvalidDuplicateNamedSimpleTypeDeclaration { - get { - return ResourceManager.GetString("NamedSimpleType_InvalidDuplicateNamedSimpleTypeDeclaration", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Protocol error: A received message contains a valid signature but it was not encrypted as required by the effective Protection Level.. - /// - internal static string net_auth_message_not_encrypted { - get { - return ResourceManager.GetString("net_auth_message_not_encrypted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Insufficient buffer space. Required: {0} Actual: {1}.. - /// - internal static string net_context_buffer_too_small { - get { - return ResourceManager.GetString("net_context_buffer_too_small", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to GSSAPI operation failed with status: {0} (Minor status: {1}).. - /// - internal static string net_gssapi_operation_failed { - get { - return ResourceManager.GetString("net_gssapi_operation_failed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to GSSAPI operation failed with error - {0} ({1}).. - /// - internal static string net_gssapi_operation_failed_detailed { - get { - return ResourceManager.GetString("net_gssapi_operation_failed_detailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified value is not valid in the '{0}' enumeration.. - /// - internal static string net_invalid_enum { - get { - return ResourceManager.GetString("net_invalid_enum", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} failed with error {1}.. - /// - internal static string net_log_operation_failed_with_error { - get { - return ResourceManager.GetString("net_log_operation_failed_with_error", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This method is not implemented by this class.. - /// - internal static string net_MethodNotImplementedException { - get { - return ResourceManager.GetString("net_MethodNotImplementedException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No support for channel binding on operating systems other than Windows.. - /// - internal static string net_nego_channel_binding_not_supported { - get { - return ResourceManager.GetString("net_nego_channel_binding_not_supported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Target name should be non empty if default credentials are passed.. - /// - internal static string net_nego_not_supported_empty_target_with_defaultcreds { - get { - return ResourceManager.GetString("net_nego_not_supported_empty_target_with_defaultcreds", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Requested protection level is not supported with the GSSAPI implementation currently installed.. - /// - internal static string net_nego_protection_level_not_supported { - get { - return ResourceManager.GetString("net_nego_protection_level_not_supported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Server implementation is not supported.. - /// - internal static string net_nego_server_not_supported { - get { - return ResourceManager.GetString("net_nego_server_not_supported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to NTLM authentication is not possible with default credentials on this platform.. - /// - internal static string net_ntlm_not_possible_default_cred { - get { - return ResourceManager.GetString("net_ntlm_not_possible_default_cred", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The requested security package is not supported.. - /// - internal static string net_securitypackagesupport { - get { - return ResourceManager.GetString("net_securitypackagesupport", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DateType column for field '{0}' in schema table is null. DataType must be non-null.. - /// - internal static string NullSchemaTableDataTypeNotSupported { - get { - return ResourceManager.GetString("NullSchemaTableDataTypeNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} - unable to allocate an environment handle.. - /// - internal static string Odbc_CantAllocateEnvironmentHandle { - get { - return ResourceManager.GetString("Odbc_CantAllocateEnvironmentHandle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} - unable to enable connection pooling.... - /// - internal static string Odbc_CantEnableConnectionpooling { - get { - return ResourceManager.GetString("Odbc_CantEnableConnectionpooling", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Can't set property on an open connection.. - /// - internal static string Odbc_CantSetPropertyOnOpenConnection { - get { - return ResourceManager.GetString("Odbc_CantSetPropertyOnOpenConnection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The connection is closed.. - /// - internal static string Odbc_ConnectionClosed { - get { - return ResourceManager.GetString("Odbc_ConnectionClosed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} [{1}] {2}. - /// - internal static string Odbc_ExceptionMessage { - get { - return ResourceManager.GetString("Odbc_ExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} - no error information available. - /// - internal static string Odbc_ExceptionNoInfoMsg { - get { - return ResourceManager.GetString("Odbc_ExceptionNoInfoMsg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} - unable to get descriptor handle.. - /// - internal static string Odbc_FailedToGetDescriptorHandle { - get { - return ResourceManager.GetString("Odbc_FailedToGetDescriptorHandle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to "The ODBC managed provider requires that the TABLE_NAME restriction be specified and non-null for the GetSchema indexes collection.. - /// - internal static string ODBC_GetSchemaRestrictionRequired { - get { - return ResourceManager.GetString("ODBC_GetSchemaRestrictionRequired", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} - unable to map type.. - /// - internal static string Odbc_GetTypeMapping_UnknownType { - get { - return ResourceManager.GetString("Odbc_GetTypeMapping_UnknownType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The .NET Framework Odbc Data Provider requires Microsoft Data Access Components(MDAC) version 2.6 or later. Version {0} was found currently installed.. - /// - internal static string Odbc_MDACWrongVersion { - get { - return ResourceManager.GetString("Odbc_MDACWrongVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid negative argument!. - /// - internal static string Odbc_NegativeArgument { - get { - return ResourceManager.GetString("Odbc_NegativeArgument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No valid mapping for a SQL_TRANSACTION '{0}' to a System.Data.IsolationLevel enumeration value.. - /// - internal static string Odbc_NoMappingForSqlTransactionLevel { - get { - return ResourceManager.GetString("Odbc_NoMappingForSqlTransactionLevel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Not in a transaction. - /// - internal static string Odbc_NotInTransaction { - get { - return ResourceManager.GetString("Odbc_NotInTransaction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} enumeration value, {1}, is not supported by the .NET Framework Odbc Data Provider.. - /// - internal static string ODBC_NotSupportedEnumerationValue { - get { - return ResourceManager.GetString("ODBC_NotSupportedEnumerationValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use IsDBNull when DBNull.Value data is expected.. - /// - internal static string Odbc_NullData { - get { - return ResourceManager.GetString("Odbc_NullData", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OdbcCommandBuilder.DeriveParameters failed because the OdbcCommand.CommandText property value is an invalid multipart name. - /// - internal static string ODBC_ODBCCommandText { - get { - return ResourceManager.GetString("ODBC_ODBCCommandText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An internal connection does not have an owner.. - /// - internal static string Odbc_OpenConnectionNoOwner { - get { - return ResourceManager.GetString("Odbc_OpenConnectionNoOwner", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid OdbcType enumeration value={0}.. - /// - internal static string Odbc_UnknownOdbcType { - get { - return ResourceManager.GetString("Odbc_UnknownOdbcType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unknown SQL type - {0}.. - /// - internal static string Odbc_UnknownSQLType { - get { - return ResourceManager.GetString("Odbc_UnknownSQLType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unknown URT type - {0}.. - /// - internal static string Odbc_UnknownURTType { - get { - return ResourceManager.GetString("Odbc_UnknownURTType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The DataAdapter for which to automatically generate OdbcCommands. - /// - internal static string OdbcCommandBuilder_DataAdapter { - get { - return ResourceManager.GetString("OdbcCommandBuilder_DataAdapter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The character used in a text command as the opening quote for quoting identifiers that contain special characters.. - /// - internal static string OdbcCommandBuilder_QuotePrefix { - get { - return ResourceManager.GetString("OdbcCommandBuilder_QuotePrefix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The character used in a text command as the closing quote for quoting identifiers that contain special characters.. - /// - internal static string OdbcCommandBuilder_QuoteSuffix { - get { - return ResourceManager.GetString("OdbcCommandBuilder_QuoteSuffix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Information used to connect to a Data Source.. - /// - internal static string OdbcConnection_ConnectionString { - get { - return ResourceManager.GetString("OdbcConnection_ConnectionString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection string exceeds maximum allowed length of {0}.. - /// - internal static string OdbcConnection_ConnectionStringTooLong { - get { - return ResourceManager.GetString("OdbcConnection_ConnectionStringTooLong", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current connection timeout value, not settable in the ConnectionString.. - /// - internal static string OdbcConnection_ConnectionTimeout { - get { - return ResourceManager.GetString("OdbcConnection_ConnectionTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current data source catalog value, 'Database=X' in the connection string.. - /// - internal static string OdbcConnection_Database { - get { - return ResourceManager.GetString("OdbcConnection_Database", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current data source, 'Server=X' in the connection string.. - /// - internal static string OdbcConnection_DataSource { - get { - return ResourceManager.GetString("OdbcConnection_DataSource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current ODBC driver.. - /// - internal static string OdbcConnection_Driver { - get { - return ResourceManager.GetString("OdbcConnection_Driver", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Version of the product accessed by the ODBC Driver.. - /// - internal static string OdbcConnection_ServerVersion { - get { - return ResourceManager.GetString("OdbcConnection_ServerVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter native type.. - /// - internal static string OdbcParameter_OdbcType { - get { - return ResourceManager.GetString("OdbcParameter_OdbcType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 'Asynchronous Processing' is not a supported feature of the .NET Framework Data OLE DB Provider(Microsoft.Data.OleDb).. - /// - internal static string OleDb_AsynchronousNotSupported { - get { - return ResourceManager.GetString("OleDb_AsynchronousNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Accessor validation was deferred and was performed while the method returned data. The binding was invalid for this column or parameter.. - /// - internal static string OleDb_BadAccessor { - get { - return ResourceManager.GetString("OleDb_BadAccessor", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Microsoft.Data.OleDb.OleDbDataAdapter internal error: invalid parameter accessor: {0} {1}.. - /// - internal static string OleDb_BadStatus_ParamAcc { - get { - return ResourceManager.GetString("OleDb_BadStatus_ParamAcc", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OleDbDataAdapter internal error: invalid row set accessor: Ordinal={0} Status={1}.. - /// - internal static string OleDb_BadStatusRowAccessor { - get { - return ResourceManager.GetString("OleDb_BadStatusRowAccessor", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Can not determine the server's decimal separator. Non-integer numeric literals can not be created.. - /// - internal static string OleDb_CanNotDetermineDecimalSeparator { - get { - return ResourceManager.GetString("OleDb_CanNotDetermineDecimalSeparator", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The data value could not be converted for reasons other than sign mismatch or data overflow. For example, the data was corrupted in the data store but the row was still retrievable.. - /// - internal static string OleDb_CantConvertValue { - get { - return ResourceManager.GetString("OleDb_CantConvertValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The provider could not allocate memory in which to return {0} data.. - /// - internal static string OleDb_CantCreate { - get { - return ResourceManager.GetString("OleDb_CantCreate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command parameter[{0}] '{1}' is invalid.. - /// - internal static string OleDb_CommandParameterBadAccessor { - get { - return ResourceManager.GetString("OleDb_CommandParameterBadAccessor", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command parameter[{0}] '{1}' data value could not be converted for reasons other than sign mismatch or data overflow.. - /// - internal static string OleDb_CommandParameterCantConvertValue { - get { - return ResourceManager.GetString("OleDb_CommandParameterCantConvertValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Conversion failed for command parameter[{0}] '{1}' because the data value overflowed the type used by the provider.. - /// - internal static string OleDb_CommandParameterDataOverflow { - get { - return ResourceManager.GetString("OleDb_CommandParameterDataOverflow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter[{0}] '{1}' has no default value.. - /// - internal static string OleDb_CommandParameterDefault { - get { - return ResourceManager.GetString("OleDb_CommandParameterDefault", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error occurred with parameter[{0}]: {1}.. - /// - internal static string OleDb_CommandParameterError { - get { - return ResourceManager.GetString("OleDb_CommandParameterError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Conversion failed for command parameter[{0}] '{1}' because the data value was signed and the type used by the provider was unsigned.. - /// - internal static string OleDb_CommandParameterSignMismatch { - get { - return ResourceManager.GetString("OleDb_CommandParameterSignMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Provider encountered an error while sending command parameter[{0}] '{1}' value and stopped processing.. - /// - internal static string OleDb_CommandParameterUnavailable { - get { - return ResourceManager.GetString("OleDb_CommandParameterUnavailable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The ICommandText interface is not supported by the '{0}' provider. Use CommandType.TableDirect instead.. - /// - internal static string OleDb_CommandTextNotSupported { - get { - return ResourceManager.GetString("OleDb_CommandTextNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to load the XML file specified in configuration setting '{0}'.. - /// - internal static string OleDb_ConfigUnableToLoadXmlMetaDataFile { - get { - return ResourceManager.GetString("OleDb_ConfigUnableToLoadXmlMetaDataFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The '{0}' configuration setting has the wrong number of values.. - /// - internal static string OleDb_ConfigWrongNumberOfValues { - get { - return ResourceManager.GetString("OleDb_ConfigWrongNumberOfValues", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Format of the initialization string does not conform to the OLE DB specification. Starting around char[{0}] in the connection string.. - /// - internal static string OleDb_ConnectionStringSyntax { - get { - return ResourceManager.GetString("OleDb_ConnectionStringSyntax", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Conversion failed because the {0} data value overflowed the type specified for the {0} value part in the consumer's buffer.. - /// - internal static string OleDb_DataOverflow { - get { - return ResourceManager.GetString("OleDb_DataOverflow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DBTYPE_VECTOR data is not supported by the .NET Framework Data OLE DB Provider(Microsoft.Data.OleDb).. - /// - internal static string OleDb_DBBindingGetVector { - get { - return ResourceManager.GetString("OleDb_DBBindingGetVector", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to IErrorInfo.GetDescription failed with {0}.. - /// - internal static string OleDb_FailedGetDescription { - get { - return ResourceManager.GetString("OleDb_FailedGetDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to IErrorInfo.GetSource failed with {0}.. - /// - internal static string OleDb_FailedGetSource { - get { - return ResourceManager.GetString("OleDb_FailedGetSource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to retrieve the IRow interface from the ADODB.Record object.. - /// - internal static string OleDb_Fill_EmptyRecord { - get { - return ResourceManager.GetString("OleDb_Fill_EmptyRecord", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to retrieve the '{0}' interface from the ADODB.RecordSet object.. - /// - internal static string OleDb_Fill_EmptyRecordSet { - get { - return ResourceManager.GetString("OleDb_Fill_EmptyRecordSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Object is not an ADODB.RecordSet or an ADODB.Record.. - /// - internal static string OleDb_Fill_NotADODB { - get { - return ResourceManager.GetString("OleDb_Fill_NotADODB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OleDbDataAdapter internal error: [get] Unknown OLE DB data type: 0x{0} ({1}).. - /// - internal static string OleDb_GVtUnknown { - get { - return ResourceManager.GetString("OleDb_GVtUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot construct the ReservedWords schema collection because the provider does not support IDBInfo.. - /// - internal static string OleDb_IDBInfoNotSupported { - get { - return ResourceManager.GetString("OleDb_IDBInfoNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The OLE DB Provider specified in the ConnectionString is too long.. - /// - internal static string OleDb_InvalidProviderSpecified { - get { - return ResourceManager.GetString("OleDb_InvalidProviderSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No restrictions are expected for the DbInfoKeywords OleDbSchemaGuid.. - /// - internal static string OleDb_InvalidRestrictionsDbInfoKeywords { - get { - return ResourceManager.GetString("OleDb_InvalidRestrictionsDbInfoKeywords", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No restrictions are expected for the DbInfoLiterals OleDbSchemaGuid.. - /// - internal static string OleDb_InvalidRestrictionsDbInfoLiteral { - get { - return ResourceManager.GetString("OleDb_InvalidRestrictionsDbInfoLiteral", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No restrictions are expected for the schema guid OleDbSchemaGuid.. - /// - internal static string OleDb_InvalidRestrictionsSchemaGuids { - get { - return ResourceManager.GetString("OleDb_InvalidRestrictionsSchemaGuids", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type does not support the OLE DB interface ISourcesRowset. - /// - internal static string OleDb_ISourcesRowsetNotSupported { - get { - return ResourceManager.GetString("OleDb_ISourcesRowsetNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The .NET Framework Data Providers require Microsoft Data Access Components(MDAC). Please install Microsoft Data Access Components(MDAC) version 2.6 or later.. - /// - internal static string OleDb_MDACNotAvailable { - get { - return ResourceManager.GetString("OleDb_MDACNotAvailable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The .NET Framework OleDb Data Provider requires Microsoft Data Access Components(MDAC) version 2.6 or later. Version {0} was found currently installed.. - /// - internal static string OleDb_MDACWrongVersion { - get { - return ResourceManager.GetString("OleDb_MDACWrongVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The .NET Framework Data Provider for OLEDB (Microsoft.Data.OleDb) does not support the Microsoft OLE DB Provider for ODBC Drivers (MSDASQL). Use the .NET Framework Data Provider for ODBC (System.Data.Odbc).. - /// - internal static string OleDb_MSDASQLNotSupported { - get { - return ResourceManager.GetString("OleDb_MSDASQLNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No error message available, result code: {0}.. - /// - internal static string OleDb_NoErrorInformation { - get { - return ResourceManager.GetString("OleDb_NoErrorInformation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' failed with no error message available, result code: {1}.. - /// - internal static string OleDb_NoErrorInformation2 { - get { - return ResourceManager.GetString("OleDb_NoErrorInformation2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unspecified error: {0}. - /// - internal static string OleDb_NoErrorMessage { - get { - return ResourceManager.GetString("OleDb_NoErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An OLE DB Provider was not specified in the ConnectionString. An example would be, 'Provider=SQLOLEDB;'.. - /// - internal static string OleDb_NoProviderSpecified { - get { - return ResourceManager.GetString("OleDb_NoProviderSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The ICommandWithParameters interface is not supported by the '{0}' provider. Command parameters are unsupported with the current provider.. - /// - internal static string OleDb_NoProviderSupportForParameters { - get { - return ResourceManager.GetString("OleDb_NoProviderSupportForParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Retrieving procedure parameter information is not supported by the '{0}' provider.. - /// - internal static string OleDb_NoProviderSupportForSProcResetParameters { - get { - return ResourceManager.GetString("OleDb_NoProviderSupportForSProcResetParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} enumeration value, {1}, is not supported by the .NET Framework OleDb Data Provider.. - /// - internal static string OLEDB_NotSupportedEnumerationValue { - get { - return ResourceManager.GetString("OLEDB_NotSupportedEnumerationValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} OleDbSchemaGuid is not a supported schema by the '{1}' provider.. - /// - internal static string OleDb_NotSupportedSchemaTable { - get { - return ResourceManager.GetString("OleDb_NotSupportedSchemaTable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OleDbCommandBuilder.DeriveParameters failed because the OleDbCommandBuilder.CommandText property value is an invalid multipart name. - /// - internal static string OLEDB_OLEDBCommandText { - get { - return ResourceManager.GetString("OLEDB_OLEDBCommandText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The .NET Framework Data Provider for OLEDB will not allow the OLE DB Provider to prompt the user in a non-interactive environment.. - /// - internal static string OleDb_PossiblePromptNotUserInteractive { - get { - return ResourceManager.GetString("OleDb_PossiblePromptNotUserInteractive", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The ColumnID element was invalid.. - /// - internal static string OleDb_PropertyBadColumn { - get { - return ResourceManager.GetString("OleDb_PropertyBadColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The value of Options was invalid.. - /// - internal static string OleDb_PropertyBadOption { - get { - return ResourceManager.GetString("OleDb_PropertyBadOption", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to initialize the '{0}' property for one of the following reasons: - /// The value data type was not the data type of the property or was not null. For example, the property was DBPROP_MEMORYUSAGE, which has a data type of Int32, and the data type was Int64. - /// The value was not a valid value. For example, the property was DBPROP_MEMORYUSAGE and the value was negative. - /// The value was a valid value for the property and the provider supports the property as a settable property, but the provider does not su [rest of string was truncated]";. - /// - internal static string OleDb_PropertyBadValue { - get { - return ResourceManager.GetString("OleDb_PropertyBadValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The '{0}'property's value was not set because doing so would have conflicted with an existing property.. - /// - internal static string OleDb_PropertyConflicting { - get { - return ResourceManager.GetString("OleDb_PropertyConflicting", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A '{0}' property was specified to be applied to all columns but could not be applied to one or more of them.. - /// - internal static string OleDb_PropertyNotAllSettable { - get { - return ResourceManager.GetString("OleDb_PropertyNotAllSettable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to (Reserved).. - /// - internal static string OleDb_PropertyNotAvailable { - get { - return ResourceManager.GetString("OleDb_PropertyNotAvailable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The optional '{0}' property's value was not set to the specified value and setting the property to the specified value was not possible.. - /// - internal static string OleDb_PropertyNotSet { - get { - return ResourceManager.GetString("OleDb_PropertyNotSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The '{0}' property was read-only, or the consumer attempted to set values of properties in the Initialization property group after the data source object was initialized. Consumers can set the value of a read-only property to its current value. This status is also returned if a settable column property could not be set for the particular column.. - /// - internal static string OleDb_PropertyNotSettable { - get { - return ResourceManager.GetString("OleDb_PropertyNotSettable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The property's value was not set because the provider did not support the '{0}' property, or the consumer attempted to get or set values of properties not in the Initialization property group and the data source object is uninitialized.. - /// - internal static string OleDb_PropertyNotSupported { - get { - return ResourceManager.GetString("OleDb_PropertyNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The provider returned an unknown DBPROPSTATUS_ value '{0}'.. - /// - internal static string OleDb_PropertyStatusUnknown { - get { - return ResourceManager.GetString("OleDb_PropertyStatusUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The '{0}' provider is not registered on the local machine.. - /// - internal static string OleDb_ProviderUnavailable { - get { - return ResourceManager.GetString("OleDb_ProviderUnavailable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' interface is not supported by the '{1}' provider. GetOleDbSchemaTable is unavailable with the current provider.. - /// - internal static string OleDb_SchemaRowsetsNotSupported { - get { - return ResourceManager.GetString("OleDb_SchemaRowsetsNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Conversion failed because the {0} data value was signed and the type specified for the {0} value part in the consumer's buffer was unsigned.. - /// - internal static string OleDb_SignMismatch { - get { - return ResourceManager.GetString("OleDb_SignMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OleDbDataAdapter internal error: [set] Unknown OLE DB data type: 0x{0} ({1}).. - /// - internal static string OleDb_SVtUnknown { - get { - return ResourceManager.GetString("OleDb_SVtUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The OleDbDataReader.Read must be used from the same thread on which is was created if that thread's ApartmentState was not ApartmentState.MTA.. - /// - internal static string OleDb_ThreadApartmentState { - get { - return ResourceManager.GetString("OleDb_ThreadApartmentState", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The ITransactionLocal interface is not supported by the '{0}' provider. Local transactions are unavailable with the current provider.. - /// - internal static string OleDb_TransactionsNotSupported { - get { - return ResourceManager.GetString("OleDb_TransactionsNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The provider could not determine the {0} value. For example, the row was just created, the default for the {0} column was not available, and the consumer had not yet set a new {0} value.. - /// - internal static string OleDb_Unavailable { - get { - return ResourceManager.GetString("OleDb_Unavailable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OLE DB Provider returned an unexpected status value of {0}.. - /// - internal static string OleDb_UnexpectedStatusValue { - get { - return ResourceManager.GetString("OleDb_UnexpectedStatusValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter[{0}]: the OleDbType property is uninitialized: OleDbType.{1}.. - /// - internal static string OleDb_UninitializedParameters { - get { - return ResourceManager.GetString("OleDb_UninitializedParameters", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The DataAdapter for which to automatically generate OleDbCommands. - /// - internal static string OleDbCommandBuilder_DataAdapter { - get { - return ResourceManager.GetString("OleDbCommandBuilder_DataAdapter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The decimal separator used in numeric literals.. - /// - internal static string OleDbCommandBuilder_DecimalSeparator { - get { - return ResourceManager.GetString("OleDbCommandBuilder_DecimalSeparator", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The prefix string wrapped around sql objects. - /// - internal static string OleDbCommandBuilder_QuotePrefix { - get { - return ResourceManager.GetString("OleDbCommandBuilder_QuotePrefix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The suffix string wrapped around sql objects. - /// - internal static string OleDbCommandBuilder_QuoteSuffix { - get { - return ResourceManager.GetString("OleDbCommandBuilder_QuoteSuffix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Information used to connect to a Data Source.. - /// - internal static string OleDbConnection_ConnectionString { - get { - return ResourceManager.GetString("OleDbConnection_ConnectionString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current connection timeout value, 'Connect Timeout=X' in the ConnectionString.. - /// - internal static string OleDbConnection_ConnectionTimeout { - get { - return ResourceManager.GetString("OleDbConnection_ConnectionTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current data source catalog value, 'Initial Catalog=X' in the connection string.. - /// - internal static string OleDbConnection_Database { - get { - return ResourceManager.GetString("OleDbConnection_Database", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current data source, 'Data Source=X' in the connection string.. - /// - internal static string OleDbConnection_DataSource { - get { - return ResourceManager.GetString("OleDbConnection_DataSource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current OLE DB provider ProgID, 'Provider=X' in the connection string.. - /// - internal static string OleDbConnection_Provider { - get { - return ResourceManager.GetString("OleDbConnection_Provider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Version of the product accessed by the OLE DB Provider.. - /// - internal static string OleDbConnection_ServerVersion { - get { - return ResourceManager.GetString("OleDbConnection_ServerVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter native type.. - /// - internal static string OleDbParameter_OleDbType { - get { - return ResourceManager.GetString("OleDbParameter_OleDbType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Occurs whenever a property for this control changes.. - /// - internal static string propertyChangedEventDescr { - get { - return ResourceManager.GetString("propertyChangedEventDescr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Min ({0}) must be less than or equal to max ({1}) in a Range object.. - /// - internal static string Range_Argument { - get { - return ResourceManager.GetString("Range_Argument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This is a null range.. - /// - internal static string Range_NullRange { - get { - return ResourceManager.GetString("Range_NullRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Collection was modified; enumeration operation might not execute.. - /// - internal static string RbTree_EnumerationBroken { - get { - return ResourceManager.GetString("RbTree_EnumerationBroken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DataTable internal index is corrupted: '{0}'.. - /// - internal static string RbTree_InvalidState { - get { - return ResourceManager.GetString("RbTree_InvalidState", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MinimumCapacity must be non-negative.. - /// - internal static string RecordManager_MinimumCapacity { - get { - return ResourceManager.GetString("RecordManager_MinimumCapacity", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later.. - /// - internal static string SEC_ProtocolWarning { - get { - return ResourceManager.GetString("SEC_ProtocolWarning", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to I/O Error detected in read/write operation. - /// - internal static string SNI_ERROR_1 { - get { - return ResourceManager.GetString("SNI_ERROR_1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Socket is null. - /// - internal static string SNI_ERROR_10 { - get { - return ResourceManager.GetString("SNI_ERROR_10", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Timeout error. - /// - internal static string SNI_ERROR_11 { - get { - return ResourceManager.GetString("SNI_ERROR_11", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No server name supplied. - /// - internal static string SNI_ERROR_12 { - get { - return ResourceManager.GetString("SNI_ERROR_12", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TerminateListener() has been called. - /// - internal static string SNI_ERROR_13 { - get { - return ResourceManager.GetString("SNI_ERROR_13", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Win9x not supported. - /// - internal static string SNI_ERROR_14 { - get { - return ResourceManager.GetString("SNI_ERROR_14", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function not supported. - /// - internal static string SNI_ERROR_15 { - get { - return ResourceManager.GetString("SNI_ERROR_15", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shared-Memory heap error. - /// - internal static string SNI_ERROR_16 { - get { - return ResourceManager.GetString("SNI_ERROR_16", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find an ip/ipv6 type address to connect. - /// - internal static string SNI_ERROR_17 { - get { - return ResourceManager.GetString("SNI_ERROR_17", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection has been closed by peer. - /// - internal static string SNI_ERROR_18 { - get { - return ResourceManager.GetString("SNI_ERROR_18", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Physical connection is not usable. - /// - internal static string SNI_ERROR_19 { - get { - return ResourceManager.GetString("SNI_ERROR_19", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection was terminated. - /// - internal static string SNI_ERROR_2 { - get { - return ResourceManager.GetString("SNI_ERROR_2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection has been closed. - /// - internal static string SNI_ERROR_20 { - get { - return ResourceManager.GetString("SNI_ERROR_20", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Encryption is enforced but there is no valid certificate. - /// - internal static string SNI_ERROR_21 { - get { - return ResourceManager.GetString("SNI_ERROR_21", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Couldn't load library. - /// - internal static string SNI_ERROR_22 { - get { - return ResourceManager.GetString("SNI_ERROR_22", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot open a new thread in server process. - /// - internal static string SNI_ERROR_23 { - get { - return ResourceManager.GetString("SNI_ERROR_23", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot post event to completion port. - /// - internal static string SNI_ERROR_24 { - get { - return ResourceManager.GetString("SNI_ERROR_24", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection string is not valid. - /// - internal static string SNI_ERROR_25 { - get { - return ResourceManager.GetString("SNI_ERROR_25", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error Locating Server/Instance Specified. - /// - internal static string SNI_ERROR_26 { - get { - return ResourceManager.GetString("SNI_ERROR_26", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error getting enabled protocols list from registry. - /// - internal static string SNI_ERROR_27 { - get { - return ResourceManager.GetString("SNI_ERROR_27", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Server doesn't support requested protocol. - /// - internal static string SNI_ERROR_28 { - get { - return ResourceManager.GetString("SNI_ERROR_28", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shared Memory is not supported for clustered server connectivity. - /// - internal static string SNI_ERROR_29 { - get { - return ResourceManager.GetString("SNI_ERROR_29", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Asynchronous operations not supported. - /// - internal static string SNI_ERROR_3 { - get { - return ResourceManager.GetString("SNI_ERROR_3", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt bind to shared memory segment. - /// - internal static string SNI_ERROR_30 { - get { - return ResourceManager.GetString("SNI_ERROR_30", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Encryption(ssl/tls) handshake failed. - /// - internal static string SNI_ERROR_31 { - get { - return ResourceManager.GetString("SNI_ERROR_31", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Packet size too large for SSL Encrypt/Decrypt operations. - /// - internal static string SNI_ERROR_32 { - get { - return ResourceManager.GetString("SNI_ERROR_32", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SSRP error. - /// - internal static string SNI_ERROR_33 { - get { - return ResourceManager.GetString("SNI_ERROR_33", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not connect to the Shared Memory pipe. - /// - internal static string SNI_ERROR_34 { - get { - return ResourceManager.GetString("SNI_ERROR_34", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An internal exception was caught. - /// - internal static string SNI_ERROR_35 { - get { - return ResourceManager.GetString("SNI_ERROR_35", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Shared Memory dll used to connect to SQL Server 2000 was not found. - /// - internal static string SNI_ERROR_36 { - get { - return ResourceManager.GetString("SNI_ERROR_36", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The SQL Server 2000 Shared Memory client dll appears to be invalid/corrupted. - /// - internal static string SNI_ERROR_37 { - get { - return ResourceManager.GetString("SNI_ERROR_37", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot open a Shared Memory connection to SQL Server 2000. - /// - internal static string SNI_ERROR_38 { - get { - return ResourceManager.GetString("SNI_ERROR_38", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shared memory connectivity to SQL Server 2000 is either disabled or not available on this machine. - /// - internal static string SNI_ERROR_39 { - get { - return ResourceManager.GetString("SNI_ERROR_39", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to . - /// - internal static string SNI_ERROR_4 { - get { - return ResourceManager.GetString("SNI_ERROR_4", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not open a connection to SQL Server. - /// - internal static string SNI_ERROR_40 { - get { - return ResourceManager.GetString("SNI_ERROR_40", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot open a Shared Memory connection to a remote SQL server. - /// - internal static string SNI_ERROR_41 { - get { - return ResourceManager.GetString("SNI_ERROR_41", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not establish dedicated administrator connection (DAC) on default port. Make sure that DAC is enabled. - /// - internal static string SNI_ERROR_42 { - get { - return ResourceManager.GetString("SNI_ERROR_42", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred while obtaining the dedicated administrator connection (DAC) port. Make sure that SQL Browser is running, or check the error log for the port number. - /// - internal static string SNI_ERROR_43 { - get { - return ResourceManager.GetString("SNI_ERROR_43", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not compose Service Principal Name (SPN) for Windows Integrated Authentication. Possible causes are server(s) incorrectly specified to connection API calls, Domain Name System (DNS) lookup failure or memory shortage. - /// - internal static string SNI_ERROR_44 { - get { - return ResourceManager.GetString("SNI_ERROR_44", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported.. - /// - internal static string SNI_ERROR_47 { - get { - return ResourceManager.GetString("SNI_ERROR_47", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connecting to a named SQL Server instance using the MultiSubnetFailover connection option is not supported.. - /// - internal static string SNI_ERROR_48 { - get { - return ResourceManager.GetString("SNI_ERROR_48", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol.. - /// - internal static string SNI_ERROR_49 { - get { - return ResourceManager.GetString("SNI_ERROR_49", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid parameter(s) found. - /// - internal static string SNI_ERROR_5 { - get { - return ResourceManager.GetString("SNI_ERROR_5", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Local Database Runtime error occurred. . - /// - internal static string SNI_ERROR_50 { - get { - return ResourceManager.GetString("SNI_ERROR_50", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An instance name was not specified while connecting to a Local Database Runtime. Specify an instance name in the format (localdb)\instance_name.. - /// - internal static string SNI_ERROR_51 { - get { - return ResourceManager.GetString("SNI_ERROR_51", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to locate a Local Database Runtime installation. Verify that SQL Server Express is properly installed and that the Local Database Runtime feature is enabled.. - /// - internal static string SNI_ERROR_52 { - get { - return ResourceManager.GetString("SNI_ERROR_52", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid Local Database Runtime registry configuration found. Verify that SQL Server Express is properly installed.. - /// - internal static string SNI_ERROR_53 { - get { - return ResourceManager.GetString("SNI_ERROR_53", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to locate the registry entry for SQLUserInstance.dll file path. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. - /// - internal static string SNI_ERROR_54 { - get { - return ResourceManager.GetString("SNI_ERROR_54", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registry value contains an invalid SQLUserInstance.dll file path. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. - /// - internal static string SNI_ERROR_55 { - get { - return ResourceManager.GetString("SNI_ERROR_55", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to load the SQLUserInstance.dll from the location specified in the registry. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. - /// - internal static string SNI_ERROR_56 { - get { - return ResourceManager.GetString("SNI_ERROR_56", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid SQLUserInstance.dll found at the location specified in the registry. Verify that the Local Database Runtime feature of SQL Server Express is properly installed.. - /// - internal static string SNI_ERROR_57 { - get { - return ResourceManager.GetString("SNI_ERROR_57", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unsupported protocol specified. - /// - internal static string SNI_ERROR_6 { - get { - return ResourceManager.GetString("SNI_ERROR_6", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid connection found when setting up new session protocol. - /// - internal static string SNI_ERROR_7 { - get { - return ResourceManager.GetString("SNI_ERROR_7", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Protocol not supported. - /// - internal static string SNI_ERROR_8 { - get { - return ResourceManager.GetString("SNI_ERROR_8", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Associating port with I/O completion mechanism failed. - /// - internal static string SNI_ERROR_9 { - get { - return ResourceManager.GetString("SNI_ERROR_9", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Incorrect physicalConnection type.. - /// - internal static string SNI_IncorrectPhysicalConnectionType { - get { - return ResourceManager.GetString("SNI_IncorrectPhysicalConnectionType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The '{0}' platform is not supported when targeting .NET Framework.. - /// - internal static string SNI_PlatformNotSupportedNetFx { - get { - return ResourceManager.GetString("SNI_PlatformNotSupportedNetFx", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to HTTP Provider. - /// - internal static string SNI_PN0 { - get { - return ResourceManager.GetString("SNI_PN0", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Named Pipes Provider. - /// - internal static string SNI_PN1 { - get { - return ResourceManager.GetString("SNI_PN1", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to . - /// - internal static string SNI_PN10 { - get { - return ResourceManager.GetString("SNI_PN10", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SQL Network Interfaces. - /// - internal static string SNI_PN11 { - get { - return ResourceManager.GetString("SNI_PN11", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Session Provider. - /// - internal static string SNI_PN2 { - get { - return ResourceManager.GetString("SNI_PN2", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Sign Provider. - /// - internal static string SNI_PN3 { - get { - return ResourceManager.GetString("SNI_PN3", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shared Memory Provider. - /// - internal static string SNI_PN4 { - get { - return ResourceManager.GetString("SNI_PN4", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SMux Provider. - /// - internal static string SNI_PN5 { - get { - return ResourceManager.GetString("SNI_PN5", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SSL Provider. - /// - internal static string SNI_PN6 { - get { - return ResourceManager.GetString("SNI_PN6", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TCP Provider. - /// - internal static string SNI_PN7 { - get { - return ResourceManager.GetString("SNI_PN7", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to VIA Provider. - /// - internal static string SNI_PN8 { - get { - return ResourceManager.GetString("SNI_PN8", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CTAIP Provider. - /// - internal static string SNI_PN9 { - get { - return ResourceManager.GetString("SNI_PN9", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection open and login was successful, but then an error occurred while enlisting the connection into the current distributed transaction.. - /// - internal static string Snix_AutoEnlist { - get { - return ResourceManager.GetString("Snix_AutoEnlist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A transport-level error has occurred during connection clean-up.. - /// - internal static string Snix_Close { - get { - return ResourceManager.GetString("Snix_Close", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 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.. - /// - internal static string Snix_Connect { - get { - return ResourceManager.GetString("Snix_Connect", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection open and login was successful, but then an error occurred while enabling MARS for this connection.. - /// - internal static string Snix_EnableMars { - get { - return ResourceManager.GetString("Snix_EnableMars", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A transport-level error has occurred when sending the request to the server.. - /// - internal static string Snix_Execute { - get { - return ResourceManager.GetString("Snix_Execute", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to establish a MARS session in preparation to send the request to the server.. - /// - internal static string Snix_GetMarsSession { - get { - return ResourceManager.GetString("Snix_GetMarsSession", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A connection was successfully established with the server, but then an error occurred during the login process.. - /// - internal static string Snix_Login { - get { - return ResourceManager.GetString("Snix_Login", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A connection was successfully established with the server, but then an error occurred when obtaining the security/SSPI context information for integrated security login.. - /// - internal static string Snix_LoginSspi { - get { - return ResourceManager.GetString("Snix_LoginSspi", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A connection was successfully established with the server, but then an error occurred during the pre-login handshake.. - /// - internal static string Snix_PreLogin { - get { - return ResourceManager.GetString("Snix_PreLogin", resourceCulture); - } - } - - /// - /// 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_PreLoginBeforeSuccessfulWrite { - get { - return ResourceManager.GetString("Snix_PreLoginBeforeSuccessfulWrite", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A transport-level error has occurred during SSPI handshake.. - /// - internal static string Snix_ProcessSspi { - get { - return ResourceManager.GetString("Snix_ProcessSspi", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A transport-level error has occurred when receiving results from the server.. - /// - internal static string Snix_Read { - get { - return ResourceManager.GetString("Snix_Read", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A transport-level error has occurred while sending information to the server.. - /// - internal static string Snix_SendRows { - get { - return ResourceManager.GetString("Snix_SendRows", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The length of '{0}' must match the length of '{1}'.. - /// - internal static string SQL_ArgumentLengthMismatch { - get { - return ResourceManager.GetString("SQL_ArgumentLengthMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This command requires an asynchronous connection. Set "Asynchronous Processing=true" in the connection string.. - /// - internal static string SQL_AsyncConnectionRequired { - get { - return ResourceManager.GetString("SQL_AsyncConnectionRequired", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The asynchronous operation has already completed.. - /// - internal static string SQL_AsyncOperationCompleted { - get { - return ResourceManager.GetString("SQL_AsyncOperationCompleted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication' with 'Integrated Security'.. - /// - internal static string SQL_AuthenticationAndIntegratedSecurity { - get { - return ResourceManager.GetString("SQL_AuthenticationAndIntegratedSecurity", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlBulkCopy.WriteToServer failed because the SqlBulkCopy.DestinationTableName is an invalid multipart name. - /// - internal static string SQL_BulkCopyDestinationTableName { - get { - return ResourceManager.GetString("SQL_BulkCopyDestinationTableName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}] Row {5}.. - /// - internal static string SQL_BulkLoadCannotConvertValue { - get { - return ResourceManager.GetString("SQL_BulkLoadCannotConvertValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The given value{0} of type {1} from the data source cannot be converted to type {2} for Column {3} [{4}].. - /// - internal static string SQL_BulkLoadCannotConvertValueWithoutRowNo { - get { - return ResourceManager.GetString("SQL_BulkLoadCannotConvertValueWithoutRowNo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Must not specify SqlBulkCopyOption.UseInternalTransaction and pass an external Transaction at the same time.. - /// - internal static string SQL_BulkLoadConflictingTransactionOption { - get { - return ResourceManager.GetString("SQL_BulkLoadConflictingTransactionOption", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unexpected existing transaction.. - /// - internal static string SQL_BulkLoadExistingTransaction { - get { - return ResourceManager.GetString("SQL_BulkLoadExistingTransaction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot access destination table '{0}'.. - /// - internal static string SQL_BulkLoadInvalidDestinationTable { - get { - return ResourceManager.GetString("SQL_BulkLoadInvalidDestinationTable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function must not be called during event.. - /// - internal static string SQL_BulkLoadInvalidOperationInsideEvent { - get { - return ResourceManager.GetString("SQL_BulkLoadInvalidOperationInsideEvent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The given column order hint is not valid.. - /// - internal static string SQL_BulkLoadInvalidOrderHint { - get { - return ResourceManager.GetString("SQL_BulkLoadInvalidOrderHint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Timeout Value '{0}' is less than 0.. - /// - internal static string SQL_BulkLoadInvalidTimeout { - get { - return ResourceManager.GetString("SQL_BulkLoadInvalidTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value cannot be converted to SqlVariant.. - /// - internal static string SQL_BulkLoadInvalidVariantValue { - get { - return ResourceManager.GetString("SQL_BulkLoadInvalidVariantValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The locale id '{0}' of the source column '{1}' and the locale id '{2}' of the destination column '{3}' do not match.. - /// - internal static string Sql_BulkLoadLcidMismatch { - get { - return ResourceManager.GetString("Sql_BulkLoadLcidMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The mapped collection is in use and cannot be accessed at this time;. - /// - internal static string SQL_BulkLoadMappingInaccessible { - get { - return ResourceManager.GetString("SQL_BulkLoadMappingInaccessible", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Mappings must be either all name or all ordinal based.. - /// - internal static string SQL_BulkLoadMappingsNamesOrOrdinalsOnly { - get { - return ResourceManager.GetString("SQL_BulkLoadMappingsNamesOrOrdinalsOnly", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The DestinationTableName property must be set before calling this method.. - /// - internal static string SQL_BulkLoadMissingDestinationTable { - get { - return ResourceManager.GetString("SQL_BulkLoadMissingDestinationTable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to obtain column collation information for the destination table. If the table is not in the current database the name must be qualified using the database name (e.g. [mydb]..[mytable](e.g. [mydb]..[mytable]); this also applies to temporary-tables (e.g. #mytable would be specified as tempdb..#mytable).. - /// - internal static string SQL_BulkLoadNoCollation { - get { - return ResourceManager.GetString("SQL_BulkLoadNoCollation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The given ColumnMapping does not match up with any column in the source or destination.. - /// - internal static string SQL_BulkLoadNonMatchingColumnMapping { - get { - return ResourceManager.GetString("SQL_BulkLoadNonMatchingColumnMapping", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The given ColumnName '{0}' does not match up with any column in data source.. - /// - internal static string SQL_BulkLoadNonMatchingColumnName { - get { - return ResourceManager.GetString("SQL_BulkLoadNonMatchingColumnName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Column '{0}' does not allow DBNull.Value.. - /// - internal static string SQL_BulkLoadNotAllowDBNull { - get { - return ResourceManager.GetString("SQL_BulkLoadNotAllowDBNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The column '{0}' was specified more than once.. - /// - internal static string SQL_BulkLoadOrderHintDuplicateColumn { - get { - return ResourceManager.GetString("SQL_BulkLoadOrderHintDuplicateColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The sorted column '{0}' is not valid in the destination table.. - /// - internal static string SQL_BulkLoadOrderHintInvalidColumn { - get { - return ResourceManager.GetString("SQL_BulkLoadOrderHintInvalidColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempt to invoke bulk copy on an object that has a pending operation.. - /// - internal static string SQL_BulkLoadPendingOperation { - get { - return ResourceManager.GetString("SQL_BulkLoadPendingOperation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to String or binary data would be truncated in table '{0}', column '{1}'. Truncated value: '{2}'.. - /// - internal static string SQL_BulkLoadStringTooLong { - get { - return ResourceManager.GetString("SQL_BulkLoadStringTooLong", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A column order hint cannot have an unspecified sort order.. - /// - internal static string SQL_BulkLoadUnspecifiedSortOrder { - get { - return ResourceManager.GetString("SQL_BulkLoadUnspecifiedSortOrder", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to instantiate a SqlAuthenticationInitializer with type '{0}'.. - /// - internal static string SQL_CannotCreateAuthInitializer { - get { - return ResourceManager.GetString("SQL_CannotCreateAuthInitializer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to instantiate an authentication provider with type '{1}' for '{0}'.. - /// - internal static string SQL_CannotCreateAuthProvider { - get { - return ResourceManager.GetString("SQL_CannotCreateAuthProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot create normalizer for '{0}'.. - /// - internal static string SQL_CannotCreateNormalizer { - get { - return ResourceManager.GetString("SQL_CannotCreateNormalizer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find an authentication provider for '{0}'.. - /// - internal static string SQL_CannotFindAuthProvider { - get { - return ResourceManager.GetString("SQL_CannotFindAuthProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find an authentication provider for '{0}'. Install the 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet package (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) to use Active Directory (Entra ID) authentication methods.. - /// - internal static string SQL_CannotFindActiveDirectoryAuthProvider { - get { - return ResourceManager.GetString("SQL_CannotFindActiveDirectoryAuthProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to read the config section for authentication providers.. - /// - internal static string SQL_CannotGetAuthProviderConfig { - get { - return ResourceManager.GetString("SQL_CannotGetAuthProviderConfig", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to get the address of the distributed transaction coordinator for the server, from the server. Is DTC enabled on the server?. - /// - internal static string SQL_CannotGetDTCAddress { - get { - return ResourceManager.GetString("SQL_CannotGetDTCAddress", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The provider '{0}' threw an exception while initializing.. - /// - internal static string SQL_CannotInitializeAuthProvider { - get { - return ResourceManager.GetString("SQL_CannotInitializeAuthProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} cannot be changed while async operation is in progress.. - /// - internal static string SQL_CannotModifyPropertyAsyncOperationInProgress { - get { - return ResourceManager.GetString("SQL_CannotModifyPropertyAsyncOperationInProgress", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Incorrect authentication parameters specified with certificate authentication.. - /// - internal static string SQL_Certificate { - get { - return ResourceManager.GetString("SQL_Certificate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The '{0}' argument must not be null or empty.. - /// - internal static string SQL_ChangePasswordArgumentMissing { - get { - return ResourceManager.GetString("SQL_ChangePasswordArgumentMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ChangePassword can only be used with SQL authentication, not with integrated security.. - /// - internal static string SQL_ChangePasswordConflictsWithSSPI { - get { - return ResourceManager.GetString("SQL_ChangePasswordConflictsWithSSPI", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ChangePassword requires SQL Server 9.0 or later.. - /// - internal static string SQL_ChangePasswordRequiresYukon { - get { - return ResourceManager.GetString("SQL_ChangePasswordRequiresYukon", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The keyword '{0}' must not be specified in the connectionString argument to ChangePassword.. - /// - internal static string SQL_ChangePasswordUseOfUnallowedKey { - get { - return ResourceManager.GetString("SQL_ChangePasswordUseOfUnallowedKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The requested operation cannot be completed because the connection has been broken.. - /// - internal static string SQL_ConnectionDoomed { - get { - return ResourceManager.GetString("SQL_ConnectionDoomed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The connection cannot be used because there is an ongoing operation that must be finished.. - /// - internal static string SQL_ConnectionLockedForBcpEvent { - get { - return ResourceManager.GetString("SQL_ConnectionLockedForBcpEvent", 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 { - return ResourceManager.GetString("SQL_ContextConnectionIsUnsupported", resourceCulture); - } - } - - - /// - /// Looks up a localized string similar to Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication={0}'.. - /// - internal static string SQL_CredentialsNotProvided { - get { - return ResourceManager.GetString("SQL_CredentialsNotProvided", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The instance of SQL Server you attempted to connect to does not support CTAIP.. - /// - internal static string SQL_CTAIPNotSupportedByServer { - get { - return ResourceManager.GetString("SQL_CTAIPNotSupportedByServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Collation specified by SQL Server is not supported.. - /// - internal static string SQL_CultureIdError { - get { - return ResourceManager.GetString("SQL_CultureIdError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type {0} is not supported on this platform.. - /// - internal static string SQL_DbTypeNotSupportedOnThisPlatform { - get { - return ResourceManager.GetString("SQL_DbTypeNotSupportedOnThisPlatform", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Device Code Flow' with 'User ID', 'UID', 'Password' or 'PWD' connection string keywords.. - /// - internal static string SQL_DeviceFlowWithUsernamePassword { - get { - return ResourceManager.GetString("SQL_DeviceFlowWithUsernamePassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0}; handshake={1}; [Login] initialization={2}; . - /// - internal static string SQL_Duration_Login_Begin { - get { - return ResourceManager.GetString("SQL_Duration_Login_Begin", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0}; handshake={1}; [Login] initialization={2}; authentication={3}; . - /// - internal static string SQL_Duration_Login_ProcessConnectionAuth { - get { - return ResourceManager.GetString("SQL_Duration_Login_ProcessConnectionAuth", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0}; handshake={1}; [Login] initialization={2}; authentication={3}; [Post-Login] complete={4}; . - /// - internal static string SQL_Duration_PostLogin { - get { - return ResourceManager.GetString("SQL_Duration_PostLogin", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0};. - /// - internal static string SQL_Duration_PreLogin_Begin { - get { - return ResourceManager.GetString("SQL_Duration_PreLogin_Begin", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The duration spent while attempting to connect to this server was - [Pre-Login] initialization={0}; handshake={1}; . - /// - internal static string SQL_Duration_PreLoginHandshake { - get { - return ResourceManager.GetString("SQL_Duration_PreLoginHandshake", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The instance of SQL Server you attempted to connect to requires encryption but this machine does not support it.. - /// - internal static string SQL_EncryptionNotSupportedByClient { - get { - return ResourceManager.GetString("SQL_EncryptionNotSupportedByClient", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The instance of SQL Server you attempted to connect to does not support encryption.. - /// - internal static string SQL_EncryptionNotSupportedByServer { - get { - return ResourceManager.GetString("SQL_EncryptionNotSupportedByServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Number of fields in record '{0}' does not match the number in the original record.. - /// - internal static string SQL_EnumeratedRecordFieldCountChanged { - get { - return ResourceManager.GetString("SQL_EnumeratedRecordFieldCountChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Metadata for field '{0}' of record '{1}' did not match the original record's metadata.. - /// - internal static string SQL_EnumeratedRecordMetaDataChanged { - get { - return ResourceManager.GetString("SQL_EnumeratedRecordMetaDataChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specified data length {0} exceeds the allowed maximum length of {1}.. - /// - internal static string SQL_ExceedsMaxDataLength { - get { - return ResourceManager.GetString("SQL_ExceedsMaxDataLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ClientConnectionId:{0}. - /// - internal static string SQL_ExClientConnectionId { - get { - return ResourceManager.GetString("SQL_ExClientConnectionId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error Number:{0},State:{1},Class:{2}. - /// - internal static string SQL_ExErrorNumberStateClass { - get { - return ResourceManager.GetString("SQL_ExErrorNumberStateClass", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ClientConnectionId before routing:{0}. - /// - internal static string SQL_ExOriginalClientConnectionId { - get { - return ResourceManager.GetString("SQL_ExOriginalClientConnectionId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Routing Destination:{0}. - /// - internal static string SQL_ExRoutingDestination { - get { - return ResourceManager.GetString("SQL_ExRoutingDestination", resourceCulture); - } - } - - /// - /// 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.. - /// - internal static string SQL_FatalTimeout { - get { - return ResourceManager.GetString("SQL_FatalTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Globalization Invariant Mode is not supported.. - /// - internal static string SQL_GlobalizationInvariantModeNotSupported { - get { - return ResourceManager.GetString("SQL_GlobalizationInvariantModeNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Instance failure.. - /// - internal static string SQL_InstanceFailure { - get { - return ResourceManager.GetString("SQL_InstanceFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Integrated' with 'Password' or 'PWD' connection string keywords.. - /// - internal static string SQL_IntegratedWithPassword { - get { - return ResourceManager.GetString("SQL_IntegratedWithPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Interactive' with 'Password' or 'PWD' connection string keywords.. - /// - internal static string SQL_InteractiveWithPassword { - get { - return ResourceManager.GetString("SQL_InteractiveWithPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. - /// - internal static string Sql_InternalError { - get { - return ResourceManager.GetString("Sql_InternalError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Buffer offset '{1}' plus the bytes available '{0}' is greater than the length of the passed in buffer.. - /// - internal static string SQL_InvalidBufferSizeOrIndex { - get { - return ResourceManager.GetString("SQL_InvalidBufferSizeOrIndex", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Data length '{0}' is less than 0.. - /// - internal static string SQL_InvalidDataLength { - get { - return ResourceManager.GetString("SQL_InvalidDataLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid internal packet size:. - /// - internal static string SQL_InvalidInternalPacketSize { - get { - return ResourceManager.GetString("SQL_InvalidInternalPacketSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The length of the value for the connection parameter <{0}> exceeds the maximum allowed 65535 characters.. - /// - internal static string SQL_InvalidOptionLength { - get { - return ResourceManager.GetString("SQL_InvalidOptionLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid 'Packet Size'. The value must be an integer >= 512 and <= 32768.. - /// - internal static string SQL_InvalidPacketSizeValue { - get { - return ResourceManager.GetString("SQL_InvalidPacketSizeValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The length of the parameter '{0}' exceeds the limit of 128 characters.. - /// - internal static string SQL_InvalidParameterNameLength { - get { - return ResourceManager.GetString("SQL_InvalidParameterNameLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid 3 part name format for TypeName.. - /// - internal static string SQL_InvalidParameterTypeNameFormat { - get { - return ResourceManager.GetString("SQL_InvalidParameterTypeNameFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Server {0}, database {1} is not configured for database mirroring.. - /// - internal static string SQL_InvalidPartnerConfiguration { - get { - return ResourceManager.GetString("SQL_InvalidPartnerConfiguration", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to read when no data is present.. - /// - internal static string SQL_InvalidRead { - get { - return ResourceManager.GetString("SQL_InvalidRead", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The server certificate failed application validation.. - /// - internal static string SQL_InvalidServerCertificate { - get { - return ResourceManager.GetString("SQL_InvalidServerCertificate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The SqlDbType '{0}' is invalid for {1}. Only {2} is supported.. - /// - internal static string SQL_InvalidSqlDbTypeWithOneAllowedType { - get { - return ResourceManager.GetString("SQL_InvalidSqlDbTypeWithOneAllowedType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unsupported SQL Server version. The .NET Framework SqlClient Data Provider can only be used with SQL Server versions 7.0 and later.. - /// - internal static string SQL_InvalidSQLServerVersionUnknown { - get { - return ResourceManager.GetString("SQL_InvalidSQLServerVersionUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid SSPI packet size.. - /// - internal static string SQL_InvalidSSPIPacketSize { - get { - return ResourceManager.GetString("SQL_InvalidSSPIPacketSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid Packet Size.. - /// - internal static string SQL_InvalidTDSPacketSize { - get { - return ResourceManager.GetString("SQL_InvalidTDSPacketSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The SQL Server instance returned an invalid or unsupported protocol version during login negotiation.. - /// - internal static string SQL_InvalidTDSVersion { - get { - return ResourceManager.GetString("SQL_InvalidTDSVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid 3 part name format for UdtTypeName.. - /// - internal static string SQL_InvalidUdt3PartNameFormat { - get { - return ResourceManager.GetString("SQL_InvalidUdt3PartNameFormat", 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 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.. - /// - internal static string SQL_KerberosTicketMissingError { - get { - return ResourceManager.GetString("SQL_KerberosTicketMissingError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The connection does not support MultipleActiveResultSets.. - /// - internal static string SQL_MarsUnsupportedOnConnection { - get { - return ResourceManager.GetString("SQL_MarsUnsupportedOnConnection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MetaData parameter array must have length equivalent to ParameterDirection array argument.. - /// - internal static string Sql_MismatchedMetaDataDirectionArrayLengths { - get { - return ResourceManager.GetString("Sql_MismatchedMetaDataDirectionArrayLengths", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlDbType.SmallMoney overflow. Value '{0}' is out of range. Must be between -214,748.3648 and 214,748.3647.. - /// - internal static string SQL_MoneyOverflow { - get { - return ResourceManager.GetString("SQL_MoneyOverflow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to authenticate the user {0} in Active Directory (Authentication={1}).. - /// - internal static string SQL_MSALFailure { - get { - return ResourceManager.GetString("SQL_MSALFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error code 0x{0}. - /// - internal static string SQL_MSALInnerException { - get { - return ResourceManager.GetString("SQL_MSALInnerException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Nested TransactionScopes are not supported.. - /// - internal static string SQL_NestedTransactionScopesNotSupported { - get { - return ResourceManager.GetString("SQL_NestedTransactionScopesNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The keyword 'Network Library' is not supported on this platform, prefix the 'Data Source' with the protocol desired instead ('tcp:' for a TCP connection, or 'np:' for a Named Pipe connection).. - /// - internal static string SQL_NetworkLibraryNotSupported { - get { - return ResourceManager.GetString("SQL_NetworkLibraryNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to GetBytes on column '{0}'. The GetBytes function can only be used on columns of type Text, NText, or Image.. - /// - internal static string SQL_NonBlobColumn { - get { - return ResourceManager.GetString("SQL_NonBlobColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to GetChars on column '{0}'. The GetChars function can only be used on columns of type Text, NText, Xml, VarChar or NVarChar.. - /// - internal static string SQL_NonCharColumn { - get { - return ResourceManager.GetString("SQL_NonCharColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication={0}' with 'Password' or 'PWD' connection string keywords.. - /// - internal static string SQL_NonInteractiveWithPassword { - get { - return ResourceManager.GetString("SQL_NonInteractiveWithPassword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SSE Instance re-direction is not supported for non-local user instances.. - /// - internal static string SQL_NonLocalSSEInstance { - get { - return ResourceManager.GetString("SQL_NonLocalSSEInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid command sent to ExecuteXmlReader. The command must return an Xml result.. - /// - internal static string SQL_NonXmlResult { - get { - return ResourceManager.GetString("SQL_NonXmlResult", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Notifications require SQL Server 9.0 or later.. - /// - internal static string SQL_NotificationsRequireYukon { - get { - return ResourceManager.GetString("SQL_NotificationsRequireYukon", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} enumeration value, {1}, is not supported by the .NET Framework SqlClient Data Provider.. - /// - internal static string SQL_NotSupportedEnumerationValue { - get { - return ResourceManager.GetString("SQL_NotSupportedEnumerationValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command parameter must have a non null and non empty command text.. - /// - internal static string Sql_NullCommandText { - get { - return ResourceManager.GetString("Sql_NullCommandText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid transaction or invalid name for a point at which to save within the transaction.. - /// - internal static string SQL_NullEmptyTransactionName { - get { - return ResourceManager.GetString("SQL_NullEmptyTransactionName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Open result count exceeded.. - /// - internal static string SQL_OpenResultCountExceeded { - get { - return ResourceManager.GetString("SQL_OpenResultCountExceeded", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Operation cancelled by user.. - /// - internal static string SQL_OperationCancelled { - get { - return ResourceManager.GetString("SQL_OperationCancelled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter '{0}' cannot be null or empty.. - /// - internal static string SQL_ParameterCannotBeEmpty { - get { - return ResourceManager.GetString("SQL_ParameterCannotBeEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command.. - /// - internal static string SQL_ParameterDirectionInvalidForOptimizedBinding { - get { - return ResourceManager.GetString("SQL_ParameterDirectionInvalidForOptimizedBinding", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter '{0}' exceeds the size limit for the sql_variant datatype.. - /// - internal static string SQL_ParameterInvalidVariant { - get { - return ResourceManager.GetString("SQL_ParameterInvalidVariant", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} type parameter '{1}' must have a valid type name.. - /// - internal static string SQL_ParameterTypeNameRequired { - get { - return ResourceManager.GetString("SQL_ParameterTypeNameRequired", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error.. - /// - internal static string SQL_ParsingError { - get { - return ResourceManager.GetString("SQL_ParsingError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Authentication Library Type: {1}. - /// - internal static string SQL_ParsingErrorAuthLibraryType { - get { - return ResourceManager.GetString("SQL_ParsingErrorAuthLibraryType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Feature Id: {1}. - /// - internal static string SQL_ParsingErrorFeatureId { - get { - return ResourceManager.GetString("SQL_ParsingErrorFeatureId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Length: {1}. - /// - internal static string SQL_ParsingErrorLength { - get { - return ResourceManager.GetString("SQL_ParsingErrorLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Offset: {1}. - /// - internal static string SQL_ParsingErrorOffset { - get { - return ResourceManager.GetString("SQL_ParsingErrorOffset", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Status: {1}. - /// - internal static string SQL_ParsingErrorStatus { - get { - return ResourceManager.GetString("SQL_ParsingErrorStatus", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Token : {1}. - /// - internal static string SQL_ParsingErrorToken { - get { - return ResourceManager.GetString("SQL_ParsingErrorToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}, Value: {1}. - /// - internal static string SQL_ParsingErrorValue { - get { - return ResourceManager.GetString("SQL_ParsingErrorValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal connection fatal error. Error state: {0}. - /// - internal static string SQL_ParsingErrorWithState { - get { - return ResourceManager.GetString("SQL_ParsingErrorWithState", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The command execution cannot proceed due to a pending asynchronous operation already in progress.. - /// - internal static string SQL_PendingBeginXXXExists { - get { - return ResourceManager.GetString("SQL_PendingBeginXXXExists", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Precision value '{0}' is either less than 0 or greater than the maximum allowed precision of 38.. - /// - internal static string SQL_PrecisionValueOutOfRange { - get { - return ResourceManager.GetString("SQL_PrecisionValueOutOfRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate failed chain validation. Error(s): '{0}'.. - /// - internal static string SQL_RemoteCertificateChainErrors { - get { - return ResourceManager.GetString("SQL_RemoteCertificateChainErrors", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The certificate provided by the server does not match the certificate provided by the ServerCertificate option.. - /// - internal static string SQL_RemoteCertificateDoesNotMatchServerCertificate { - get { - return ResourceManager.GetString("SQL_RemoteCertificateDoesNotMatchServerCertificate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate name mismatch. The provided 'DataSource' or 'HostNameInCertificate' does not match the name in the certificate.. - /// - internal static string SQL_RemoteCertificateNameMismatch { - get { - return ResourceManager.GetString("SQL_RemoteCertificateNameMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate not available while validating the certificate.. - /// - internal static string SQL_RemoteCertificateNotAvailable { - get { - return ResourceManager.GetString("SQL_RemoteCertificateNotAvailable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Scale value '{0}' is either less than 0 or greater than the maximum allowed scale of 38.. - /// - internal static string SQL_ScaleValueOutOfRange { - get { - return ResourceManager.GetString("SQL_ScaleValueOutOfRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication=Active Directory Device Code Flow' has been specified in the connection string.. - /// - internal static string SQL_SettingCredentialWithDeviceFlow { - get { - return ResourceManager.GetString("SQL_SettingCredentialWithDeviceFlow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication=Active Directory Integrated' has been specified in the connection string.. - /// - internal static string SQL_SettingCredentialWithIntegrated { - get { - return ResourceManager.GetString("SQL_SettingCredentialWithIntegrated", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string.. - /// - internal static string SQL_SettingCredentialWithInteractive { - get { - return ResourceManager.GetString("SQL_SettingCredentialWithInteractive", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot set the Credential property if 'Authentication={0}' has been specified in the connection string.. - /// - internal static string SQL_SettingCredentialWithNonInteractive { - get { - return ResourceManager.GetString("SQL_SettingCredentialWithNonInteractive", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Device Code Flow', if the Credential property has been set.. - /// - internal static string SQL_SettingDeviceFlowWithCredential { - get { - return ResourceManager.GetString("SQL_SettingDeviceFlowWithCredential", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Integrated', if the Credential property has been set.. - /// - internal static string SQL_SettingIntegratedWithCredential { - get { - return ResourceManager.GetString("SQL_SettingIntegratedWithCredential", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set.. - /// - internal static string SQL_SettingInteractiveWithCredential { - get { - return ResourceManager.GetString("SQL_SettingInteractiveWithCredential", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot use 'Authentication={0}', if the Credential property has been set.. - /// - internal static string SQL_SettingNonInteractiveWithCredential { - get { - return ResourceManager.GetString("SQL_SettingNonInteractiveWithCredential", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A severe error occurred on the current command. The results, if any, should be discarded.. - /// - internal static string SQL_SevereError { - get { - return ResourceManager.GetString("SQL_SevereError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlDbType.SmallDateTime overflow. Value '{0}' is out of range. Must be between 1/1/1900 12:00:00 AM and 6/6/2079 11:59:59 PM.. - /// - internal static string SQL_SmallDateTimeOverflow { - get { - return ResourceManager.GetString("SQL_SmallDateTimeOverflow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} enumeration value, {1}, is not supported by SQL Server 7.0 or SQL Server 2000.. - /// - internal static string SQL_SnapshotNotSupported { - get { - return ResourceManager.GetString("SQL_SnapshotNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Memory allocation for internal connection failed.. - /// - internal static string SQL_SNIPacketAllocationFailure { - get { - return ResourceManager.GetString("SQL_SNIPacketAllocationFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Socket did not throw expected '{0}' with error code '{1}'.. - /// - internal static string SQL_SocketDidNotThrow { - get { - return ResourceManager.GetString("SQL_SocketDidNotThrow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlCommand.DeriveParameters failed because the SqlCommand.CommandText property value is an invalid multipart name. - /// - internal static string SQL_SqlCommandCommandText { - get { - return ResourceManager.GetString("SQL_SqlCommandCommandText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' cannot be called when the SqlDataRecord is read only.. - /// - internal static string SQL_SqlUpdatableRecordReadOnly { - get { - return ResourceManager.GetString("SQL_SqlUpdatableRecordReadOnly", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The target principal name is incorrect. Cannot generate SSPI context.. - /// - internal static string SQL_SSPIGenerateError { - get { - return ResourceManager.GetString("SQL_SSPIGenerateError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot initialize SSPI package.. - /// - internal static string SQL_SSPIInitializeError { - get { - return ResourceManager.GetString("SQL_SSPIInitializeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to GetStream on column '{0}'. The GetStream function can only be used on columns of type Binary, Image, Udt or VarBinary.. - /// - internal static string SQL_StreamNotSupportOnColumnType { - get { - return ResourceManager.GetString("SQL_StreamNotSupportOnColumnType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Stream does not support reading.. - /// - internal static string SQL_StreamReadNotSupported { - get { - return ResourceManager.GetString("SQL_StreamReadNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Stream does not support seeking.. - /// - internal static string SQL_StreamSeekNotSupported { - get { - return ResourceManager.GetString("SQL_StreamSeekNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Stream does not support writing.. - /// - internal static string SQL_StreamWriteNotSupported { - get { - return ResourceManager.GetString("SQL_StreamWriteNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Processing of results from SQL Server failed because of an invalid multipart name. - /// - internal static string SQL_TDSParserTableName { - get { - return ResourceManager.GetString("SQL_TDSParserTableName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to GetTextReader on column '{0}'. The GetTextReader function can only be used on columns of type Char, NChar, NText, NVarChar, Text or VarChar.. - /// - internal static string SQL_TextReaderNotSupportOnColumnType { - get { - return ResourceManager.GetString("SQL_TextReaderNotSupportOnColumnType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.. - /// - internal static string SQL_Timeout { - get { - return ResourceManager.GetString("SQL_Timeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Active Directory Device Code Flow authentication timed out. The user took too long to respond to the authentication request.. - /// - internal static string SQL_Timeout_Active_Directory_DeviceFlow_Authentication { - get { - return ResourceManager.GetString("SQL_Timeout_Active_Directory_DeviceFlow_Authentication", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.. - /// - internal static string SQL_Timeout_Active_Directory_Interactive_Authentication { - get { - return ResourceManager.GetString("SQL_Timeout_Active_Directory_Interactive_Authentication", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.. - /// - internal static string SQL_Timeout_Execution { - get { - return ResourceManager.GetString("SQL_Timeout_Execution", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This failure occurred while attempting to connect to the {0} server.. - /// - internal static string SQL_Timeout_FailoverInfo { - get { - return ResourceManager.GetString("SQL_Timeout_FailoverInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed at the start of the login phase. This could be because of insufficient time provided for connection timeout.. - /// - internal static string SQL_Timeout_Login_Begin { - get { - return ResourceManager.GetString("SQL_Timeout_Login_Begin", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed while attempting to authenticate the login. This could be because the server failed to authenticate the user or the server was unable to respond back in time.. - /// - internal static string SQL_Timeout_Login_ProcessConnectionAuth { - get { - return ResourceManager.GetString("SQL_Timeout_Login_ProcessConnectionAuth", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections.. - /// - internal static string SQL_Timeout_PostLogin { - get { - return ResourceManager.GetString("SQL_Timeout_PostLogin", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed at the start of the pre-login phase. This could be because of insufficient time provided for connection timeout.. - /// - internal static string SQL_Timeout_PreLogin_Begin { - get { - return ResourceManager.GetString("SQL_Timeout_PreLogin_Begin", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement. This could be because the pre-login handshake failed or the server was unable to respond back in time.. - /// - internal static string SQL_Timeout_PreLogin_ConsumeHandshake { - get { - return ResourceManager.GetString("SQL_Timeout_PreLogin_ConsumeHandshake", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed while attempting to create and initialize a socket to the server. This could be either because the server was unreachable or unable to respond back in time.. - /// - internal static string SQL_Timeout_PreLogin_InitializeConnection { - get { - return ResourceManager.GetString("SQL_Timeout_PreLogin_InitializeConnection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connection Timeout Expired. The timeout period elapsed while making a pre-login handshake request. This could be because the server was unable to respond back in time.. - /// - internal static string SQL_Timeout_PreLogin_SendHandshake { - get { - return ResourceManager.GetString("SQL_Timeout_PreLogin_SendHandshake", resourceCulture); - } - } - - /// - /// 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 { - return ResourceManager.GetString("SQL_Timeout_RoutingDestinationInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlDbType.Time overflow. Value '{0}' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999.. - /// - internal static string SQL_TimeOverflow { - get { - return ResourceManager.GetString("SQL_TimeOverflow", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Scale value '{0}' is either less than 0 or greater than the maximum allowed scale of 7.. - /// - internal static string SQL_TimeScaleValueOutOfRange { - get { - return ResourceManager.GetString("SQL_TimeScaleValueOutOfRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Too many values.. - /// - internal static string SQL_TooManyValues { - get { - return ResourceManager.GetString("SQL_TooManyValues", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlParameter.TypeName is an invalid multipart name. - /// - internal static string SQL_TypeName { - get { - return ResourceManager.GetString("SQL_TypeName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlParameter.UdtTypeName is an invalid multipart name. - /// - internal static string SQL_UDTTypeName { - get { - return ResourceManager.GetString("SQL_UDTTypeName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unexpected server event: {0}.. - /// - internal static string SQL_UnexpectedSmiEvent { - get { - return ResourceManager.GetString("SQL_UnexpectedSmiEvent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unrecognized System.Transactions.IsolationLevel enumeration value: {0}.. - /// - internal static string SQL_UnknownSysTxIsolationLevel { - get { - return ResourceManager.GetString("SQL_UnknownSysTxIsolationLevel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The authentication '{0}' is not supported.. - /// - internal static string SQL_UnsupportedAuthentication { - get { - return ResourceManager.GetString("SQL_UnsupportedAuthentication", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The provider '{0}' does not support authentication '{1}'.. - /// - internal static string SQL_UnsupportedAuthenticationByProvider { - get { - return ResourceManager.GetString("SQL_UnsupportedAuthenticationByProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unsupported authentication specified in this context: {0}. - /// - internal static string SQL_UnsupportedAuthenticationSpecified { - get { - return ResourceManager.GetString("SQL_UnsupportedAuthenticationSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The server is attempting to use a feature that is not supported on this platform.. - /// - internal static string SQL_UnsupportedFeature { - get { - return ResourceManager.GetString("SQL_UnsupportedFeature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The keyword '{0}' is not supported on this platform.. - /// - internal static string SQL_UnsupportedKeyword { - get { - return ResourceManager.GetString("SQL_UnsupportedKeyword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SQL authentication method '{0}' is not supported.. - /// - internal static string SQL_UnsupportedSqlAuthenticationMethod { - get { - return ResourceManager.GetString("SQL_UnsupportedSqlAuthenticationMethod", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The currently loaded System.Transactions.dll does not support Global Transactions.. - /// - internal static string SQL_UnsupportedSysTxVersion { - get { - return ResourceManager.GetString("SQL_UnsupportedSysTxVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Received an unsupported token '{0}' while reading data from the server.. - /// - internal static string SQL_UnsupportedToken { - get { - return ResourceManager.GetString("SQL_UnsupportedToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to User Instance and Failover are not compatible options. Please choose only one of the two in the connection string.. - /// - internal static string SQL_UserInstanceFailoverNotCompatible { - get { - return ResourceManager.GetString("SQL_UserInstanceFailoverNotCompatible", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A user instance was requested in the connection string but the server specified does not support this option.. - /// - internal static string SQL_UserInstanceFailure { - get { - return ResourceManager.GetString("SQL_UserInstanceFailure", resourceCulture); - } - } - - /// - /// 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_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); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to GetXmlReader on column '{0}'. The GetXmlReader function can only be used on columns of type Xml.. - /// - internal static string SQL_XmlReaderNotSupportOnColumnType { - get { - return ResourceManager.GetString("SQL_XmlReaderNotSupportOnColumnType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Exception occurred while trying to set the AppContext Switch '{0}'={1}.. - /// - internal static string SqlAppContextSwitchManager_InvalidValue { - get { - return ResourceManager.GetString("SqlAppContextSwitchManager_InvalidValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Notification values used by Microsoft SQL Server.. - /// - internal static string SqlCommand_Notification { - get { - return ResourceManager.GetString("SqlCommand_Notification", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Automatic enlistment in notifications used by Microsoft SQL Server.. - /// - internal static string SqlCommand_NotificationAutoEnlist { - get { - return ResourceManager.GetString("SqlCommand_NotificationAutoEnlist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The DataAdapter for which to automatically generate SqlCommands. - /// - internal static string SqlCommandBuilder_DataAdapter { - get { - return ResourceManager.GetString("SqlCommandBuilder_DataAdapter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The decimal separator used in numeric literals.. - /// - internal static string SqlCommandBuilder_DecimalSeparator { - get { - return ResourceManager.GetString("SqlCommandBuilder_DecimalSeparator", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The character used in a text command as the opening quote for quoting identifiers that contain special characters.. - /// - internal static string SqlCommandBuilder_QuotePrefix { - get { - return ResourceManager.GetString("SqlCommandBuilder_QuotePrefix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The character used in a text command as the closing quote for quoting identifiers that contain special characters.. - /// - internal static string SqlCommandBuilder_QuoteSuffix { - get { - return ResourceManager.GetString("SqlCommandBuilder_QuoteSuffix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Access token to use for authentication.. - /// - internal static string SqlConnection_AccessToken { - get { - return ResourceManager.GetString("SqlConnection_AccessToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to State of connection, synchronous or asynchronous. 'Asynchronous Processing=x' in the connection string.. - /// - internal static string SqlConnection_Asynchronous { - get { - return ResourceManager.GetString("SqlConnection_Asynchronous", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A guid to represent the physical connection.. - /// - internal static string SqlConnection_ClientConnectionId { - get { - return ResourceManager.GetString("SqlConnection_ClientConnectionId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Information used to connect to a DataSource, such as 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'.. - /// - internal static string SqlConnection_ConnectionString { - get { - return ResourceManager.GetString("SqlConnection_ConnectionString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current connection timeout value, 'Connect Timeout=X' in the ConnectionString.. - /// - internal static string SqlConnection_ConnectionTimeout { - get { - return ResourceManager.GetString("SqlConnection_ConnectionTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to User Id and secure password to use for authentication.. - /// - internal static string SqlConnection_Credential { - get { - return ResourceManager.GetString("SqlConnection_Credential", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Custom column encryption key store providers.. - /// - internal static string SqlConnection_CustomColumnEncryptionKeyStoreProviders { - get { - return ResourceManager.GetString("SqlConnection_CustomColumnEncryptionKeyStoreProviders", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current SQL Server database, 'Initial Catalog=X' in the connection string.. - /// - internal static string SqlConnection_Database { - get { - return ResourceManager.GetString("SqlConnection_Database", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current SqlServer that the connection is opened to, 'Data Source=X' in the connection string.. - /// - internal static string SqlConnection_DataSource { - get { - return ResourceManager.GetString("SqlConnection_DataSource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Network packet size, 'Packet Size=x' in the connection string.. - /// - internal static string SqlConnection_PacketSize { - get { - return ResourceManager.GetString("SqlConnection_PacketSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Information used to connect for replication.. - /// - internal static string SqlConnection_Replication { - get { - return ResourceManager.GetString("SqlConnection_Replication", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Server Process Id (SPID) of the active connection.. - /// - internal static string SqlConnection_ServerProcessId { - get { - return ResourceManager.GetString("SqlConnection_ServerProcessId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Version of the SQL Server accessed by the SqlConnection.. - /// - internal static string SqlConnection_ServerVersion { - get { - return ResourceManager.GetString("SqlConnection_ServerVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Collect statistics for this connection.. - /// - internal static string SqlConnection_StatisticsEnabled { - get { - return ResourceManager.GetString("SqlConnection_StatisticsEnabled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Workstation Id, 'Workstation ID=x' in the connection string.. - /// - internal static string SqlConnection_WorkstationId { - get { - return ResourceManager.GetString("SqlConnection_WorkstationId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot convert object of type '{0}' to object of type '{1}'.. - /// - internal static string SqlConvert_ConvertFailed { - get { - return ResourceManager.GetString("SqlConvert_ConvertFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The connection is broken and recovery is not possible. The client driver attempted to recover the connection one or more times and all attempts failed. Increase the value of ConnectRetryCount to increase the number of recovery attempts.. - /// - internal static string SQLCR_AllAttemptsFailed { - get { - return ResourceManager.GetString("SQLCR_AllAttemptsFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The server did not preserve SSL encryption during a recovery attempt, connection recovery is not possible.. - /// - internal static string SQLCR_EncryptionChanged { - get { - return ResourceManager.GetString("SQLCR_EncryptionChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid ConnectRetryCount value (should be 0-255).. - /// - internal static string SQLCR_InvalidConnectRetryCountValue { - get { - return ResourceManager.GetString("SQLCR_InvalidConnectRetryCountValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid ConnectRetryInterval value (should be 1-60).. - /// - internal static string SQLCR_InvalidConnectRetryIntervalValue { - get { - return ResourceManager.GetString("SQLCR_InvalidConnectRetryIntervalValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Next reconnection attempt will exceed query timeout. Reconnection was terminated.. - /// - internal static string SQLCR_NextAttemptWillExceedQueryTimeout { - get { - return ResourceManager.GetString("SQLCR_NextAttemptWillExceedQueryTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The server did not acknowledge a recovery attempt, connection recovery is not possible.. - /// - internal static string SQLCR_NoCRAckAtReconnection { - get { - return ResourceManager.GetString("SQLCR_NoCRAckAtReconnection", resourceCulture); - } - } - - /// - /// 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_TDSVersionNotPreserved { - get { - return ResourceManager.GetString("SQLCR_TDSVersionNotPreserved", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.. - /// - internal static string SQLCR_UnrecoverableClient { - get { - return ResourceManager.GetString("SQLCR_UnrecoverableClient", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The connection is broken and recovery is not possible. The connection is marked by the server as unrecoverable. No attempt was made to restore the connection.. - /// - internal static string SQLCR_UnrecoverableServer { - get { - return ResourceManager.GetString("SQLCR_UnrecoverableServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failure while attempting to promote transaction.. - /// - internal static string SqlDelegatedTransaction_PromotionFailed { - get { - return ResourceManager.GetString("SqlDelegatedTransaction_PromotionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to To add a command to existing dependency object.. - /// - internal static string SqlDependency_AddCommandDependency { - get { - return ResourceManager.GetString("SqlDependency_AddCommandDependency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The SQL Server Service Broker for the current database is not enabled, and as a result query notifications are not supported. Please enable the Service Broker for this database if you wish to use notifications.. - /// - internal static string SqlDependency_DatabaseBrokerDisabled { - get { - return ResourceManager.GetString("SqlDependency_DatabaseBrokerDisabled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When using SqlDependency without providing an options value, SqlDependency.Start() must be called prior to execution of a command added to the SqlDependency instance.. - /// - internal static string SqlDependency_DefaultOptionsButNoStart { - get { - return ResourceManager.GetString("SqlDependency_DefaultOptionsButNoStart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command is already associated with another dependency object. Can not overwrite.. - /// - internal static string SqlDependency_Duplicate { - get { - return ResourceManager.GetString("SqlDependency_Duplicate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlDependency does not support calling Start() with different connection strings having the same server, user, and database in the same app domain.. - /// - internal static string SqlDependency_DuplicateStart { - get { - return ResourceManager.GetString("SqlDependency_DuplicateStart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlDependency.OnChange does not support multiple event registrations for the same delegate.. - /// - internal static string SqlDependency_EventNoDuplicate { - get { - return ResourceManager.GetString("SqlDependency_EventNoDuplicate", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Property to indicate if this dependency is invalid.. - /// - internal static string SqlDependency_HasChanges { - get { - return ResourceManager.GetString("SqlDependency_HasChanges", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A string that uniquely identifies this dependency object.. - /// - internal static string SqlDependency_Id { - get { - return ResourceManager.GetString("SqlDependency_Id", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No SqlDependency exists for the key.. - /// - internal static string SqlDependency_IdMismatch { - get { - return ResourceManager.GetString("SqlDependency_IdMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Timeout specified is invalid. Timeout cannot be < 0.. - /// - internal static string SqlDependency_InvalidTimeout { - get { - return ResourceManager.GetString("SqlDependency_InvalidTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlDependency.Start has been called for the server the command is executing against more than once, but there is no matching server/user/database Start() call for current command.. - /// - internal static string SqlDependency_NoMatchingServerDatabaseStart { - get { - return ResourceManager.GetString("SqlDependency_NoMatchingServerDatabaseStart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When using SqlDependency without providing an options value, SqlDependency.Start() must be called for each server that is being executed against.. - /// - internal static string SqlDependency_NoMatchingServerStart { - get { - return ResourceManager.GetString("SqlDependency_NoMatchingServerStart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Event that can be used to subscribe for change notifications.. - /// - internal static string SqlDependency_OnChange { - get { - return ResourceManager.GetString("SqlDependency_OnChange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Dependency object used to receive query notifications.. - /// - internal static string SqlDependency_SqlDependency { - get { - return ResourceManager.GetString("SqlDependency_SqlDependency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unexpected type detected on deserialize.. - /// - internal static string SqlDependency_UnexpectedValueOnDeserialize { - get { - return ResourceManager.GetString("SqlDependency_UnexpectedValueOnDeserialize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The process cannot access the file specified because it has been opened in another transaction.. - /// - internal static string SqlFileStream_FileAlreadyInTransaction { - get { - return ResourceManager.GetString("SqlFileStream_FileAlreadyInTransaction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An invalid parameter was passed to the function.. - /// - internal static string SqlFileStream_InvalidParameter { - get { - return ResourceManager.GetString("SqlFileStream_InvalidParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The path name is not valid.. - /// - internal static string SqlFileStream_InvalidPath { - get { - return ResourceManager.GetString("SqlFileStream_InvalidPath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlFileStream is not supported on this platform.. - /// - internal static string SqlFileStream_NotSupported { - get { - return ResourceManager.GetString("SqlFileStream_NotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The path name is invalid or does not point to a disk file.. - /// - internal static string SqlFileStream_PathNotValidDiskResource { - get { - return ResourceManager.GetString("SqlFileStream_PathNotValidDiskResource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The dbType {0} is invalid for this constructor.. - /// - internal static string SqlMetaData_InvalidSqlDbTypeForConstructorFormat { - get { - return ResourceManager.GetString("SqlMetaData_InvalidSqlDbTypeForConstructorFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The name is too long.. - /// - internal static string SqlMetaData_NameTooLong { - get { - return ResourceManager.GetString("SqlMetaData_NameTooLong", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to GetMetaData is not valid for this SqlDbType.. - /// - internal static string SqlMetaData_NoMetadata { - get { - return ResourceManager.GetString("SqlMetaData_NoMetadata", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The sort order and ordinal must either both be specified, or neither should be specified (SortOrder.Unspecified and -1). The values given were: order = {0}, ordinal = {1}.. - /// - internal static string SqlMetaData_SpecifyBothSortOrderAndOrdinal { - get { - return ResourceManager.GetString("SqlMetaData_SpecifyBothSortOrderAndOrdinal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SQL Type has already been loaded with data.. - /// - internal static string SqlMisc_AlreadyFilledMessage { - get { - return ResourceManager.GetString("SqlMisc_AlreadyFilledMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Arithmetic Overflow.. - /// - internal static string SqlMisc_ArithOverflowMessage { - get { - return ResourceManager.GetString("SqlMisc_ArithOverflowMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The buffer is insufficient. Read or write operation failed.. - /// - internal static string SqlMisc_BufferInsufficientMessage { - get { - return ResourceManager.GetString("SqlMisc_BufferInsufficientMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to access a closed XmlReader.. - /// - internal static string SqlMisc_ClosedXmlReaderMessage { - get { - return ResourceManager.GetString("SqlMisc_ClosedXmlReaderMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Two strings to be compared have different collation.. - /// - internal static string SqlMisc_CompareDiffCollationMessage { - get { - return ResourceManager.GetString("SqlMisc_CompareDiffCollationMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Two strings to be concatenated have different collation.. - /// - internal static string SqlMisc_ConcatDiffCollationMessage { - get { - return ResourceManager.GetString("SqlMisc_ConcatDiffCollationMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Conversion overflows.. - /// - internal static string SqlMisc_ConversionOverflowMessage { - get { - return ResourceManager.GetString("SqlMisc_ConversionOverflowMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.. - /// - internal static string SqlMisc_DateTimeOverflowMessage { - get { - return ResourceManager.GetString("SqlMisc_DateTimeOverflowMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Divide by zero error encountered.. - /// - internal static string SqlMisc_DivideByZeroMessage { - get { - return ResourceManager.GetString("SqlMisc_DivideByZeroMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The input wasn't in a correct format.. - /// - internal static string SqlMisc_FormatMessage { - get { - return ResourceManager.GetString("SqlMisc_FormatMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid array size.. - /// - internal static string SqlMisc_InvalidArraySizeMessage { - get { - return ResourceManager.GetString("SqlMisc_InvalidArraySizeMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid SqlDateTime.. - /// - internal static string SqlMisc_InvalidDateTimeMessage { - get { - return ResourceManager.GetString("SqlMisc_InvalidDateTimeMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Argument to GetDayOfWeek must be integer between 1 and 7.. - /// - internal static string SqlMisc_InvalidFirstDayMessage { - get { - return ResourceManager.GetString("SqlMisc_InvalidFirstDayMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid flag value.. - /// - internal static string SqlMisc_InvalidFlagMessage { - get { - return ResourceManager.GetString("SqlMisc_InvalidFlagMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to call {0} when the stream is closed.. - /// - internal static string SqlMisc_InvalidOpStreamClosed { - get { - return ResourceManager.GetString("SqlMisc_InvalidOpStreamClosed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to call {0} when the stream non-readable.. - /// - internal static string SqlMisc_InvalidOpStreamNonReadable { - get { - return ResourceManager.GetString("SqlMisc_InvalidOpStreamNonReadable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to call {0} when the stream is non-seekable.. - /// - internal static string SqlMisc_InvalidOpStreamNonSeekable { - get { - return ResourceManager.GetString("SqlMisc_InvalidOpStreamNonSeekable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attempt to call {0} when the stream non-writable.. - /// - internal static string SqlMisc_InvalidOpStreamNonWritable { - get { - return ResourceManager.GetString("SqlMisc_InvalidOpStreamNonWritable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid numeric precision/scale.. - /// - internal static string SqlMisc_InvalidPrecScaleMessage { - get { - return ResourceManager.GetString("SqlMisc_InvalidPrecScaleMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The SqlBytes and SqlChars don't support length of more than 2GB in this version.. - /// - internal static string SqlMisc_LenTooLargeMessage { - get { - return ResourceManager.GetString("SqlMisc_LenTooLargeMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Message. - /// - internal static string SqlMisc_MessageString { - get { - return ResourceManager.GetString("SqlMisc_MessageString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There is no buffer. Read or write operation failed.. - /// - internal static string SqlMisc_NoBufferMessage { - get { - return ResourceManager.GetString("SqlMisc_NoBufferMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SQL Type has not been loaded with data.. - /// - internal static string SqlMisc_NotFilledMessage { - get { - return ResourceManager.GetString("SqlMisc_NotFilledMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Null. - /// - internal static string SqlMisc_NullString { - get { - return ResourceManager.GetString("SqlMisc_NullString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Data is Null. This method or property cannot be called on Null values.. - /// - internal static string SqlMisc_NullValueMessage { - get { - return ResourceManager.GetString("SqlMisc_NullValueMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Conversion from SqlDecimal to Decimal overflows.. - /// - internal static string SqlMisc_NumeToDecOverflowMessage { - get { - return ResourceManager.GetString("SqlMisc_NumeToDecOverflowMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot set to non-zero length, because current value is Null.. - /// - internal static string SqlMisc_SetNonZeroLenOnNullMessage { - get { - return ResourceManager.GetString("SqlMisc_SetNonZeroLenOnNullMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SqlType error.. - /// - internal static string SqlMisc_SqlTypeMessage { - get { - return ResourceManager.GetString("SqlMisc_SqlTypeMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Stream has been closed or disposed.. - /// - internal static string SqlMisc_StreamClosedMessage { - get { - return ResourceManager.GetString("SqlMisc_StreamClosedMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred while reading.. - /// - internal static string SqlMisc_StreamErrorMessage { - get { - return ResourceManager.GetString("SqlMisc_StreamErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Subclass did not override a required method.. - /// - internal static string SqlMisc_SubclassMustOverride { - get { - return ResourceManager.GetString("SqlMisc_SubclassMustOverride", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A time zone was specified. SqlDateTime does not support time zones.. - /// - internal static string SqlMisc_TimeZoneSpecifiedMessage { - get { - return ResourceManager.GetString("SqlMisc_TimeZoneSpecifiedMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Data returned is larger than 2Gb in size. Use SequentialAccess command behavior in order to get all of the data.. - /// - internal static string SqlMisc_TruncationMaxDataMessage { - get { - return ResourceManager.GetString("SqlMisc_TruncationMaxDataMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Numeric arithmetic causes truncation.. - /// - internal static string SqlMisc_TruncationMessage { - get { - return ResourceManager.GetString("SqlMisc_TruncationMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot write to non-zero offset, because current value is Null.. - /// - internal static string SqlMisc_WriteNonZeroOffsetOnNullMessage { - get { - return ResourceManager.GetString("SqlMisc_WriteNonZeroOffsetOnNullMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot write from an offset that is larger than current length. It would leave uninitialized data in the buffer.. - /// - internal static string SqlMisc_WriteOffsetLargerThanLenMessage { - get { - return ResourceManager.GetString("SqlMisc_WriteOffsetLargerThanLenMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connecting to a mirrored SQL Server instance using the MultiSubnetFailover connection option is not supported.. - /// - internal static string SQLMSF_FailoverPartnerNotSupported { - get { - return ResourceManager.GetString("SQLMSF_FailoverPartnerNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This SqlCommand object is already associated with another SqlDependency object.. - /// - internal static string SQLNotify_AlreadyHasCommand { - get { - return ResourceManager.GetString("SQLNotify_AlreadyHasCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Notification Error. Type={0}, Info={1}, Source={2}.. - /// - internal static string SQLNotify_ErrorFormat { - get { - return ResourceManager.GetString("SQLNotify_ErrorFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DBNull value for parameter '{0}' is not supported. Table-valued parameters cannot be DBNull.. - /// - internal static string SqlParameter_DBNullNotSupportedForTVP { - get { - return ResourceManager.GetString("SqlParameter_DBNullNotSupportedForTVP", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Precision '{0}' required to send all values in column '{1}' exceeds the maximum supported precision '{2}'. The values must all fit in a single precision.. - /// - internal static string SqlParameter_InvalidTableDerivedPrecisionForTvp { - get { - return ResourceManager.GetString("SqlParameter_InvalidTableDerivedPrecisionForTvp", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Offset in variable length data types.. - /// - internal static string SqlParameter_Offset { - get { - return ResourceManager.GetString("SqlParameter_Offset", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Name of the parameter, like '@p1'. - /// - internal static string SqlParameter_ParameterName { - get { - return ResourceManager.GetString("SqlParameter_ParameterName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When used by DataAdapter.Update, the parameter value is changed from DBNull.Value into (Int32)1 or (Int32)0 if non-null.. - /// - internal static string SqlParameter_SourceColumnNullMapping { - get { - return ResourceManager.GetString("SqlParameter_SourceColumnNullMapping", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter native type.. - /// - internal static string SqlParameter_SqlDbType { - get { - return ResourceManager.GetString("SqlParameter_SqlDbType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The server's name for the type.. - /// - internal static string SqlParameter_TypeName { - get { - return ResourceManager.GetString("SqlParameter_TypeName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TypeName specified for parameter '{0}'. TypeName must only be set for Structured parameters.. - /// - internal static string SqlParameter_UnexpectedTypeNameForNonStruct { - get { - return ResourceManager.GetString("SqlParameter_UnexpectedTypeNameForNonStruct", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ParameterDirection '{0}' specified for parameter '{1}' is not supported. Table-valued parameters only support ParameterDirection.Input.. - /// - internal static string SqlParameter_UnsupportedTVPOutputParameter { - get { - return ResourceManager.GetString("SqlParameter_UnsupportedTVPOutputParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to XmlSchemaCollectionDatabase. - /// - internal static string SqlParameter_XmlSchemaCollectionDatabase { - get { - return ResourceManager.GetString("SqlParameter_XmlSchemaCollectionDatabase", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to XmlSchemaCollectionName. - /// - internal static string SqlParameter_XmlSchemaCollectionName { - get { - return ResourceManager.GetString("SqlParameter_XmlSchemaCollectionName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to XmlSchemaCollectionOwningSchema. - /// - internal static string SqlParameter_XmlSchemaCollectionOwningSchema { - get { - return ResourceManager.GetString("SqlParameter_XmlSchemaCollectionOwningSchema", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The sort ordinal {0} was specified twice.. - /// - internal static string SqlProvider_DuplicateSortOrdinal { - get { - return ResourceManager.GetString("SqlProvider_DuplicateSortOrdinal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The size of column '{0}' is not supported. The size is {1}.. - /// - internal static string SqlProvider_InvalidDataColumnMaxLength { - get { - return ResourceManager.GetString("SqlProvider_InvalidDataColumnMaxLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type of column '{0}' is not supported. The type is '{1}'. - /// - internal static string SqlProvider_InvalidDataColumnType { - get { - return ResourceManager.GetString("SqlProvider_InvalidDataColumnType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The sort ordinal {0} was not specified.. - /// - internal static string SqlProvider_MissingSortOrdinal { - get { - return ResourceManager.GetString("SqlProvider_MissingSortOrdinal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There are not enough fields in the Structured type. Structured types must have at least one field.. - /// - internal static string SqlProvider_NotEnoughColumnsInStructuredType { - get { - return ResourceManager.GetString("SqlProvider_NotEnoughColumnsInStructuredType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The sort ordinal {0} on field {1} exceeds the total number of fields.. - /// - internal static string SqlProvider_SortOrdinalGreaterThanFieldCount { - get { - return ResourceManager.GetString("SqlProvider_SortOrdinalGreaterThanFieldCount", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' is not less than '{1}'; '{2}' cannot be greater than '{3}'.. - /// - internal static string SqlRetryLogic_InvalidMinMaxPair { - get { - return ResourceManager.GetString("SqlRetryLogic_InvalidMinMaxPair", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value '{0}' is out of range. Must be between {1} and {2}.. - /// - internal static string SqlRetryLogic_InvalidRange { - get { - return ResourceManager.GetString("SqlRetryLogic_InvalidRange", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The retry has been canceled at attempt {0}.. - /// - internal static string SqlRetryLogic_RetryCanceled { - get { - return ResourceManager.GetString("SqlRetryLogic_RetryCanceled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The number of retries has exceeded the maximum of {0} attempt(s).. - /// - internal static string SqlRetryLogic_RetryExceeded { - get { - return ResourceManager.GetString("SqlRetryLogic_RetryExceeded", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connecting to a mirrored SQL Server instance using the ApplicationIntent ReadOnly connection option is not supported.. - /// - internal static string SQLROR_FailoverNotSupported { - get { - return ResourceManager.GetString("SQLROR_FailoverNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid routing information received.. - /// - internal static string SQLROR_InvalidRoutingInfo { - get { - return ResourceManager.GetString("SQLROR_InvalidRoutingInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid enhanced routing information received.. - /// - internal static string SQLROR_InvalidEnhancedRoutingInfo { - get { - return ResourceManager.GetString("SQLROR_InvalidEnhancedRoutingInfo", resourceCulture); - } - } - - /// - /// 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 { - return ResourceManager.GetString("SQLROR_RecursiveRoutingNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Server provided routing information, but timeout already expired.. - /// - internal static string SQLROR_TimeoutAfterRoutingInfo { - get { - return ResourceManager.GetString("SQLROR_TimeoutAfterRoutingInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unexpected routing information received.. - /// - internal static string SQLROR_UnexpectedRoutingInfo { - get { - return ResourceManager.GetString("SQLROR_UnexpectedRoutingInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Structured, multiple-valued types can only be used for parameters, and cannot be nested within another type.. - /// - internal static string SQLTVP_TableTypeCanOnlyBeParameter { - get { - return ResourceManager.GetString("SQLTVP_TableTypeCanOnlyBeParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The provider has failed to load the following assembly: {0}. - /// - internal static string SQLUDT_CantLoadAssembly { - get { - return ResourceManager.GetString("SQLUDT_CantLoadAssembly", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to get Type Info for {0},{1}. - /// - internal static string SQLUDT_InvalidDbId { - get { - return ResourceManager.GetString("SQLUDT_InvalidDbId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UDT size must be less than {1}, size: {0}. - /// - internal static string SQLUDT_InvalidSize { - get { - return ResourceManager.GetString("SQLUDT_InvalidSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specified type is not registered on the target server.{0}.. - /// - internal static string SQLUDT_InvalidSqlType { - get { - return ResourceManager.GetString("SQLUDT_InvalidSqlType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' is an invalid user defined type, reason: {1}.. - /// - internal static string SqlUdt_InvalidUdtMessage { - get { - return ResourceManager.GetString("SqlUdt_InvalidUdtMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UdtTypeName property must be set for UDT parameters.. - /// - internal static string SQLUDT_InvalidUdtTypeName { - get { - return ResourceManager.GetString("SQLUDT_InvalidUdtTypeName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UDT parameters not permitted in the where clause unless part of the primary key.. - /// - internal static string SQLUDT_InWhereClause { - get { - return ResourceManager.GetString("SQLUDT_InWhereClause", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to range: 0-8000. - /// - internal static string SQLUDT_MaxByteSizeValue { - get { - return ResourceManager.GetString("SQLUDT_MaxByteSizeValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to unexpected error encountered in SqlClient data provider. {0}. - /// - internal static string SQLUDT_Unexpected { - get { - return ResourceManager.GetString("SQLUDT_Unexpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UdtTypeName property must be set only for UDT parameters.. - /// - internal static string SQLUDT_UnexpectedUdtTypeName { - get { - return ResourceManager.GetString("SQLUDT_UnexpectedUdtTypeName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Native format can't be supported.. - /// - internal static string SqlUdtReason_CannotSupportNative { - get { - return ResourceManager.GetString("SqlUdtReason_CannotSupportNative", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to does not implement IBinarySerialize. - /// - internal static string SqlUdtReason_CannotSupportUserDefined { - get { - return ResourceManager.GetString("SqlUdtReason_CannotSupportUserDefined", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serialization without mapping is not yet supported.. - /// - internal static string SqlUdtReason_MaplessNotYetSupported { - get { - return ResourceManager.GetString("SqlUdtReason_MaplessNotYetSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to supports both in-memory and user-defined formats. - /// - internal static string SqlUdtReason_MultipleSerFormats { - get { - return ResourceManager.GetString("SqlUdtReason_MultipleSerFormats", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Multiple valued assembly references must have a nonzero Assembly Id.. - /// - internal static string SqlUdtReason_MultivaluedAssemblyId { - get { - return ResourceManager.GetString("SqlUdtReason_MultivaluedAssemblyId", resourceCulture); - } - } - - /// - /// 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_NativeFormatExplicitLayoutNotAllowed { - get { - return ResourceManager.GetString("SqlUdtReason_NativeFormatExplicitLayoutNotAllowed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Native format does not support fields (directly or through another field) of type '{0}'. - /// - internal static string SqlUdtReason_NativeFormatNoFieldSupport { - get { - return ResourceManager.GetString("SqlUdtReason_NativeFormatNoFieldSupport", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Native UDT specifies a max byte size. - /// - internal static string SqlUdtReason_NativeUdtMaxByteSize { - get { - return ResourceManager.GetString("SqlUdtReason_NativeUdtMaxByteSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Native UDT not sequential layout due to type '{0}'. - /// - internal static string SqlUdtReason_NativeUdtNotSequentialLayout { - get { - return ResourceManager.GetString("SqlUdtReason_NativeUdtNotSequentialLayout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to field '{0}' is marked non-serialized. - /// - internal static string SqlUdtReason_NonSerializableField { - get { - return ResourceManager.GetString("SqlUdtReason_NonSerializableField", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to does not have a public constructor. - /// - internal static string SqlUdtReason_NoPublicConstructor { - get { - return ResourceManager.GetString("SqlUdtReason_NoPublicConstructor", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to no public constructors. - /// - internal static string SqlUdtReason_NoPublicConstructors { - get { - return ResourceManager.GetString("SqlUdtReason_NoPublicConstructors", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to does not implement INullable. - /// - internal static string SqlUdtReason_NotNullable { - get { - return ResourceManager.GetString("SqlUdtReason_NotNullable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to not serializable. - /// - internal static string SqlUdtReason_NotSerializable { - get { - return ResourceManager.GetString("SqlUdtReason_NotSerializable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to no UDT attribute. - /// - internal static string SqlUdtReason_NoUdtAttribute { - get { - return ResourceManager.GetString("SqlUdtReason_NoUdtAttribute", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 'public static x Null { get; }' method is missing. - /// - internal static string SqlUdtReason_NullPropertyMissing { - get { - return ResourceManager.GetString("SqlUdtReason_NullPropertyMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 'public static x Parse(System.Data.SqlTypes.SqlString)' method is missing. - /// - internal static string SqlUdtReason_ParseMethodMissing { - get { - return ResourceManager.GetString("SqlUdtReason_ParseMethodMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 'public override string ToString()' method is missing. - /// - internal static string SqlUdtReason_ToStringMethodMissing { - get { - return ResourceManager.GetString("SqlUdtReason_ToStringMethodMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type is not public. - /// - internal static string SqlUdtReason_TypeNotPublic { - get { - return ResourceManager.GetString("SqlUdtReason_TypeNotPublic", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' is not a supported handle type.. - /// - internal static string SSPIInvalidHandleType { - get { - return ResourceManager.GetString("SSPIInvalidHandleType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot get value because it is DBNull.. - /// - internal static string StrongTyping_CannotAccessDBNull { - get { - return ResourceManager.GetString("StrongTyping_CannotAccessDBNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot remove column since it is built in to this dataSet.. - /// - internal static string StrongTyping_CannotRemoveColumn { - get { - return ResourceManager.GetString("StrongTyping_CannotRemoveColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot remove relation since it is built in to this dataSet.. - /// - internal static string StrongTyping_CannotRemoveRelation { - get { - return ResourceManager.GetString("StrongTyping_CannotRemoveRelation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attestation information was not returned by SQL Server. Enclave type is '{0}' and enclave attestation URL is '{1}'.. - /// - internal static string TCE_AttestationInfoNotReturnedFromSQLServer { - get { - return ResourceManager.GetString("TCE_AttestationInfoNotReturnedFromSQLServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error occurred when generating enclave package. Attestation Protocol has not been specified in the connection string, but the query requires enclave computations.. - /// - internal static string TCE_AttestationProtocolNotSpecifiedForGeneratingEnclavePackage { - get { - return ResourceManager.GetString("TCE_AttestationProtocolNotSpecifiedForGeneratingEnclavePackage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You have specified the attestation protocol in the connection string, but the SQL Server in use does not support enclave based computations - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details.. - /// - internal static string TCE_AttestationProtocolNotSupported { - get { - return ResourceManager.GetString("TCE_AttestationProtocolNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to initialize connection. The attestation protocol '{0}' does not support the enclave type '{1}'.. - /// - internal static string TCE_AttestationProtocolNotSupportEnclaveType { - get { - return ResourceManager.GetString("TCE_AttestationProtocolNotSupportEnclaveType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You have specified the enclave attestation URL in the connection string, but the SQL Server in use does not support enclave based computations - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details.. - /// - internal static string TCE_AttestationURLNotSupported { - get { - return ResourceManager.GetString("TCE_AttestationURLNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} should be identical on all commands ({1}, {2}, {3}, {4}) when doing batch updates.. - /// - internal static string TCE_BatchedUpdateColumnEncryptionSettingMismatch { - get { - return ResourceManager.GetString("TCE_BatchedUpdateColumnEncryptionSettingMismatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to instantiate an enclave provider with type '{1}' for name '{0}'. Error message: {2} . - /// - internal static string TCE_CannotCreateSqlColumnEncryptionEnclaveProvider { - get { - return ResourceManager.GetString("TCE_CannotCreateSqlColumnEncryptionEnclaveProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to read the configuration section for enclave providers. Make sure the section is correctly formatted in your application configuration file. Error Message: {0}. - /// - internal static string TCE_CannotGetSqlColumnEncryptionEnclaveProviderConfig { - get { - return ResourceManager.GetString("TCE_CannotGetSqlColumnEncryptionEnclaveProviderConfig", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Key store providers cannot be set more than once.. - /// - internal static string TCE_CanOnlyCallOnce { - get { - return ResourceManager.GetString("TCE_CanOnlyCallOnce", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate with thumbprint '{0}' not found in certificate store '{1}' in certificate location '{2}'.. - /// - internal static string TCE_CertificateNotFound { - get { - return ResourceManager.GetString("TCE_CertificateNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate with thumbprint '{0}' not found in certificate store '{1}' in certificate location '{2}'. 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.. - /// - internal static string TCE_CertificateNotFoundSysErr { - get { - return ResourceManager.GetString("TCE_CertificateNotFoundSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate specified in key path '{0}' does not have a private key to encrypt a column encryption key. Verify the certificate is imported correctly.. - /// - internal static string TCE_CertificateWithNoPrivateKey { - get { - return ResourceManager.GetString("TCE_CertificateWithNoPrivateKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate specified in key path '{0}' does not have a private key to decrypt a column encryption key. Verify the certificate is imported correctly.. - /// - internal static string TCE_CertificateWithNoPrivateKeySysErr { - get { - return ResourceManager.GetString("TCE_CertificateWithNoPrivateKeySysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to decrypt column '{0}'.. - /// - internal static string TCE_ColumnDecryptionFailed { - get { - return ResourceManager.GetString("TCE_ColumnDecryptionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Encrypted column encryption keys not found when trying to send the keys to the enclave.. - /// - internal static string TCE_ColumnEncryptionKeysNotFound { - get { - return ResourceManager.GetString("TCE_ColumnEncryptionKeysNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. The signature returned by SQL Server for enclave-enabled column master key, specified at key path '{0}', cannot be null or empty.. - /// - internal static string TCE_ColumnMasterKeySignatureNotFound { - get { - return ResourceManager.GetString("TCE_ColumnMasterKeySignatureNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The signature returned by SQL Server for the column master key, specified in key path '{0}', is invalid (does not match the computed signature). Recreate column master key metadata, making sure the signature inside the metadata is computed using the column master key being referenced in the metadata. If the error persists, please contact Microsoft for assistance.. - /// - internal static string TCE_ColumnMasterKeySignatureVerificationFailed { - get { - return ResourceManager.GetString("TCE_ColumnMasterKeySignatureVerificationFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specifies an attestation protocol for its corresponding enclave attestation service.. - /// - internal static string TCE_DbConnectionString_AttestationProtocol { - get { - return ResourceManager.GetString("TCE_DbConnectionString_AttestationProtocol", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Default column encryption setting for all the commands on the connection.. - /// - internal static string TCE_DbConnectionString_ColumnEncryptionSetting { - get { - return ResourceManager.GetString("TCE_DbConnectionString_ColumnEncryptionSetting", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specifies an endpoint of an enclave attestation service, which will be used to verify whether the enclave, configured in the SQL Server instance for computations on database columns encrypted using Always Encrypted, is valid and secure.. - /// - internal static string TCE_DbConnectionString_EnclaveAttestationUrl { - get { - return ResourceManager.GetString("TCE_DbConnectionString_EnclaveAttestationUrl", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specifies an IP address preference when connecting to SQL instances.. - /// - internal static string TCE_DbConnectionString_IPAddressPreference { - get { - return ResourceManager.GetString("TCE_DbConnectionString_IPAddressPreference", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Decryption failed. The last 10 bytes of the encrypted column encryption key are: '{0}'. The first 10 bytes of ciphertext are: '{1}'.. - /// - internal static string TCE_DecryptionFailed { - get { - return ResourceManager.GetString("TCE_DecryptionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Empty argument '{0}' specified when constructing an object of type '{1}'. '{0}' cannot be empty.. - /// - internal static string TCE_EmptyArgumentInConstructorInternal { - get { - return ResourceManager.GetString("TCE_EmptyArgumentInConstructorInternal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Argument '{0}' cannot be empty when executing method '{1}.{2}'.. - /// - internal static string TCE_EmptyArgumentInternal { - get { - return ResourceManager.GetString("TCE_EmptyArgumentInternal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Empty certificate thumbprint specified in certificate path '{0}'.. - /// - internal static string TCE_EmptyCertificateThumbprint { - get { - return ResourceManager.GetString("TCE_EmptyCertificateThumbprint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Empty certificate thumbprint specified in certificate path '{0}'.. - /// - internal static string TCE_EmptyCertificateThumbprintSysErr { - get { - return ResourceManager.GetString("TCE_EmptyCertificateThumbprintSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Empty key identifier specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_EmptyCngKeyId { - get { - return ResourceManager.GetString("TCE_EmptyCngKeyId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Empty key identifier specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_EmptyCngKeyIdSysErr { - get { - return ResourceManager.GetString("TCE_EmptyCngKeyIdSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Empty Microsoft Cryptography API: Next Generation (CNG) provider name specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_EmptyCngName { - get { - return ResourceManager.GetString("TCE_EmptyCngName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Empty Microsoft Cryptography API: Next Generation (CNG) provider name specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_EmptyCngNameSysErr { - get { - return ResourceManager.GetString("TCE_EmptyCngNameSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Empty column encryption key specified.. - /// - internal static string TCE_EmptyColumnEncryptionKey { - get { - return ResourceManager.GetString("TCE_EmptyColumnEncryptionKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Empty key identifier specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_EmptyCspKeyId { - get { - return ResourceManager.GetString("TCE_EmptyCspKeyId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Empty key identifier specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_EmptyCspKeyIdSysErr { - get { - return ResourceManager.GetString("TCE_EmptyCspKeyIdSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Empty Microsoft cryptographic service provider (CSP) name specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_EmptyCspName { - get { - return ResourceManager.GetString("TCE_EmptyCspName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Empty Microsoft cryptographic service provider (CSP) name specified in column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_EmptyCspNameSysErr { - get { - return ResourceManager.GetString("TCE_EmptyCspNameSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Empty encrypted column encryption key specified.. - /// - internal static string TCE_EmptyEncryptedColumnEncryptionKey { - get { - return ResourceManager.GetString("TCE_EmptyEncryptedColumnEncryptionKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid key store provider name specified. Key store provider names cannot be null or empty.. - /// - internal static string TCE_EmptyProviderName { - get { - return ResourceManager.GetString("TCE_EmptyProviderName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You have specified the enclave attestation URL and attestation protocol in the connection string, but the SQL Server in use does not support enclave based computations - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details.. - /// - internal static string TCE_EnclaveComputationsNotSupported { - get { - return ResourceManager.GetString("TCE_EnclaveComputationsNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No enclave provider found for enclave type '{0}' and attestation protocol '{1}'. Please specify the correct attestation protocol in the connection string. . - /// - internal static string TCE_EnclaveProviderNotFound { - get { - return ResourceManager.GetString("TCE_EnclaveProviderNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Executing a query requires enclave computations, but the application configuration is missing the enclave provider section.. - /// - internal static string TCE_EnclaveProvidersNotConfiguredForEnclaveBasedQuery { - get { - return ResourceManager.GetString("TCE_EnclaveProvidersNotConfiguredForEnclaveBasedQuery", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You have specified the enclave attestation URL in the connection string, but the SQL Server did not return an enclave type. Please make sure the enclave type is correctly configured in your instance - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details.. - /// - internal static string TCE_EnclaveTypeNotReturned { - get { - return ResourceManager.GetString("TCE_EnclaveTypeNotReturned", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The enclave type '{0}' returned from the server is not supported.. - /// - internal static string TCE_EnclaveTypeNotSupported { - get { - return ResourceManager.GetString("TCE_EnclaveTypeNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Enclave type received from SQL Server is null or empty when executing a query requiring enclave computations.. - /// - internal static string TCE_EnclaveTypeNullForEnclaveBasedQuery { - get { - return ResourceManager.GetString("TCE_EnclaveTypeNullForEnclaveBasedQuery", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error encountered while generating package to be sent to enclave. Error message: {0}. - /// - internal static string TCE_ExceptionWhenGeneratingEnclavePackage { - get { - return ResourceManager.GetString("TCE_ExceptionWhenGeneratingEnclavePackage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Failed to encrypt byte package to be sent to the enclave. Error Message: {0} . - /// - internal static string TCE_FailedToEncryptRegisterRulesBytePackage { - get { - return ResourceManager.GetString("TCE_FailedToEncryptRegisterRulesBytePackage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. The buffer specified by argument '{0}' for method '{1}.{2}' has insufficient space.. - /// - internal static string TCE_InsufficientBuffer { - get { - return ResourceManager.GetString("TCE_InsufficientBuffer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified ciphertext's encryption algorithm version '{0}' does not match the expected encryption algorithm version '{1}'.. - /// - internal static string TCE_InvalidAlgorithmVersion { - get { - return ResourceManager.GetString("TCE_InvalidAlgorithmVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specified encrypted column encryption key contains an invalid encryption algorithm version '{0}'. Expected version is '{1}'.. - /// - internal static string TCE_InvalidAlgorithmVersionInEncryptedCEK { - get { - return ResourceManager.GetString("TCE_InvalidAlgorithmVersionInEncryptedCEK", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid attestation parameters specified by the enclave provider for enclave type '{0}'. Error occurred when converting the value '{1}' of parameter '{2}' to unsigned int. Error Message: {3}. - /// - internal static string TCE_InvalidAttestationParameterUnableToConvertToUnsignedInt { - get { - return ResourceManager.GetString("TCE_InvalidAttestationParameterUnableToConvertToUnsignedInt", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specified ciphertext has an invalid authentication tag.. - /// - internal static string TCE_InvalidAuthenticationTag { - get { - return ResourceManager.GetString("TCE_InvalidAuthenticationTag", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid certificate location '{0}' in certificate path '{1}'. Use the following format: <certificate location>{4}<certificate store>{4}<certificate thumbprint>, where <certificate location> is either '{2}' or '{3}'.. - /// - internal static string TCE_InvalidCertificateLocation { - get { - return ResourceManager.GetString("TCE_InvalidCertificateLocation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid certificate location '{0}' in certificate path '{1}'. Use the following format: <certificate location>{3}<certificate store>{3}<certificate thumbprint>, where <certificate location> is '{2}'.. - /// - internal static string TCE_InvalidCertificateLocation_Unix - { - get - { - return ResourceManager.GetString("TCE_InvalidCertificateLocation_Unix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid certificate location '{0}' in certificate path '{1}'. Use the following format: <certificate location>{4}<certificate store>{4}<certificate thumbprint>, where <certificate location> is either '{2}' or '{3}'.. - /// - internal static string TCE_InvalidCertificateLocationSysErr { - get { - return ResourceManager.GetString("TCE_InvalidCertificateLocationSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid certificate location '{0}' in certificate path '{1}'. Use the following format: <certificate location>{3}<certificate store>{3}<certificate thumbprint>, where <certificate location> is '{2}'.. - /// - internal static string TCE_InvalidCertificateLocationSysErr_Unix - { - get - { - return ResourceManager.GetString("TCE_InvalidCertificateLocationSysErr_Unix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid certificate path: '{0}'. Use the following format: <certificate location>{3}<certificate store>{3}<certificate thumbprint>, where <certificate location> is either '{1}' or '{2}'.. - /// - internal static string TCE_InvalidCertificatePath { - get { - return ResourceManager.GetString("TCE_InvalidCertificatePath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid certificate path: '{0}'. Use the following format: <certificate location>{2}<certificate store>{2}<certificate thumbprint>, where <certificate location> is '{1}'.. - /// - internal static string TCE_InvalidCertificatePath_Unix - { - get - { - return ResourceManager.GetString("TCE_InvalidCertificatePath_Unix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid certificate path: '{0}'. Use the following format: <certificate location>{3}<certificate store>{3}<certificate thumbprint>, where <certificate location> is either '{1}' or '{2}'.. - /// - internal static string TCE_InvalidCertificatePathSysErr { - get { - return ResourceManager.GetString("TCE_InvalidCertificatePathSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid certificate path: '{0}'. Use the following format: <certificate location>{2}<certificate store>{2}<certificate thumbprint>, where <certificate location> is '{1}'.. - /// - internal static string TCE_InvalidCertificatePathSysErr_Unix - { - get - { - return ResourceManager.GetString("TCE_InvalidCertificatePathSysErr_Unix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid certificate store '{0}' specified in certificate path '{1}'. Expected value: '{2}'.. - /// - internal static string TCE_InvalidCertificateStore { - get { - return ResourceManager.GetString("TCE_InvalidCertificateStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid certificate store '{0}' specified in certificate path '{1}'. Expected value: '{2}'.. - /// - internal static string TCE_InvalidCertificateStoreSysErr { - get { - return ResourceManager.GetString("TCE_InvalidCertificateStoreSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.. - /// - internal static string TCE_InvalidCiphertextLengthInEncryptedCEK { - get { - return ResourceManager.GetString("TCE_InvalidCiphertextLengthInEncryptedCEK", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specified ciphertext has an invalid size of {0} bytes, which is below the minimum {1} bytes required for decryption.. - /// - internal static string TCE_InvalidCipherTextSize { - get { - return ResourceManager.GetString("TCE_InvalidCipherTextSize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred while opening the Microsoft Cryptography API: Next Generation (CNG) key: '{0}'. Verify that the CNG provider name '{1}' is valid, installed on the machine, and the key '{2}' exists.. - /// - internal static string TCE_InvalidCngKey { - get { - return ResourceManager.GetString("TCE_InvalidCngKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. An error occurred while opening the Microsoft Cryptography API: Next Generation (CNG) key: '{0}'. Verify that the CNG provider name '{1}' is valid, installed on the machine, and the key '{2}' exists.. - /// - internal static string TCE_InvalidCngKeySysErr { - get { - return ResourceManager.GetString("TCE_InvalidCngKeySysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_InvalidCngPath { - get { - return ResourceManager.GetString("TCE_InvalidCngPath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid column master key path: '{0}'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_InvalidCngPathSysErr { - get { - return ResourceManager.GetString("TCE_InvalidCngPathSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid key identifier: '{0}'. Verify that the key identifier in column master key path: '{1}' is valid and exists in the CSP.. - /// - internal static string TCE_InvalidCspKeyId { - get { - return ResourceManager.GetString("TCE_InvalidCspKeyId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid key identifier: '{0}'. Verify that the key identifier in column master key path: '{1}' is valid and exists in the CSP.. - /// - internal static string TCE_InvalidCspKeyIdSysErr { - get { - return ResourceManager.GetString("TCE_InvalidCspKeyIdSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid Microsoft cryptographic service provider (CSP) name: '{0}'. Verify that the CSP provider name in column master key path: '{1}' is valid and installed on the machine.. - /// - internal static string TCE_InvalidCspName { - get { - return ResourceManager.GetString("TCE_InvalidCspName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid Microsoft cryptographic service provider (CSP) name: '{0}'. Verify that the CSP provider name in column master key path: '{1}' is valid and installed on the machine.. - /// - internal static string TCE_InvalidCspNameSysErr { - get { - return ResourceManager.GetString("TCE_InvalidCspNameSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_InvalidCspPath { - get { - return ResourceManager.GetString("TCE_InvalidCspPath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid column master key path: '{0}'. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{1}<Key Identifier>.. - /// - internal static string TCE_InvalidCspPathSysErr { - get { - return ResourceManager.GetString("TCE_InvalidCspPathSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid key store provider name '{0}'. '{1}' prefix is reserved for system key store providers.. - /// - internal static string TCE_InvalidCustomKeyStoreProviderName { - get { - return ResourceManager.GetString("TCE_InvalidCustomKeyStoreProviderName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. The given database id '{0}' is not valid. Error occurred when converting the database id to unsigned int. Error Message: {1}. - /// - internal static string TCE_InvalidDatabaseIdUnableToCastToUnsignedInt { - get { - return ResourceManager.GetString("TCE_InvalidDatabaseIdUnableToCastToUnsignedInt", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Error occurred when populating enclave metadata. The referenced column encryption key ordinal '{0}' is missing in the encryption metadata returned by SQL Server. Max ordinal is '{1}'. . - /// - internal static string TCE_InvalidEncryptionKeyOrdinalEnclaveMetadata { - get { - return ResourceManager.GetString("TCE_InvalidEncryptionKeyOrdinalEnclaveMetadata", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Error occurred when populating parameter metadata. The referenced column encryption key ordinal '{0}' is missing in the encryption metadata returned by SQL Server. Max ordinal is '{1}'. . - /// - internal static string TCE_InvalidEncryptionKeyOrdinalParameterMetadata { - get { - return ResourceManager.GetString("TCE_InvalidEncryptionKeyOrdinalParameterMetadata", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Encryption type '{1}' specified for the column in the database is either invalid or corrupted. Valid encryption types for algorithm '{0}' are: {2}.. - /// - internal static string TCE_InvalidEncryptionType { - get { - return ResourceManager.GetString("TCE_InvalidEncryptionType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid key encryption algorithm specified: '{0}'. Expected value: '{1}'.. - /// - internal static string TCE_InvalidKeyEncryptionAlgorithm { - get { - return ResourceManager.GetString("TCE_InvalidKeyEncryptionAlgorithm", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Invalid key encryption algorithm specified: '{0}'. Expected value: '{1}'.. - /// - internal static string TCE_InvalidKeyEncryptionAlgorithmSysErr { - get { - return ResourceManager.GetString("TCE_InvalidKeyEncryptionAlgorithmSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. The given key id '{0}' is not valid. Error occurred when converting the key id to unsigned short. Error Message: {1}. - /// - internal static string TCE_InvalidKeyIdUnableToCastToUnsignedShort { - get { - return ResourceManager.GetString("TCE_InvalidKeyIdUnableToCastToUnsignedShort", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The column encryption key has been successfully decrypted but its length: {1} does not match the length: {2} for algorithm '{0}'. Verify the encrypted value of the column encryption key in the database.. - /// - internal static string TCE_InvalidKeySize { - get { - return ResourceManager.GetString("TCE_InvalidKeySize", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid key store provider name: '{0}'. A key store provider name must denote either a system key store provider or a registered custom key store provider. Valid system key store provider names are: {1}. Valid (currently registered) custom key store provider names are: {2}. Please verify key store provider information in column master key definitions in the database, and verify all custom key store providers used in your application are registered properly.. - /// - internal static string TCE_InvalidKeyStoreProviderName { - get { - return ResourceManager.GetString("TCE_InvalidKeyStoreProviderName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified encrypted column encryption key signature does not match the signature computed with the column master key ({0}) in '{1}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.. - /// - internal static string TCE_InvalidSignature { - get { - return ResourceManager.GetString("TCE_InvalidSignature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.. - /// - internal static string TCE_InvalidSignatureInEncryptedCEK { - get { - return ResourceManager.GetString("TCE_InvalidSignatureInEncryptedCEK", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to decrypt a column encryption key using key store provider: '{0}'. Verify the properties of the column encryption key and its column master key in your database. The last 10 bytes of the encrypted column encryption key are: '{1}'.. - /// - internal static string TCE_KeyDecryptionFailed { - get { - return ResourceManager.GetString("TCE_KeyDecryptionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to decrypt a column encryption key using key store provider: '{0}'. The last 10 bytes of the encrypted column encryption key are: '{1}'.. - /// - internal static string TCE_KeyDecryptionFailedCertStore { - get { - return ResourceManager.GetString("TCE_KeyDecryptionFailedCertStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specified certificate path has {0} bytes, which exceeds maximum length of {1} bytes.. - /// - internal static string TCE_LargeCertificatePathLength { - get { - return ResourceManager.GetString("TCE_LargeCertificatePathLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Specified certificate path has {0} bytes, which exceeds maximum length of {1} bytes.. - /// - internal static string TCE_LargeCertificatePathLengthSysErr { - get { - return ResourceManager.GetString("TCE_LargeCertificatePathLengthSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Error occurred when parsing the results of '{0}'. The attestation information resultset is expected to contain only one row, but it contains multiple rows.. - /// - internal static string TCE_MultipleRowsReturnedForAttestationInfo { - get { - return ResourceManager.GetString("TCE_MultipleRowsReturnedForAttestationInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error occurred when generating enclave package. Attestation URL has not been specified in the connection string, but the query requires enclave computations. Enclave type is '{0}'. . - /// - internal static string TCE_NoAttestationUrlSpecifiedForEnclaveBasedQueryGeneratingEnclavePackage { - get { - return ResourceManager.GetString("TCE_NoAttestationUrlSpecifiedForEnclaveBasedQueryGeneratingEnclavePackage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error occurred when reading '{0}' resultset. Attestation URL has not been specified in the connection string, but the query requires enclave computations. Enclave type is '{1}'. . - /// - internal static string TCE_NoAttestationUrlSpecifiedForEnclaveBasedQuerySpDescribe { - get { - return ResourceManager.GetString("TCE_NoAttestationUrlSpecifiedForEnclaveBasedQuerySpDescribe", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} instance in use does not support column encryption.. - /// - internal static string TCE_NotSupportedByServer { - get { - return ResourceManager.GetString("TCE_NotSupportedByServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Null argument '{0}' specified when constructing an object of type '{1}'. '{0}' cannot be null.. - /// - internal static string TCE_NullArgumentInConstructorInternal { - get { - return ResourceManager.GetString("TCE_NullArgumentInConstructorInternal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Argument '{0}' cannot be null when executing method '{1}.{2}'.. - /// - internal static string TCE_NullArgumentInternal { - get { - return ResourceManager.GetString("TCE_NullArgumentInternal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate path cannot be null. Use the following format: <certificate location>{2}<certificate store>{2}<certificate thumbprint>, where <certificate location> is either '{0}' or '{1}'.. - /// - internal static string TCE_NullCertificatePath { - get { - return ResourceManager.GetString("TCE_NullCertificatePath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Certificate path cannot be null. Use the following format: <certificate location>{1}<certificate store>{1}<certificate thumbprint>, where <certificate location> is '{0}'.. - /// - internal static string TCE_NullCertificatePath_Unix - { - get - { - return ResourceManager.GetString("TCE_NullCertificatePath_Unix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Certificate path cannot be null. Use the following format: <certificate location>{2}<certificate store>{2}<certificate thumbprint>, where <certificate location> is either '{0}' or '{1}'.. - /// - internal static string TCE_NullCertificatePathSysErr { - get { - return ResourceManager.GetString("TCE_NullCertificatePathSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Certificate path cannot be null. Use the following format: <certificate location>{1}<certificate store>{1}<certificate thumbprint>, where <certificate location> is '{0}'.. - /// - internal static string TCE_NullCertificatePathSysErr_Unix - { - get - { - return ResourceManager.GetString("TCE_NullCertificatePathSysErr_Unix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Ciphertext value cannot be null.. - /// - internal static string TCE_NullCipherText { - get { - return ResourceManager.GetString("TCE_NullCipherText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Column master key path cannot be null. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{0}<Key Identifier>.. - /// - internal static string TCE_NullCngPath { - get { - return ResourceManager.GetString("TCE_NullCngPath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Column master key path cannot be null. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{0}<Key Identifier>.. - /// - internal static string TCE_NullCngPathSysErr { - get { - return ResourceManager.GetString("TCE_NullCngPathSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Encryption algorithm cannot be null. Valid algorithms are: {0}.. - /// - internal static string TCE_NullColumnEncryptionAlgorithm { - get { - return ResourceManager.GetString("TCE_NullColumnEncryptionAlgorithm", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Column encryption key cannot be null.. - /// - internal static string TCE_NullColumnEncryptionKey { - get { - return ResourceManager.GetString("TCE_NullColumnEncryptionKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Column encryption key cannot be null.. - /// - internal static string TCE_NullColumnEncryptionKeySysErr { - get { - return ResourceManager.GetString("TCE_NullColumnEncryptionKeySysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Column master key path cannot be null. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{0}<Key Identifier>.. - /// - internal static string TCE_NullCspPath { - get { - return ResourceManager.GetString("TCE_NullCspPath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Column master key path cannot be null. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{0}<Key Identifier>.. - /// - internal static string TCE_NullCspPathSysErr { - get { - return ResourceManager.GetString("TCE_NullCspPathSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Column encryption key store provider dictionary cannot be null. Expecting a non-null value.. - /// - internal static string TCE_NullCustomKeyStoreProviderDictionary { - get { - return ResourceManager.GetString("TCE_NullCustomKeyStoreProviderDictionary", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Enclave package is null during execution of an enclave based query. Enclave type is '{0}' and enclaveAttestationUrl is '{1}'.. - /// - internal static string TCE_NullEnclavePackageForEnclaveBasedQuery { - get { - return ResourceManager.GetString("TCE_NullEnclavePackageForEnclaveBasedQuery", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Enclave session is null during query execution. Enclave type is '{0}' and enclaveAttestationUrl is '{1}'.. - /// - internal static string TCE_NullEnclaveSessionDuringQueryExecution { - get { - return ResourceManager.GetString("TCE_NullEnclaveSessionDuringQueryExecution", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to communicate with the enclave. Null enclave session information received from the enclave provider. Enclave type is '{0}' and enclave attestation URL is '{1}'.. - /// - internal static string TCE_NullEnclaveSessionReturnedFromProvider { - get { - return ResourceManager.GetString("TCE_NullEnclaveSessionReturnedFromProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Encrypted column encryption key cannot be null.. - /// - internal static string TCE_NullEncryptedColumnEncryptionKey { - get { - return ResourceManager.GetString("TCE_NullEncryptedColumnEncryptionKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Key encryption algorithm cannot be null.. - /// - internal static string TCE_NullKeyEncryptionAlgorithm { - get { - return ResourceManager.GetString("TCE_NullKeyEncryptionAlgorithm", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Key encryption algorithm cannot be null.. - /// - internal static string TCE_NullKeyEncryptionAlgorithmSysErr { - get { - return ResourceManager.GetString("TCE_NullKeyEncryptionAlgorithmSysErr", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Plaintext value cannot be null.. - /// - internal static string TCE_NullPlainText { - get { - return ResourceManager.GetString("TCE_NullPlainText", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Null reference specified for key store provider '{0}'. Expecting a non-null value.. - /// - internal static string TCE_NullProviderValue { - get { - return ResourceManager.GetString("TCE_NullProviderValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. Failed to serialize keys to be sent to the enclave. The start offset specified by argument '{0}' for method {1}.{2} is out of bounds.. - /// - internal static string TCE_OffsetOutOfBounds { - get { - return ResourceManager.GetString("TCE_OffsetOutOfBounds", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to decrypt parameter '{0}'.. - /// - internal static string TCE_ParamDecryptionFailed { - get { - return ResourceManager.GetString("TCE_ParamDecryptionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to encrypt parameter '{0}'.. - /// - internal static string TCE_ParamEncryptionFailed { - get { - return ResourceManager.GetString("TCE_ParamEncryptionFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Metadata for parameter '{1}' in statement or procedure '{2}' is missing in resultset returned by {0}.. - /// - internal static string TCE_ParamEncryptionMetaDataMissing { - get { - return ResourceManager.GetString("TCE_ParamEncryptionMetaDataMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot set {0} for {3} '{1}' because encryption is not enabled for the statement or procedure '{2}'.. - /// - internal static string TCE_ParamInvalidForceColumnEncryptionSetting { - get { - return ResourceManager.GetString("TCE_ParamInvalidForceColumnEncryptionSetting", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot execute statement or procedure '{1}' because {2} was set for {3} '{0}' and the database expects this parameter to be sent as plaintext. This may be due to a configuration error.. - /// - internal static string TCE_ParamUnExpectedEncryptionMetadata { - get { - return ResourceManager.GetString("TCE_ParamUnExpectedEncryptionMetadata", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. Metadata for parameters for command '{1}' in a batch is missing in the resultset returned by {0}.. - /// - internal static string TCE_ProcEncryptionMetaDataMissing { - get { - return ResourceManager.GetString("TCE_ProcEncryptionMetaDataMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Retrieving encrypted column '{0}' with {1} is not supported.. - /// - internal static string TCE_SequentialAccessNotSupportedOnEncryptedColumn { - get { - return ResourceManager.GetString("TCE_SequentialAccessNotSupportedOnEncryptedColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal Error. SqlColumnEncryptionEnclaveProviderName cannot be null or empty.. - /// - internal static string TCE_SqlColumnEncryptionEnclaveProviderNameCannotBeEmpty { - get { - return ResourceManager.GetString("TCE_SqlColumnEncryptionEnclaveProviderNameCannotBeEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Column encryption setting for the command. Overrides the connection level default.. - /// - internal static string TCE_SqlCommand_ColumnEncryptionSetting { - get { - return ResourceManager.GetString("TCE_SqlCommand_ColumnEncryptionSetting", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Defines the time-to-live of entries in the column encryption key cache.. - /// - internal static string TCE_SqlConnection_ColumnEncryptionKeyCacheTtl { - get { - return ResourceManager.GetString("TCE_SqlConnection_ColumnEncryptionKeyCacheTtl", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Defines whether query metadata caching is enabled.. - /// - internal static string TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled { - get { - return ResourceManager.GetString("TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Dictionary object containing SQL Server names and their trusted column master key paths.. - /// - internal static string TCE_SqlConnection_TrustedColumnMasterKeyPaths { - get { - return ResourceManager.GetString("TCE_SqlConnection_TrustedColumnMasterKeyPaths", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Forces parameter to be encrypted before sending sensitive data to server. . - /// - internal static string TCE_SqlParameter_ForceColumnEncryption { - get { - return ResourceManager.GetString("TCE_SqlParameter_ForceColumnEncryption", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Retrieving encrypted column '{0}' as a {1} is not supported.. - /// - internal static string TCE_StreamNotSupportOnEncryptedColumn { - get { - return ResourceManager.GetString("TCE_StreamNotSupportOnEncryptedColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to establish secure channel. Error Message: {0}. - /// - internal static string TCE_UnableToEstablishSecureChannel { - get { - return ResourceManager.GetString("TCE_UnableToEstablishSecureChannel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to verify a column master key signature. Error message: {0} . - /// - internal static string TCE_UnableToVerifyColumnMasterKeySignature { - get { - return ResourceManager.GetString("TCE_UnableToVerifyColumnMasterKeySignature", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. The result returned by '{0}' is invalid. The attestation information resultset is missing for enclave type '{1}'. . - /// - internal static string TCE_UnexpectedDescribeParamFormatAttestationInfo { - get { - return ResourceManager.GetString("TCE_UnexpectedDescribeParamFormatAttestationInfo", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Internal error. The result returned by '{0}' is invalid. The parameter metadata resultset is missing.. - /// - internal static string TCE_UnexpectedDescribeParamFormatParameterMetadata { - get { - return ResourceManager.GetString("TCE_UnexpectedDescribeParamFormatParameterMetadata", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Encryption algorithm '{0}' for the column in the database is either invalid or corrupted. Valid algorithms are: {1}.. - /// - internal static string TCE_UnknownColumnEncryptionAlgorithm { - get { - return ResourceManager.GetString("TCE_UnknownColumnEncryptionAlgorithm", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Encryption algorithm id '{0}' for the column in the database is either invalid or corrupted. Valid encryption algorithm ids are: {1}.. - /// - internal static string TCE_UnknownColumnEncryptionAlgorithmId { - get { - return ResourceManager.GetString("TCE_UnknownColumnEncryptionAlgorithmId", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to decrypt a column encryption key. Invalid key store provider name: '{0}'. A key store provider name must denote either a system key store provider or a registered custom key store provider. Valid system key store provider names are: {1}. Valid (currently registered) custom key store provider names are: {2}. Please verify key store provider information in column master key definitions in the database, and verify all custom key store providers used in your application are registered properly.. - /// - internal static string TCE_UnrecognizedKeyStoreProviderName { - get { - return ResourceManager.GetString("TCE_UnrecognizedKeyStoreProviderName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Encryption and decryption of data type '{0}' is not supported.. - /// - internal static string TCE_UnsupportedDatatype { - get { - return ResourceManager.GetString("TCE_UnsupportedDatatype", resourceCulture); + return ResourceManager.GetString("TCE_InvalidKeyEncryptionAlgorithm", resourceCulture); } } /// - /// Looks up a localized string similar to Normalization version '{0}' received from {2} is not supported. Valid normalization versions are: {1}.. + /// Looks up a localized string similar to Internal error. Invalid key encryption algorithm specified: '{0}'. Expected value: '{1}'.. /// - internal static string TCE_UnsupportedNormalizationVersion { + internal static string TCE_InvalidKeyEncryptionAlgorithmSysErr { get { - return ResourceManager.GetString("TCE_UnsupportedNormalizationVersion", resourceCulture); + return ResourceManager.GetString("TCE_InvalidKeyEncryptionAlgorithmSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Column master key path '{0}' received from server '{1}' is not a trusted key path.. + /// Looks up a localized string similar to Internal Error. The given key id '{0}' is not valid. Error occurred when converting the key id to unsigned short. Error Message: {1}. /// - internal static string TCE_UntrustedKeyPath { + internal static string TCE_InvalidKeyIdUnableToCastToUnsignedShort { get { - return ResourceManager.GetString("TCE_UntrustedKeyPath", resourceCulture); + return ResourceManager.GetString("TCE_InvalidKeyIdUnableToCastToUnsignedShort", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot encrypt. Encrypting resulted in {0} bytes of ciphertext which exceeds the maximum allowed limit of {1} bytes. The specified plaintext value is likely too large (plaintext size is: {2} bytes).. + /// Looks up a localized string similar to The column encryption key has been successfully decrypted but its length: {1} does not match the length: {2} for algorithm '{0}'. Verify the encrypted value of the column encryption key in the database.. /// - internal static string TCE_VeryLargeCiphertext { + internal static string TCE_InvalidKeySize { get { - return ResourceManager.GetString("TCE_VeryLargeCiphertext", resourceCulture); + return ResourceManager.GetString("TCE_InvalidKeySize", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to check if the enclave is running in the production mode. Contact Customer Support Services.. + /// Looks up a localized string similar to Invalid key store provider name: '{0}'. A key store provider name must denote either a system key store provider or a registered custom key store provider. Valid system key store provider names are: {1}. Valid (currently registered) custom key store provider names are: {2}. Please verify key store provider information in column master key definitions in the database, and verify all custom key store providers used in your application are registered properly.. /// - internal static string VerifyEnclaveDebuggable { + internal static string TCE_InvalidKeyStoreProviderName { get { - return ResourceManager.GetString("VerifyEnclaveDebuggable", resourceCulture); + return ResourceManager.GetString("TCE_InvalidKeyStoreProviderName", resourceCulture); } } /// - /// Looks up a localized string similar to Could not verify enclave policy due to a difference between the expected and actual values of the policy on property '{0}'. Actual: '{1}', Expected: '{2}' - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details.. + /// Looks up a localized string similar to The specified encrypted column encryption key signature does not match the signature computed with the column master key ({0}) in '{1}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.. /// - internal static string VerifyEnclavePolicyFailedFormat { + internal static string TCE_InvalidSignature { get { - return ResourceManager.GetString("VerifyEnclavePolicyFailedFormat", resourceCulture); + return ResourceManager.GetString("TCE_InvalidSignature", resourceCulture); } } /// - /// Looks up a localized string similar to Signature verification of the enclave report failed. The report signature does not match the signature computed using the HGS root certificate. Verify the DNS mapping for the endpoint - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details. If correct, contact Customer Support Services.. + /// Looks up a localized string similar to The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.. /// - internal static string VerifyEnclaveReportFailed { + internal static string TCE_InvalidSignatureInEncryptedCEK { get { - return ResourceManager.GetString("VerifyEnclaveReportFailed", resourceCulture); + return ResourceManager.GetString("TCE_InvalidSignatureInEncryptedCEK", resourceCulture); } } /// - /// Looks up a localized string similar to The enclave report received from SQL Server is not in the correct format. Contact Customer Support Services.. + /// Looks up a localized string similar to Failed to decrypt a column encryption key using key store provider: '{0}'. Verify the properties of the column encryption key and its column master key in your database. The last 10 bytes of the encrypted column encryption key are: '{1}'.. /// - internal static string VerifyEnclaveReportFormatFailed { + internal static string TCE_KeyDecryptionFailed { get { - return ResourceManager.GetString("VerifyEnclaveReportFormatFailed", resourceCulture); + return ResourceManager.GetString("TCE_KeyDecryptionFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to build a chain of trust between the enclave host's health report and the HGS root certificate for attestation URL '{0}' with status: '{1}'. Verify the attestation URL matches the URL configured on the SQL Server - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details. If both the client and SQL Server use the same attestation service, contact Customer Support Services.. + /// Looks up a localized string similar to Failed to decrypt a column encryption key using key store provider: '{0}'. The last 10 bytes of the encrypted column encryption key are: '{1}'.. /// - internal static string VerifyHealthCertificateChainFormat { + internal static string TCE_KeyDecryptionFailedCertStore { get { - return ResourceManager.GetString("VerifyHealthCertificateChainFormat", resourceCulture); + return ResourceManager.GetString("TCE_KeyDecryptionFailedCertStore", resourceCulture); } } /// - /// Looks up a localized string similar to The value of attribute '{0}' should be '{1}' or '{2}'.. + /// Looks up a localized string similar to Specified certificate path has {0} bytes, which exceeds maximum length of {1} bytes.. /// - internal static string Xml_AttributeValues { + internal static string TCE_LargeCertificatePathLength { get { - return ResourceManager.GetString("Xml_AttributeValues", resourceCulture); + return ResourceManager.GetString("TCE_LargeCertificatePathLength", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot convert '{0}' to type '{1}'.. + /// Looks up a localized string similar to Internal error. Specified certificate path has {0} bytes, which exceeds maximum length of {1} bytes.. /// - internal static string Xml_CannotConvert { + internal static string TCE_LargeCertificatePathLengthSysErr { get { - return ResourceManager.GetString("Xml_CannotConvert", resourceCulture); + return ResourceManager.GetString("TCE_LargeCertificatePathLengthSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Unable to proceed with deserialization. Data does not implement IXMLSerializable, therefore polymorphism is not supported.. + /// Looks up a localized string similar to Internal error. Error occurred when parsing the results of '{0}'. The attestation information resultset is expected to contain only one row, but it contains multiple rows.. /// - internal static string Xml_CanNotDeserializeObjectType { + internal static string TCE_MultipleRowsReturnedForAttestationInfo { get { - return ResourceManager.GetString("Xml_CanNotDeserializeObjectType", resourceCulture); + return ResourceManager.GetString("TCE_MultipleRowsReturnedForAttestationInfo", resourceCulture); } } /// - /// Looks up a localized string similar to DataSet cannot instantiate an abstract ComplexType for the node {0}.. + /// Looks up a localized string similar to Error occurred when generating enclave package. Attestation URL has not been specified in the connection string, but the query requires enclave computations. Enclave type is '{0}'. . /// - internal static string Xml_CannotInstantiateAbstract { + internal static string TCE_NoAttestationUrlSpecifiedForEnclaveBasedQueryGeneratingEnclavePackage { get { - return ResourceManager.GetString("Xml_CannotInstantiateAbstract", resourceCulture); + return ResourceManager.GetString("TCE_NoAttestationUrlSpecifiedForEnclaveBasedQueryGeneratingEnclavePackage", resourceCulture); } } /// - /// Looks up a localized string similar to DataSet doesn't allow the circular reference in the ComplexType named '{0}'.. + /// Looks up a localized string similar to Error occurred when reading '{0}' resultset. Attestation URL has not been specified in the connection string, but the query requires enclave computations. Enclave type is '{1}'. . /// - internal static string Xml_CircularComplexType { + internal static string TCE_NoAttestationUrlSpecifiedForEnclaveBasedQuerySpDescribe { get { - return ResourceManager.GetString("Xml_CircularComplexType", resourceCulture); + return ResourceManager.GetString("TCE_NoAttestationUrlSpecifiedForEnclaveBasedQuerySpDescribe", resourceCulture); } } /// - /// Looks up a localized string similar to Column name '{0}' is defined for different mapping types.. + /// Looks up a localized string similar to {0} instance in use does not support column encryption.. /// - internal static string Xml_ColumnConflict { + internal static string TCE_NotSupportedByServer { get { - return ResourceManager.GetString("Xml_ColumnConflict", resourceCulture); + return ResourceManager.GetString("TCE_NotSupportedByServer", resourceCulture); } } /// - /// Looks up a localized string similar to DataTable does not support schema inference from Xml.. + /// Looks up a localized string similar to Internal Error. Null argument '{0}' specified when constructing an object of type '{1}'. '{0}' cannot be null.. /// - internal static string Xml_DataTableInferenceNotSupported { + internal static string TCE_NullArgumentInConstructorInternal { get { - return ResourceManager.GetString("Xml_DataTableInferenceNotSupported", resourceCulture); + return ResourceManager.GetString("TCE_NullArgumentInConstructorInternal", resourceCulture); } } /// - /// Looks up a localized string similar to Data type not defined.. + /// Looks up a localized string similar to Internal Error. Argument '{0}' cannot be null when executing method '{1}.{2}'.. /// - internal static string Xml_DatatypeNotDefined { + internal static string TCE_NullArgumentInternal { get { - return ResourceManager.GetString("Xml_DatatypeNotDefined", resourceCulture); + return ResourceManager.GetString("TCE_NullArgumentInternal", resourceCulture); } } /// - /// Looks up a localized string similar to The constraint name {0} is already used in the schema.. + /// Looks up a localized string similar to Certificate path cannot be null. Use the following format: <certificate location>{2}<certificate store>{2}<certificate thumbprint>, where <certificate location> is either '{0}' or '{1}'.. /// - internal static string Xml_DuplicateConstraint { + internal static string TCE_NullCertificatePath { get { - return ResourceManager.GetString("Xml_DuplicateConstraint", resourceCulture); + return ResourceManager.GetString("TCE_NullCertificatePath", resourceCulture); } } /// - /// Looks up a localized string similar to DataSet will not serialize types that implement IDynamicMetaObjectProvider but do not also implement IXmlSerializable.. + /// Looks up a localized string similar to Certificate path cannot be null. Use the following format: <certificate location>{1}<certificate store>{1}<certificate thumbprint>, where <certificate location> is '{0}'.. /// - internal static string Xml_DynamicWithoutXmlSerializable { + internal static string TCE_NullCertificatePath_Unix { get { - return ResourceManager.GetString("Xml_DynamicWithoutXmlSerializable", resourceCulture); + return ResourceManager.GetString("TCE_NullCertificatePath_Unix", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot find ElementType name='{0}'.. + /// Looks up a localized string similar to Internal error. Certificate path cannot be null. Use the following format: <certificate location>{2}<certificate store>{2}<certificate thumbprint>, where <certificate location> is either '{0}' or '{1}'.. /// - internal static string Xml_ElementTypeNotFound { + internal static string TCE_NullCertificatePathSysErr { get { - return ResourceManager.GetString("Xml_ElementTypeNotFound", resourceCulture); + return ResourceManager.GetString("TCE_NullCertificatePathSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to DataSet cannot expand entities. Use XmlValidatingReader and set the EntityHandling property accordingly.. + /// Looks up a localized string similar to Internal error. Certificate path cannot be null. Use the following format: <certificate location>{1}<certificate store>{1}<certificate thumbprint>, where <certificate location> is '{0}'.. /// - internal static string Xml_FoundEntity { + internal static string TCE_NullCertificatePathSysErr_Unix { get { - return ResourceManager.GetString("Xml_FoundEntity", resourceCulture); + return ResourceManager.GetString("TCE_NullCertificatePathSysErr_Unix", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid XPath selection inside field node. Cannot find: {0}.. + /// Looks up a localized string similar to Internal error. Ciphertext value cannot be null.. /// - internal static string Xml_InvalidField { + internal static string TCE_NullCipherText { get { - return ResourceManager.GetString("Xml_InvalidField", resourceCulture); + return ResourceManager.GetString("TCE_NullCipherText", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid 'Key' node inside constraint named: {0}.. + /// Looks up a localized string similar to Column master key path cannot be null. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{0}<Key Identifier>.. /// - internal static string Xml_InvalidKey { + internal static string TCE_NullCngPath { get { - return ResourceManager.GetString("Xml_InvalidKey", resourceCulture); + return ResourceManager.GetString("TCE_NullCngPath", resourceCulture); } } /// - /// Looks up a localized string similar to Prefix '{0}' is not valid, because it contains special characters.. + /// Looks up a localized string similar to Internal error. Column master key path cannot be null. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation (CNG) provider: <CNG Provider Name>{0}<Key Identifier>.. /// - internal static string Xml_InvalidPrefix { + internal static string TCE_NullCngPathSysErr { get { - return ResourceManager.GetString("Xml_InvalidPrefix", resourceCulture); + return ResourceManager.GetString("TCE_NullCngPathSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid XPath selection inside selector node: {0}.. + /// Looks up a localized string similar to Internal error. Encryption algorithm cannot be null. Valid algorithms are: {0}.. /// - internal static string Xml_InvalidSelector { + internal static string TCE_NullColumnEncryptionAlgorithm { get { - return ResourceManager.GetString("Xml_InvalidSelector", resourceCulture); + return ResourceManager.GetString("TCE_NullColumnEncryptionAlgorithm", resourceCulture); } } /// - /// Looks up a localized string similar to IsDataSet attribute is missing in input Schema.. + /// Looks up a localized string similar to Column encryption key cannot be null.. /// - internal static string Xml_IsDataSetAttributeMissingInSchema { + internal static string TCE_NullColumnEncryptionKey { get { - return ResourceManager.GetString("Xml_IsDataSetAttributeMissingInSchema", resourceCulture); + return ResourceManager.GetString("TCE_NullColumnEncryptionKey", resourceCulture); } } /// - /// Looks up a localized string similar to Duplicated declaration '{0}'.. + /// Looks up a localized string similar to Internal error. Column encryption key cannot be null.. /// - internal static string Xml_MergeDuplicateDeclaration { + internal static string TCE_NullColumnEncryptionKeySysErr { get { - return ResourceManager.GetString("Xml_MergeDuplicateDeclaration", resourceCulture); + return ResourceManager.GetString("TCE_NullColumnEncryptionKeySysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid Relation definition: different length keys.. + /// Looks up a localized string similar to Column master key path cannot be null. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{0}<Key Identifier>.. /// - internal static string Xml_MismatchKeyLength { + internal static string TCE_NullCspPath { get { - return ResourceManager.GetString("Xml_MismatchKeyLength", resourceCulture); + return ResourceManager.GetString("TCE_NullCspPath", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid {0} syntax: missing required '{1}' attribute.. + /// Looks up a localized string similar to Internal error. Column master key path cannot be null. Use the following format for a key stored in a Microsoft cryptographic service provider (CSP): <CSP Provider Name>{0}<Key Identifier>.. /// - internal static string Xml_MissingAttribute { + internal static string TCE_NullCspPathSysErr { get { - return ResourceManager.GetString("Xml_MissingAttribute", resourceCulture); + return ResourceManager.GetString("TCE_NullCspPathSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Missing '{0}' part in '{1}' constraint named '{2}'.. + /// Looks up a localized string similar to Column encryption key store provider dictionary cannot be null. Expecting a non-null value.. /// - internal static string Xml_MissingRefer { + internal static string TCE_NullCustomKeyStoreProviderDictionary { get { - return ResourceManager.GetString("Xml_MissingRefer", resourceCulture); + return ResourceManager.GetString("TCE_NullCustomKeyStoreProviderDictionary", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot load diffGram. The 'sql' node is missing.. + /// Looks up a localized string similar to Internal Error. Enclave package is null during execution of an enclave based query. Enclave type is '{0}' and enclaveAttestationUrl is '{1}'.. /// - internal static string Xml_MissingSQL { + internal static string TCE_NullEnclavePackageForEnclaveBasedQuery { get { - return ResourceManager.GetString("Xml_MissingSQL", resourceCulture); + return ResourceManager.GetString("TCE_NullEnclavePackageForEnclaveBasedQuery", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot load diffGram. Table '{0}' is missing in the destination dataset.. + /// Looks up a localized string similar to Internal Error. Enclave session is null during query execution. Enclave type is '{0}' and enclaveAttestationUrl is '{1}'.. /// - internal static string Xml_MissingTable { + internal static string TCE_NullEnclaveSessionDuringQueryExecution { get { - return ResourceManager.GetString("Xml_MissingTable", resourceCulture); + return ResourceManager.GetString("TCE_NullEnclaveSessionDuringQueryExecution", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot proceed with serializing DataTable '{0}'. It contains a DataRow which has multiple parent rows on the same Foreign Key.. + /// Looks up a localized string similar to Unable to communicate with the enclave. Null enclave session information received from the enclave provider. Enclave type is '{0}' and enclave attestation URL is '{1}'.. /// - internal static string Xml_MultipleParentRows { + internal static string TCE_NullEnclaveSessionReturnedFromProvider { get { - return ResourceManager.GetString("Xml_MultipleParentRows", resourceCulture); + return ResourceManager.GetString("TCE_NullEnclaveSessionReturnedFromProvider", resourceCulture); } } /// - /// Looks up a localized string similar to An error occurred with the multiple target converter while writing an Xml Schema. A null or empty string was returned.. + /// Looks up a localized string similar to Internal error. Encrypted column encryption key cannot be null.. /// - internal static string Xml_MultipleTargetConverterEmpty { + internal static string TCE_NullEncryptedColumnEncryptionKey { get { - return ResourceManager.GetString("Xml_MultipleTargetConverterEmpty", resourceCulture); + return ResourceManager.GetString("TCE_NullEncryptedColumnEncryptionKey", resourceCulture); } } /// - /// Looks up a localized string similar to An error occurred with the multiple target converter while writing an Xml Schema. See the inner exception for details.. + /// Looks up a localized string similar to Key encryption algorithm cannot be null.. /// - internal static string Xml_MultipleTargetConverterError { + internal static string TCE_NullKeyEncryptionAlgorithm { get { - return ResourceManager.GetString("Xml_MultipleTargetConverterError", resourceCulture); + return ResourceManager.GetString("TCE_NullKeyEncryptionAlgorithm", resourceCulture); } } /// - /// Looks up a localized string similar to Circular reference in self-nested table '{0}'.. + /// Looks up a localized string similar to Internal error. Key encryption algorithm cannot be null.. /// - internal static string Xml_NestedCircular { + internal static string TCE_NullKeyEncryptionAlgorithmSysErr { get { - return ResourceManager.GetString("Xml_NestedCircular", resourceCulture); + return ResourceManager.GetString("TCE_NullKeyEncryptionAlgorithmSysErr", resourceCulture); } } /// - /// Looks up a localized string similar to Type '{0}' does not implement IXmlSerializable interface therefore can not proceed with serialization.. + /// Looks up a localized string similar to Internal error. Plaintext value cannot be null.. /// - internal static string Xml_PolymorphismNotSupported { + internal static string TCE_NullPlainText { get { - return ResourceManager.GetString("Xml_PolymorphismNotSupported", resourceCulture); + return ResourceManager.GetString("TCE_NullPlainText", resourceCulture); } } /// - /// Looks up a localized string similar to Child table key is missing in relation '{0}'.. + /// Looks up a localized string similar to Null reference specified for key store provider '{0}'. Expecting a non-null value.. /// - internal static string Xml_RelationChildKeyMissing { + internal static string TCE_NullProviderValue { get { - return ResourceManager.GetString("Xml_RelationChildKeyMissing", resourceCulture); + return ResourceManager.GetString("TCE_NullProviderValue", resourceCulture); } } /// - /// Looks up a localized string similar to Child table name is missing in relation '{0}'.. + /// Looks up a localized string similar to Internal Error. Failed to serialize keys to be sent to the enclave. The start offset specified by argument '{0}' for method {1}.{2} is out of bounds.. /// - internal static string Xml_RelationChildNameMissing { + internal static string TCE_OffsetOutOfBounds { get { - return ResourceManager.GetString("Xml_RelationChildNameMissing", resourceCulture); + return ResourceManager.GetString("TCE_OffsetOutOfBounds", resourceCulture); } } /// - /// Looks up a localized string similar to Parent table name is missing in relation '{0}'.. + /// Looks up a localized string similar to Failed to decrypt parameter '{0}'.. /// - internal static string Xml_RelationParentNameMissing { + internal static string TCE_ParamDecryptionFailed { get { - return ResourceManager.GetString("Xml_RelationParentNameMissing", resourceCulture); + return ResourceManager.GetString("TCE_ParamDecryptionFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Parent table key is missing in relation '{0}'.. + /// Looks up a localized string similar to Failed to encrypt parameter '{0}'.. /// - internal static string Xml_RelationTableKeyMissing { + internal static string TCE_ParamEncryptionFailed { get { - return ResourceManager.GetString("Xml_RelationTableKeyMissing", resourceCulture); + return ResourceManager.GetString("TCE_ParamEncryptionFailed", resourceCulture); } } /// - /// Looks up a localized string similar to DataSet doesn't support 'union' or 'list' as simpleType.. + /// Looks up a localized string similar to Internal error. Metadata for parameter '{1}' in statement or procedure '{2}' is missing in resultset returned by {0}.. /// - internal static string Xml_SimpleTypeNotSupported { + internal static string TCE_ParamEncryptionMetaDataMissing { get { - return ResourceManager.GetString("Xml_SimpleTypeNotSupported", resourceCulture); + return ResourceManager.GetString("TCE_ParamEncryptionMetaDataMissing", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot determine the DataSet Element. IsDataSet attribute exist more than once.. + /// Looks up a localized string similar to Cannot set {0} for {3} '{1}' because encryption is not enabled for the statement or procedure '{2}'.. /// - internal static string Xml_TooManyIsDataSetAttributeInSchema { + internal static string TCE_ParamInvalidForceColumnEncryptionSetting { get { - return ResourceManager.GetString("Xml_TooManyIsDataSetAttributeInSchema", resourceCulture); + return ResourceManager.GetString("TCE_ParamInvalidForceColumnEncryptionSetting", resourceCulture); } } /// - /// Looks up a localized string similar to Undefined data type: '{0}'.. + /// Looks up a localized string similar to Cannot execute statement or procedure '{1}' because {2} was set for {3} '{0}' and the database expects this parameter to be sent as plaintext. This may be due to a configuration error.. /// - internal static string Xml_UndefinedDatatype { + internal static string TCE_ParamUnExpectedEncryptionMetadata { get { - return ResourceManager.GetString("Xml_UndefinedDatatype", resourceCulture); + return ResourceManager.GetString("TCE_ParamUnExpectedEncryptionMetadata", resourceCulture); } } /// - /// Looks up a localized string similar to Value '{1}' is invalid for attribute '{0}'.. + /// Looks up a localized string similar to Internal error. Metadata for parameters for command '{1}' in a batch is missing in the resultset returned by {0}.. /// - internal static string Xml_ValueOutOfRange { + internal static string TCE_ProcEncryptionMetaDataMissing { get { - return ResourceManager.GetString("Xml_ValueOutOfRange", resourceCulture); + return ResourceManager.GetString("TCE_ProcEncryptionMetaDataMissing", resourceCulture); } } /// - /// Looks up a localized string similar to Could not find an empty slot in the connection pool.. + /// Looks up a localized string similar to Retrieving encrypted column '{0}' with {1} is not supported.. /// - internal static string SQL_ConnectionPoolNoEmptySlot { + internal static string TCE_SequentialAccessNotSupportedOnEncryptedColumn { get { - return ResourceManager.GetString("SQL_ConnectionPoolNoEmptySlot", resourceCulture); + return ResourceManager.GetString("TCE_SequentialAccessNotSupportedOnEncryptedColumn", resourceCulture); } } /// - /// Looks up a localized string similar to The connection pool has been shut down.. + /// Looks up a localized string similar to Column encryption setting for the command. Overrides the connection level default.. /// - internal static string SQL_ConnectionPoolShutDown { + internal static string TCE_SqlCommand_ColumnEncryptionSetting { get { - return ResourceManager.GetString("SQL_ConnectionPoolShutDown", resourceCulture); + return ResourceManager.GetString("TCE_SqlCommand_ColumnEncryptionSetting", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot get RequiredLength when HasDataLength is false.. + /// Looks up a localized string similar to Defines the time-to-live of entries in the column encryption key cache.. /// - internal static string SQL_Packet_RequiredLengthUnavailable { + internal static string TCE_SqlConnection_ColumnEncryptionKeyCacheTtl { get { - return ResourceManager.GetString("SQL_Packet_RequiredLengthUnavailable", resourceCulture); + return ResourceManager.GetString("TCE_SqlConnection_ColumnEncryptionKeyCacheTtl", resourceCulture); } } /// - /// Looks up a localized string similar to Partial packet cannot be non-null when setting to non-null.. + /// Looks up a localized string similar to Defines whether query metadata caching is enabled.. /// - internal static string SQL_Multiplexer_PartialPacketNotNull { + internal static string TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled { get { - return ResourceManager.GetString("SQL_Multiplexer_PartialPacketNotNull", resourceCulture); + return ResourceManager.GetString("TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled", resourceCulture); } } /// - /// Looks up a localized string similar to MultiplexPackets cannot return both consumePartialPacket and consumeInputDirectly.. + /// Looks up a localized string similar to Dictionary object containing SQL Server names and their trusted column master key paths.. /// - internal static string SQL_Multiplexer_ConflictingConsumeModes { + internal static string TCE_SqlConnection_TrustedColumnMasterKeyPaths { get { - return ResourceManager.GetString("SQL_Multiplexer_ConflictingConsumeModes", resourceCulture); + return ResourceManager.GetString("TCE_SqlConnection_TrustedColumnMasterKeyPaths", resourceCulture); } } /// - /// Looks up a localized string similar to Attempted to pend a read when the fail async pends test hook was enabled.. + /// Looks up a localized string similar to Retrieving encrypted column '{0}' as a {1} is not supported.. /// - internal static string SQL_FailAsyncPendsEnabled { + internal static string TCE_StreamNotSupportOnEncryptedColumn { get { - return ResourceManager.GetString("SQL_FailAsyncPendsEnabled", resourceCulture); + return ResourceManager.GetString("TCE_StreamNotSupportOnEncryptedColumn", resourceCulture); } } /// - /// Looks up a localized string similar to Packet modification detected. Hash is null but packet contains non-null buffer.. + /// Looks up a localized string similar to Unable to verify a column master key signature. Error message: {0} . /// - internal static string SQL_PacketHashNullWithNonNullBuffer { + internal static string TCE_UnableToVerifyColumnMasterKeySignature { get { - return ResourceManager.GetString("SQL_PacketHashNullWithNonNullBuffer", resourceCulture); + return ResourceManager.GetString("TCE_UnableToVerifyColumnMasterKeySignature", resourceCulture); } } /// - /// Looks up a localized string similar to Packet modification detected. Hash from packet creation does not match hash from packet check.. + /// Looks up a localized string similar to Internal error. The result returned by '{0}' is invalid. The attestation information resultset is missing for enclave type '{1}'. . /// - internal static string SQL_PacketHashMismatch { + internal static string TCE_UnexpectedDescribeParamFormatAttestationInfo { get { - return ResourceManager.GetString("SQL_PacketHashMismatch", resourceCulture); + return ResourceManager.GetString("TCE_UnexpectedDescribeParamFormatAttestationInfo", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot transition from state '{0}' to '{1}'.. + /// Looks up a localized string similar to Internal error. The result returned by '{0}' is invalid. The parameter metadata resultset is missing.. /// - internal static string SQL_ActiveDirectoryInvalidStateTransition { + internal static string TCE_UnexpectedDescribeParamFormatParameterMetadata { get { - return ResourceManager.GetString("SQL_ActiveDirectoryInvalidStateTransition", resourceCulture); + return ResourceManager.GetString("TCE_UnexpectedDescribeParamFormatParameterMetadata", resourceCulture); } } /// - /// Looks up a localized string similar to Unsupported state: '{0}'.. + /// Looks up a localized string similar to Encryption algorithm '{0}' for the column in the database is either invalid or corrupted. Valid algorithms are: {1}.. /// - internal static string SQL_ActiveDirectoryUnsupportedState { + internal static string TCE_UnknownColumnEncryptionAlgorithm { get { - return ResourceManager.GetString("SQL_ActiveDirectoryUnsupportedState", resourceCulture); + return ResourceManager.GetString("TCE_UnknownColumnEncryptionAlgorithm", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to create SqlRetryLogicBaseProvider object because the retryMethod value is null or empty.. + /// Looks up a localized string similar to Encryption algorithm id '{0}' for the column in the database is either invalid or corrupted. Valid encryption algorithm ids are: {1}.. /// - internal static string SQLCR_RetryMethodNullOrEmpty { + internal static string TCE_UnknownColumnEncryptionAlgorithmId { get { - return ResourceManager.GetString("SQLCR_RetryMethodNullOrEmpty", resourceCulture); + return ResourceManager.GetString("TCE_UnknownColumnEncryptionAlgorithmId", resourceCulture); } } /// - /// Looks up a localized string similar to Exception occurred when running the '{0}.{1}()' method.. + /// Looks up a localized string similar to Failed to decrypt a column encryption key. Invalid key store provider name: '{0}'. A key store provider name must denote either a system key store provider or a registered custom key store provider. Valid system key store provider names are: {1}. Valid (currently registered) custom key store provider names are: {2}. Please verify key store provider information in column master key definitions in the database, and verify all custom key store providers used in your application are registered properly.. /// - internal static string SQLCR_RetryMethodException { + internal static string TCE_UnrecognizedKeyStoreProviderName { get { - return ResourceManager.GetString("SQLCR_RetryMethodException", resourceCulture); + return ResourceManager.GetString("TCE_UnrecognizedKeyStoreProviderName", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to resolve the '{0}' method from '{1}' type.. + /// Looks up a localized string similar to Encryption and decryption of data type '{0}' is not supported.. /// - internal static string SQLCR_ResolveMethodFailed { + internal static string TCE_UnsupportedDatatype { get { - return ResourceManager.GetString("SQLCR_ResolveMethodFailed", resourceCulture); + return ResourceManager.GetString("TCE_UnsupportedDatatype", resourceCulture); } } /// - /// Looks up a localized string similar to Invalid return type; return type must be of '{0}' type.. + /// Looks up a localized string similar to Normalization version '{0}' received from {2} is not supported. Valid normalization versions are: {1}.. /// - internal static string SQLCR_InvalidReturnType { + internal static string TCE_UnsupportedNormalizationVersion { get { - return ResourceManager.GetString("SQLCR_InvalidReturnType", resourceCulture); + return ResourceManager.GetString("TCE_UnsupportedNormalizationVersion", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to create SqlRetryLogicBaseProvider object because of invalid '{0}' parameters.{1}The function must have a parameter of type 'SqlRetryLogicOption'.. + /// Looks up a localized string similar to Column master key path '{0}' received from server '{1}' is not a trusted key path.. /// - internal static string SQLCR_InvalidRetryParameters { + internal static string TCE_UntrustedKeyPath { get { - return ResourceManager.GetString("SQLCR_InvalidRetryParameters", resourceCulture); + return ResourceManager.GetString("TCE_UntrustedKeyPath", resourceCulture); } } /// - /// Looks up a localized string similar to Failed to create SqlRetryLogicBaseProvider object because of invalid retryMethod parameters.{0}Parameter '{1} {2}' doesn't have a default value.. + /// Looks up a localized string similar to Failed to check if the enclave is running in the production mode. Contact Customer Support Services.. /// - internal static string SQLCR_InvalidParameterNoDefault { + internal static string VerifyEnclaveDebuggable { get { - return ResourceManager.GetString("SQLCR_InvalidParameterNoDefault", resourceCulture); + return ResourceManager.GetString("VerifyEnclaveDebuggable", resourceCulture); } } /// - /// Looks up a localized string similar to Sequence contains more than one matching element.. + /// Looks up a localized string similar to Could not verify enclave policy due to a difference between the expected and actual values of the policy on property '{0}'. Actual: '{1}', Expected: '{2}' - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details.. /// - internal static string SQLCR_SequenceMoreThanOneMatch { + internal static string VerifyEnclavePolicyFailedFormat { get { - return ResourceManager.GetString("SQLCR_SequenceMoreThanOneMatch", resourceCulture); + return ResourceManager.GetString("VerifyEnclavePolicyFailedFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Sequence contains no matching element.. + /// Looks up a localized string similar to Signature verification of the enclave report failed. The report signature does not match the signature computed using the HGS root certificate. Verify the DNS mapping for the endpoint - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details. If correct, contact Customer Support Services.. /// - internal static string SQLCR_SequenceNoMatch { + internal static string VerifyEnclaveReportFailed { get { - return ResourceManager.GetString("SQLCR_SequenceNoMatch", resourceCulture); + return ResourceManager.GetString("VerifyEnclaveReportFailed", resourceCulture); } } /// - /// Looks up a localized string similar to Can only rent from a pool if an onCreate delegate is available.. + /// Looks up a localized string similar to The enclave report received from SQL Server is not in the correct format. Contact Customer Support Services.. /// - internal static string SQL_ObjectPoolNoCreateDelegate { + internal static string VerifyEnclaveReportFormatFailed { get { - return ResourceManager.GetString("SQL_ObjectPoolNoCreateDelegate", resourceCulture); + return ResourceManager.GetString("VerifyEnclaveReportFormatFailed", resourceCulture); } } /// - /// Looks up a localized string similar to The type '{0}' has an invalid size.. + /// Looks up a localized string similar to Failed to build a chain of trust between the enclave host's health report and the HGS root certificate for attestation URL '{0}' with status: '{1}'. Verify the attestation URL matches the URL configured on the SQL Server - see https://go.microsoft.com/fwlink/?linkid=2160553 for more details. If both the client and SQL Server use the same attestation service, contact Customer Support Services.. /// - internal static string SQL_InvalidUdtSize { + internal static string VerifyHealthCertificateChainFormat { get { - return ResourceManager.GetString("SQL_InvalidUdtSize", resourceCulture); + return ResourceManager.GetString("VerifyHealthCertificateChainFormat", resourceCulture); } } } diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx index 0006d91c9d..ca9dcf0a93 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Ascending - Invalid index {0} for this {1} with Count={2}. @@ -132,18 +129,12 @@ The {0} is already contained by another {1}. - - The {0} with is already contained by this {1}. - The {0} only accepts non-null {1} type objects. Attempted to remove an {0} that is not contained by this {1}. - - The {0}.{1} is required to be unique, '{2}' already exists in the collection. - The connection was not closed. {0} @@ -171,12 +162,6 @@ Invalid attempt to call {0} when reader is closed. - - Cannot enlist in the transaction because the connection is the primary connection for a delegated or promoted transaction. - - - Descending - Expecting non-empty string for '{0}' parameter. @@ -204,9 +189,6 @@ The ConnectionString property has not been initialized. - - A Non CLS Exception was caught. - Given security element is not a permission element. @@ -252,9 +234,6 @@ Data - - StateChange - Update @@ -264,48 +243,12 @@ The ConnectionState indicating whether the connection is open or closed. - - Event triggered when the connection changes state. - - - The parameter generic type. - - - Input, output, or bidirectional parameter. - - - a design-time property used for strongly typed code-generation. - - - Offset in variable length data types. - - - Name of the parameter. - - - Size of variable length data types (string & arrays). - - - When used by a DataAdapter.Update, the source column name that is used to find the DataSetColumn name in the ColumnMappings. This is to copy a value between the parameter and a data row. - - - When used by a DataAdapter.Update (UpdateCommand only), the version of the DataRow value that is used to update the data source. - - - When used by DataAdapter.Update, the parameter value is changed from DBNull.Value into (Int32)1 or (Int32)0 if non-null. - - - Value of the parameter. - Unable to build the '{0}' collection because execution of the SQL query failed. See the inner exception for details. More restrictions were provided than the requested schema ('{0}') supports. - - '{2}' is not a valid value for the '{1}' restriction of the '{0}' schema collection. - The requested collection ({0}) is not defined. @@ -315,15 +258,6 @@ The requested collection ({0}) is not supported by this version of the provider. - - One of the required DataSourceInformation tables columns is missing. - - - The DataSourceInformation table must contain exactly one row. - - - One or more of the required columns of the restrictions collection is missing. - A restriction exists for which there is no matching row in the restrictions collection. @@ -342,42 +276,21 @@ The collection '{0}' is missing from the metadata XML. - - The metadata XML is invalid. - - - The metadata XML is invalid. The {0} collection must contain a {1} column and it must be a string column. - The metadata XML is invalid. The {1} column of the {0} collection must contain a non-empty string. - - Action - - - Behavior - Fill InfoMessage - - Mapping - StatementCompleted - - UDT - Notification - - Schema - XML @@ -393,9 +306,6 @@ Pooling - - Named ConnectionString - Security @@ -408,2073 +318,204 @@ Connection Resiliency - - The collection that holds custom user information. - - - Indicates whether comparing strings within the DataSet is case sensitive. - - - The name of this DataSet. - - - Indicates a custom "view" of the data contained by the DataSet. This view allows filtering, searching, and navigating through the custom data view. - - - Indicates whether constraint rules are to be followed. - - - Indicates that the DataSet has errors. - - - Indicates a locale under which to compare strings within the DataSet. - - - Indicates the XML uri namespace for the root element pointed at by this DataSet. - - - Indicates the prefix of the namespace used for this DataSet. - - - The collection that holds the relations for this DataSet. - - - The collection that holds the tables for this DataSet. - - - Occurs when it is not possible to merge schemas for two tables with the same name. - - - Occurs after Initialization is finished. - - - Represents an in-memory cache of data. - - - Indicates whether comparing strings within the table is case sensitive. - - - Returns the child relations for this table. - - - The collection that holds the columns for this table. - - - The collection that holds the constraints for this table. - - - Indicates the DataSet to which this table belongs. - - - This is the default DataView for the table. - - - The expression used to compute the data-bound value of this row. - - - Returns whether the table has errors. - - - Indicates a locale under which to compare strings within the table. - - - Indicates an initial starting size for this table. - - - Indicates the XML uri namespace for the elements contained in this table. - - - Indicates the Prefix of the namespace used for this table in XML representation. - - - Returns the parent relations for this table. - - - Indicates the column(s) that represent the primary key for this table. - - - Indicates the collection that holds the rows of data for this table. - - - Indicates the name used to look up this table in the Tables collection of a DataSet. - - - Occurs after a row in the table has been successfully edited. - - - Occurs when the row is being changed so that the event handler can modify or cancel the change. The user can modify values in the row and should throw an exception to cancel the edit. - - - Occurs after a row in the table has been successfully deleted. - - - Occurs when a row in the table marked for deletion. Throw an exception to cancel the deletion. - - - Occurs when a value has been submitted for this column. The user can modify the proposed value and should throw an exception to cancel the edit. - - - Occurs when a value has been changed for this column. - - - Occurs prior to clearing all rows from the table. - - - Occurs after all rows in the table has been successfully cleared. - - - Occurs after a new DataRow has been instantiated. - - - The name used to look up this relation in the Relations collection of a DataSet. - - - Indicates the child columns of this relation. - - - Indicates the parent columns of this relation. - - - Indicates whether relations are nested. - - - For deletions, indicates what kind of cascading should take place across this relation. - - - For updates, indicates what kind of cascading should take place across this relation. - - - For accept and reject changes, indicates what kind of cascading should take place across this relation. - - - Indicates the child columns of this constraint. - - - Indicates the parent columns of this constraint. - - - Indicates the child table of this constraint. - - - Indicates the columns of this constraint. - - - Indicates if this constraint is a primary key. - - - Indicates the name of this constraint. - - - Indicates the table of this constraint. - - - Indicates whether null values are allowed in this column. - - - Indicates whether the column automatically increments itself for new rows added to the table. The type of this column must be Int16, Int32, or Int64. - - - Indicates the starting value for an AutoIncrement column. - - - Indicates the increment used by an AutoIncrement column. - - - Indicates the default user-interface caption for this column. - - - Indicates the name used to look up this column in the Columns collection of a DataTable. - - - Returns the DataTable to which this column belongs. - - - Indicates the type of data stored in this column. - - - Indicates the default column value used when adding new rows to the table. - - - Indicates the value that this column computes for each row based on other columns instead of taking user input. - - - Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing. - - - Indicates the XML uri for elements or attributes stored in this column. - - - Indicates the Prefix used for this DataColumn in xml representation. - - - Indicates the index of this column in the Columns collection. - - - Indicates whether this column allows changes once a row has been added to the table. - - - Indicates whether this column should restrict its values in the rows of the table to be unique. - - - Indicates the maximum length of the value this column allows. - - - Indicates DateTimeMode of this DataColumn. - - - Indicates whether this DataView and the user interface associated with it allows deletes. - - - Indicates whether this DataView and the user interface associated with it allows edits. - - - Indicates whether this DataView and the user interface associated with it allows new rows to be added. - - - Returns the number of items currently in this view. - - - This returns a pointer to back to the DataViewManager that owns this DataSet (if any). - - - Indicates whether the view is open. - - - Indicates an expression used to filter the data returned by this DataView. - - - Indicates the versions of data returned by this DataView. - - - Indicates the names of the column and the order in which data is returned by this DataView. - - - Indicates whether to use the default sort if the Sort property is not set. - - - Indicates the table this DataView uses to get data. - - - Indicates that the data returned by this DataView has somehow changed. - - - Indicates the source of data for this DataViewManager. - - - Indicates the sorting/filtering/state settings for any table in the corresponding DataSet. - - - DataSet doesn't support 'union' or 'list' as simpleType. - - - Invalid {0} syntax: missing required '{1}' attribute. - - - Value '{1}' is invalid for attribute '{0}'. - - - The value of attribute '{0}' should be '{1}' or '{2}'. - - - Cannot find ElementType name='{0}'. - - - Parent table name is missing in relation '{0}'. - - - Child table name is missing in relation '{0}'. - - - Parent table key is missing in relation '{0}'. - - - Child table key is missing in relation '{0}'. - - - Undefined data type: '{0}'. - - - Data type not defined. - - - Invalid XPath selection inside field node. Cannot find: {0}. - - - Invalid XPath selection inside selector node: {0}. - - - Invalid 'Key' node inside constraint named: {0}. - - - The constraint name {0} is already used in the schema. - - - Cannot convert '{0}' to type '{1}'. - - - Missing '{0}' part in '{1}' constraint named '{2}'. - - - Invalid Relation definition: different length keys. - - - DataSet doesn't allow the circular reference in the ComplexType named '{0}'. - - - DataSet cannot instantiate an abstract ComplexType for the node {0}. - - - An error occurred with the multiple target converter while writing an Xml Schema. See the inner exception for details. - - - An error occurred with the multiple target converter while writing an Xml Schema. A null or empty string was returned. - - - Duplicated declaration '{0}'. - - - Cannot load diffGram. Table '{0}' is missing in the destination dataset. - - - Cannot load diffGram. The 'sql' node is missing. - - - Column name '{0}' is defined for different mapping types. - - - Prefix '{0}' is not valid, because it contains special characters. - - - Circular reference in self-nested table '{0}'. - - - DataSet cannot expand entities. Use XmlValidatingReader and set the EntityHandling property accordingly. - - - Type '{0}' does not implement IXmlSerializable interface therefore can not proceed with serialization. - - - Unable to proceed with deserialization. Data does not implement IXMLSerializable, therefore polymorphism is not supported. - - - DataTable does not support schema inference from Xml. - - - Cannot proceed with serializing DataTable '{0}'. It contains a DataRow which has multiple parent rows on the same Foreign Key. - - - IsDataSet attribute is missing in input Schema. - - - Cannot determine the DataSet Element. IsDataSet attribute exist more than once. - - - DataSet will not serialize types that implement IDynamicMetaObjectProvider but do not also implement IXmlSerializable. - - - The feature not implemented. {0}. - - - Syntax error: Missing operand after '{0}' operator. - - - Type mismatch in expression '{0}'. - - - Expression is too complex. - - - Cannot find column [{0}]. - - - The expression contains an invalid string constant: {0}. - - - The expression contains undefined function call {0}(). - - - Syntax error in the expression. - - - Invalid number of arguments: function {0}(). - - - The expression is missing the closing parenthesis. - - - Cannot interpret token '{0}' at position {1}. - - - Expected {0}, but actual token at the position {2} is {1}. - - - Cannot convert from {0} to {1}. - - - Cannot convert value '{0}' to Type: {1}. - - - Invalid column name [{0}]. - - - The expression contains invalid date constant '{0}'. - - - Only constant expressions are allowed in the expression list for the IN operator. - - - Error in Like operator: the string pattern '{0}' is invalid. - - - Syntax error: The items following the IN keyword must be separated by commas and be enclosed in parentheses. - - - Type mismatch in function argument: {0}(), argument {1}, expected {2}. - - - Type mismatch in function argument: {0}(), argument {1}, expected one of the Integer types. - - - Cannot perform '{0}' operation on {1} and {2}. - - - Operator '{0}' is ambiguous on operands of type '{1}' and '{2}'. Cannot mix signed and unsigned types. Please use explicit Convert() function. - - - Syntax error: The IN keyword must be followed by a non-empty list of expressions separated by commas, and also must be enclosed in parentheses. - - - The expression contains unsupported operator '{0}'. - - - The expression contains invalid name: '{0}'. - - - Syntax error: Missing operand before '{0}' operator. - - - The expression has too many closing parentheses. - - - The table [{0}] involved in more than one relation. You must explicitly mention a relation name in the expression '{1}'. - - - Syntax error in aggregate argument: Expecting a single column argument with possible 'Child' qualifier. - - - Unbound reference in the aggregate expression '{0}'. - - - Cannot evaluate non-constant expression without current row. - - - Unbound reference in the expression '{0}'. - - - Cannot evaluate. Expression '{0}' is not an aggregate. - - - Filter expression '{0}' does not evaluate to a Boolean term. - - - Invalid type name '{0}'. - - - Syntax error in Lookup expression: Expecting keyword 'Parent' followed by a single column argument with possible relation qualifier: Parent[(<relation_name>)].<column_name>. - - - Need a row or a table to Invoke DataFilter. - - - {0}() argument is out of range. + + Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection. - - Syntax error: Invalid usage of 'Is' operator. Correct syntax: <expression> Is [Not] Null. + + Cannot convert object of type '{0}' to object of type '{1}'. - - Value is either too large or too small for Type '{0}'. + + The only acceptable value for the property '{0}' is '{1}'. - - Divide by zero error encountered. + + The acceptable values for the property '{0}' are '{1}' or '{2}'. - - Cannot find the parent relation '{0}'. + + Specified QuotePrefix and QuoteSuffix values do not match. - - 'hours' argument is out of range. Value must be between -14 and +14. + + The length of argument '{0}' exceeds its limit of '{1}'. - - 'minutes' argument is out of range. Value must be between -59 and +59. + + Invalid value for key '{0}'. - - Provided range for time one exceeds total of 14 hours. + + Use of key '{0}' requires the key '{1}' to be present. - - Kind property of provided DateTime argument, does not match 'hours' and 'minutes' arguments. + + The value's length for key '{0}' exceeds its limit of '{1}'. - - A DataColumn of type '{0}' does not support expression. + + Keyword not supported: '{0}'. - - Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints. + + Internal .NET Framework Data Provider error {0}. - - Collection itself is not modifiable. + + {0} "{1}". - - The given name '{0}' matches at least two names in the collection object with different cases, but does not match either of them with the same case. + + {0} "{1}", incorrect usage of quotes. - - The given name '{0}' matches at least two names in the collection object with different namespaces. + + {0} "{1}", the current limit of "{2}" is insufficient. - - Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection. + + SqlBulkCopy.WriteToServer failed because the SqlBulkCopy.DestinationTableName is an invalid multipart name - - '{0}' argument is out of range. + + Processing of results from SQL Server failed because of an invalid multipart name - - '{0}' argument cannot be null. + + SqlParameter.UdtTypeName is an invalid multipart name - - '{0}' argument contains null value. + + SqlParameter.TypeName is an invalid multipart name - - Cannot find column {0}. + + SqlCommand.DeriveParameters failed because the SqlCommand.CommandText property value is an invalid multipart name - - Column '{0}' already belongs to this DataTable. + + Connecting to a mirrored SQL Server instance using the MultiSubnetFailover connection option is not supported. - - Column '{0}' already belongs to another DataTable. + + The {0} enumeration value, {1}, is not supported by the {2} method. - - Cannot have more than one SimpleContent columns in a DataTable. + + The {0} enumeration value, {1}, is not supported by the .NET Framework SqlClient Data Provider. - - Cannot add a SimpleContent column to a table containing element columns or nested relations. + + {0}: CommandText property has not been initialized - - A column named '{0}' already belongs to this DataTable. + + {0}: Connection property has not been initialized. - - Cannot add a column named '{0}': a nested table with the same name already belongs to this DataTable. + + {0} requires an open and available Connection. {1} - - A column named '{0}' already belongs to this DataTable: cannot set a nested table name to the same name. + + The stored procedure '{0}' doesn't exist. - - Cannot remove a column that doesn't belong to this table. + + The transaction is either not associated with the current connection or has been completed. - - Cannot remove this column, because it's part of the primary key. + + The transaction associated with the current connection has completed but has not been disposed. The transaction must be disposed before the connection can be used to execute SQL statements. - - Cannot remove this column, because it is part of the parent key for relationship {0}. + + {0} requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized. - - Cannot remove this column, because it is a part of the constraint {0} on the table {1}. + + There is already an open DataReader associated with this {0} which must be closed first. - - Cannot remove this column, because it is part of an expression: {0} = {1}. + + {0} DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{1}. - - Cannot set AutoIncrement property for a computed column. - - - Cannot set AutoIncrement property for a column with DefaultValue set. - - - Cannot set a DefaultValue on an AutoIncrement column. - - - AutoIncrementStep must be a non-zero value. - - - ColumnName is required when it is part of a DataTable. - - - Cannot change DataType of a column once it has data. - - - Column requires a valid DataType. - - - The DefaultValue for column {0} is of type {1} and cannot be converted to {2}. - - - The DefaultValue for the column is of type {0} and cannot be converted to {1}. - - - The DefaultValue for column {0} is of type {1}, but the column is of type {2}. - - - Cannot change ReadOnly property for the expression column. - - - Cannot change Unique property for the expression column. - - - Cannot create an expression on a column that has AutoIncrement or Unique. - - - Cannot set expression because column cannot be made ReadOnly. - - - Cannot set Expression property on column {0}, because it is a part of a constraint. - - - Cannot create a constraint based on Expression column {0}. - - - Cannot set Expression property due to circular reference in the expression. - - - Column '{0}' has null values in it. - - - Column '{0}' does not allow nulls. - - - Column '{0}' is read only. - - - Column '{0}' contains non-unique values. - - - Column '{0}' does not belong to table {1}. - - - Column must belong to a table. - - - Couldn't store <{0}> in {1} Column. Expected type is {2}. - - - Cannot set Column '{0}' to be null. Please use DBNull instead. - - - Cannot set column '{0}'. The value violates the MaxLength limit of this column. - - - MaxLength applies to string data type only. You cannot set Column '{0}' property MaxLength to be non-negative number. - - - Cannot set Column '{0}' property MaxLength to '{1}'. There is at least one string in the table longer than the new limit. - - - Cannot set Column '{0}' property MaxLength. The Column is SimpleContent. - - - Cannot set Column '{0}' property DataType to {1}. The Column is SimpleContent. - - - Cannot set Column '{0}' property MappingType to SimpleContent. The Column DataType is {1}. - - - Column '{0}' exceeds the MaxLength limit. - - - Column '{0}' does not allow DBNull.Value. - - - Cannot change the Column '{0}' property Namespace. The Column is SimpleContent. - - - Cannot change AutoIncrement of a DataColumn with type '{0}' once it has data. - - - Column '{0}' does not belong to underlying table '{1}'. - - - DataColumn with type '{0}' is a complexType. Can not serialize value of a complex type as Attribute - - - The DateTimeMode can be set only on DataColumns of type DateTime. - - - '{0}' is Invalid DataSetDateTime value. - - - Cannot change DateTimeMode from '{0}' to '{1}' once the table has data. - - - Type '{0}' does not contain static Null property or field. - - - Type '{0}' does not implement IRevertibleChangeTracking; therefore can not proceed with RejectChanges(). - - - SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState. - - - Ordinal '{0}' exceeds the maximum number. - - - DataSet does not support System.Nullable<>. - - - Cannot change the name of a constraint to empty string when it is in the ConstraintCollection. - - - Cannot enforce constraints on constraint {0}. - - - Column '{0}' is constrained to be unique. Value '{1}' is already present. - - - Constraint '{0}' does not belong to this DataTable. - - - Cannot find constraint {0}. - - - Constraint matches constraint named {0} already in collection. - - - A Constraint named '{0}' already belongs to this DataTable. - - - These columns don't currently have unique values. - - - These columns don't point to this table. - - - This constraint cannot be enabled as not all values have corresponding parent values. - - - This constraint cannot be added since ForeignKey doesn't belong to table {0}. - - - Cannot remove a constraint that doesn't belong to this table. - - - Cannot remove unique constraint '{0}'. Remove foreign key constraint '{1}' first. - - - Cannot delete this row because constraints are enforced on relation {0}, and deleting this row will strand child rows. - - - Cannot make this change because constraints are enforced on relation {0}, and changing this value will strand child rows. - - - Cannot clear table {0} because ForeignKeyConstraint {1} enforces constraints and there are child rows in {2}. - - - ForeignKeyConstraint {0} requires the child key values ({1}) to exist in the parent table. - - - Property not accessible because '{0}'. - - - Cannot remove this row because it has child rows, and constraints on relation {0} are enforced. - - - Cannot add primary key constraint since primary key is already set for the table. - - - Cannot add constraint to DataTable '{0}' which is a child table in two nested relations. - - - Cannot create a Key from Columns that belong to different tables. - - - Cannot have 0 columns. - - - Cannot have more than {0} columns. - - - Cannot create a Key when the same column is listed more than once: '{0}' - - - Cannot remove unique constraint since it's the primary key of a table. - - - Cannot remove unique constraint since it's the primary key of table {0}. - - - Parent Columns and Child Columns don't have type-matching columns. - - - ParentKey and ChildKey are identical. - - - ParentColumns and ChildColumns should be the same length. - - - ParentColumns and ChildColumns must not be zero length. - - - The row doesn't belong to the same DataSet as this relation. - - - RelationName is required when it is part of a DataSet. - - - GetChildRows requires a row whose Table is {0}, but the specified row's Table is {1}. - - - This relation should connect two tables in this DataSet to be added to this DataSet. - - - GetParentRow requires a row whose Table is {0}, but the specified row's Table is {1}. - - - SetParentRow requires a child row whose Table is {0}, but the specified row's Table is {1}. - - - Cannot have a relationship between tables in different DataSets. - - - Cannot create a relation between tables in different DataSets. - - - A relation already exists for these child columns. - - - This relation doesn't belong to this relation collection. - - - This relation already belongs to another DataSet. - - - This relation already belongs to this DataSet. - - - A Relation named '{0}' already belongs to this DataSet. - - - Relation {0} does not belong to this DataSet. - - - Cannot find relation {0}. - - - Cannot create a collection on a null table. - - - The table this collection displays relations for has been removed from its DataSet. - - - Cannot add a relation to this table's ParentRelation collection where this table isn't the child table. - - - Cannot add a relation to this table's ChildRelation collection where this table isn't the parent table. - - - Cannot set the 'Nested' property to false for this relation. - - - The same table '{0}' cannot be the child table in two nested relations. - - - The table ({0}) cannot be the child table to itself in nested relations. - - - Cannot add a DataRelation or Constraint that has different Locale or CaseSensitive settings between its parent and child tables. - - - Cannot create a DataRelation if Parent or Child Columns are not in a DataSet. - - - Nested table '{0}' which inherits its namespace cannot have multiple parent tables in different namespaces. - - - Nested table '{0}' with empty namespace cannot have multiple parent tables in different namespaces. - - - The row doesn't belong to the same DataSet as this relation. - - - Cannot perform this operation on a row not in the table. - - - This relation and child row don't belong to same DataSet. - - - Cannot change a proposed value in the RowChanging event. - - - Cannot call EndEdit() inside an OnRowChanging event. - - - Cannot call BeginEdit() inside the RowChanging event. - - - Cannot call CancelEdit() inside an OnRowChanging event. Throw an exception to cancel this update. - - - Cannot call Delete inside an OnRowDeleting event. Throw an exception to cancel this delete. - - - Input array is longer than the number of columns in this table. - - - There is no Current data to access. - - - There is no Original data to access. - - - There is no Proposed data to access. - - - This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row. - - - Deleted row information cannot be accessed through the row. - - - Version must be Original, Current, or Proposed. - - - There is no row at position {0}. - - - The row insert position {0} is invalid. - - - The rowOrder value={0} has been found twice for table named '{1}'. - - - Values are missing in the rowOrder sequence for table '{0}'. - - - The given DataRow is not in the current DataRowCollection. - - - This row already belongs to another table. - - - This row already belongs to this table. - - - Cannot delete this row since it's already deleted. - - - This row is empty. - - - Cannot remove a row that's already been removed. - - - A child row has multiple parents. - - - Unrecognized row state bit pattern. - - - Cannot change the name of the DataSet to an empty string. - - - The name '{0}' is invalid. A DataSet cannot have the same name of the DataTable. - - - The schema namespace is invalid. Please use this one instead: {0}. - - - Cannot change CaseSensitive or Locale property. This change would lead to at least one DataRelation or Constraint to have different Locale or CaseSensitive settings between its related tables. - - - SchemaSerializationMode property can be set only if it is overridden by derived DataSet. - - - PrimaryKey columns do not belong to this table. - - - Cannot add a nested relation or an element column to a table containing a SimpleContent column. - - - TableName is required when it is part of a DataSet. - - - DataTable already has a simple content column. - - - Table doesn't have a primary key. - - - {0} isn't a valid Sort string entry. - - - Cannot serialize the DataTable. A DataTable being used in one or more DataColumn expressions is not a descendant of current DataTable. - - - This DataTable can only be remoted as part of DataSet. One or more Expression Columns has reference to other DataTable(s). - - - Cannot have different remoting format property value for DataSet and DataTable. - - - Cannot serialize the DataTable. DataTable name is not set. - - - A DataTable named '{0}' already belongs to this DataSet. - - - A DataTable named '{0}' with the same Namespace '{1}' already belongs to this DataSet. - - - The table ({0}) cannot be the child table to itself in a nested relation: the DataSet name conflicts with the table name. - - - The name '{0}' is invalid. A DataTable cannot have the same name of the DataSet. - - - DataTable already belongs to another DataSet. - - - DataTable already belongs to this DataSet. - - - Table {0} does not belong to this DataSet. - - - Cannot find table {0}. - - - Cannot remove a table that has existing relations. Remove relations first. - - - Cannot remove table {0}, because it referenced in ForeignKeyConstraint {1}. Remove the constraint first. - - - DataTable '{0}' does not match to any DataTable in source. - - - Target DataSet missing definition for {0}. - - - Target DataSet missing {0} {1}. - - - <target>.{0} and <source>.{0} have conflicting properties: DataType property mismatch. - - - <target>.PrimaryKey and <source>.PrimaryKey have different Length. - - - Mismatch columns in the PrimaryKey : <target>.{0} versus <source>.{1}. - - - Relation {0} cannot be merged, because keys have mismatch columns. - - - Target table {0} missing definition for column {1}. - - - PrimaryKey column {0} does not exist in source Table. - - - The RowStates parameter must be set to a valid combination of values from the DataViewRowState enumeration. - - - Find finds a row based on a Sort order, and no Sort order is specified. - - - Expecting {0} value(s) for the key being indexed, but received {1} value(s). - - - Invalid usage of aggregate function {0}() and Type: {1}. - - - Invalid storage type: {0}. - - - The DataSet Xml persistency does not support the value '{0}' as Char value, please use Byte storage instead. - - - Type of value has a mismatch with column type - - - Type '{0}' does not implement IComparable interface. Comparison cannot be done. - - - Cannot set {0}. - - - Cannot change DataSet on a DataViewManager that's already the default view for a DataSet. - - - RowStateFilter cannot show ModifiedOriginals and ModifiedCurrents at the same time. - - - Cannot change Table property on a DefaultView or a DataView coming from a DataViewManager. - - - Cannot change DataSet property once it is set. - - - DataSet must be set prior to using DataViewManager. - - - Cannot change Table property once it is set. - - - DataTable must be set prior to using DataView. - - - Cannot bind to DataTable with no name. - - - Cannot set an object into this list. - - - Cannot call AddNew on a DataView where AllowNew is false. - - - DataView is not open. - - - The relation is not parented to the table to which this DataView points. - - - Cannot delete on a DataSource where AllowDelete is false. - - - Cannot edit on a DataSource where AllowEdit is false. - - - Index {0} is either negative or above rows count. - - - Cannot add external objects to this list. - - - Cannot clear this list. - - - Cannot insert external objects to this list. - - - Cannot remove objects not in the list. - - - {0} is neither a DataColumn nor a DataRelation for table {1}. - - - Min ({0}) must be less than or equal to max ({1}) in a Range object. - - - This is a null range. - - - MinimumCapacity must be non-negative. - - - Cannot generate identifier for name '{0}'. - - - There is more than one table with the same name '{0}' (even if namespace is different). - - - Column '{0}': Type '{1}' cannot be null. - - - Column '{0}': Type '{1}' does not have parameterless constructor. - - - Column '{0}': Type '{1}' does not have constructor with string argument. - - - Cannot convert object of type '{0}' to object of type '{1}'. - - - Data Exception. - - - Constraint Exception. - - - Deleted rows inaccessible. - - - Duplicate name not allowed. - - - Operation not supported in the RowChanging event. - - - Invalid constraint. - - - Missing primary key. - - - Null not allowed. - - - Column is marked read only. - - - Row not found in table. - - - Version not found. - - - ReadOnly Data is Modified. - - - DataTableReader is invalid for current DataTable '{0}'. - - - Schema of current DataTable '{0}' in DataTableReader has changed, DataTableReader is invalid. - - - DataTableReader Cannot be created. There is no DataTable in DataSet. - - - Cannot create DataTableReader. Argument is Empty. - - - Cannot create DataTableReader. Arguments contain null value. - - - Current DataRow is either in Deleted or Detached state. - - - Current DataTable '{0}' is empty. There is no DataRow in DataTable. - - - DataTable internal index is corrupted: '{0}'. - - - Collection was modified; enumeration operation might not execute. - - - Simple type '{0}' has already be declared with different '{1}'. - - - Invalid foliation. - - - Cannot change the table name once the associated DataSet is mapped to a loaded XML document. - - - Cannot change the table namespace once the associated DataSet is mapped to a loaded XML document. - - - Cannot change the column name once the associated DataSet is mapped to a loaded XML document. - - - Cannot change the column namespace once the associated DataSet is mapped to a loaded XML document. - - - Cannot change the ColumnMapping property once the associated DataSet is mapped to a loaded XML document. - - - Cannot add or remove columns from the table once the DataSet is mapped to a loaded XML document. - - - Cannot add or remove tables from the DataSet once the DataSet is mapped to a loaded XML document. - - - Cannot add, remove, or change Nested relations from the DataSet once the DataSet is mapped to a loaded XML document. - - - The DataSet parameter is invalid. It cannot be null. - - - Cannot change the DataSet name once the DataSet is mapped to a loaded XML document. - - - This type of node cannot be cloned: {0}. - - - Cannot load XmlDataDocument if it already contains data. Please use a new XmlDataDocument. - - - DataSet can be associated with at most one XmlDataDocument. Cannot associate the DataSet with the current XmlDataDocument because the DataSet is already associated with another XmlDataDocument. - - - Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations. - - - GetElementById() is not supported on DataDocument. - - - Cannot create entity references on DataDocument. - - - Clear function on DateSet and DataTable is not supported on XmlDataDocument. - - - Cannot remove column since it is built in to this dataSet. - - - Cannot remove relation since it is built in to this dataSet. - - - Occurs whenever a property for this control changes. - - - Occurs whenever this collection's membership changes. - - - Cannot get value because it is DBNull. - - - The '{0}' property requires Microsoft WindowsNT or a WindowsNT based operating system. - - - Unable to find the requested .NET Framework Data Provider. It may not be installed. - - - The requested .NET Framework Data Provider's implementation does not have an Instance field of a System.Data.Common.DbProviderFactory derived type. - - - Failed to find or load the registered .NET Framework Data Provider. - - - The missing .NET Framework Data Provider's assembly qualified name is required. - - - Only elements allowed. - - - Child nodes not allowed. - - - Unrecognized attribute '{0}'. - - - Unrecognized element. - - - The '{0}' section can only appear once per config file. - - - Required attribute '{0}' not found. - - - Required attribute '{0}' cannot be empty. - - - Expecting non-empty array for '{0}' parameter. - - - The only acceptable value for the property '{0}' is '{1}'. - - - The acceptable values for the property '{0}' are '{1}' or '{2}'. - - - Specified QuotePrefix and QuoteSuffix values do not match. - - - The length of argument '{0}' exceeds its limit of '{1}'. - - - Expecting argument of type {1}, but received type {0}. - - - Invalid value for key '{0}'. - - - Use of key '{0}' requires the key '{1}' to be present. - - - The value's length for key '{0}' exceeds its limit of '{1}'. - - - Keyword not supported: '{0}'. - - - Unable to load the UDL file. - - - Invalid UDL file. - - - Internal .NET Framework Data Provider error {0}. - - - The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated. - - - The DataAdapter.SelectCommand property needs to be initialized. - - - The DataAdapter.SelectCommand.Connection property needs to be initialized; - - - {0} "{1}". - - - {0} "{1}", incorrect usage of quotes. - - - {0} "{1}", the current limit of "{2}" is insufficient. - - - SqlBulkCopy.WriteToServer failed because the SqlBulkCopy.DestinationTableName is an invalid multipart name - - - Processing of results from SQL Server failed because of an invalid multipart name - - - SqlParameter.UdtTypeName is an invalid multipart name - - - SqlParameter.TypeName is an invalid multipart name - - - SqlCommand.DeriveParameters failed because the SqlCommand.CommandText property value is an invalid multipart name - - - OdbcCommandBuilder.DeriveParameters failed because the OdbcCommand.CommandText property value is an invalid multipart name - - - OleDbCommandBuilder.DeriveParameters failed because the OleDbCommandBuilder.CommandText property value is an invalid multipart name - - - Connecting to a mirrored SQL Server instance using the MultiSubnetFailover connection option is not supported. - - - The column mapping from SourceColumn '{0}' failed because the DataColumn '{1}' is a computed column. - - - Inconvertible type mismatch between SourceColumn '{0}' of {1} and the DataColumn '{2}' of {3}. - - - Missing the DataColumn '{0}' in the DataTable '{1}' for the SourceColumn '{2}'. - - - SourceColumn is required to be a non-empty string. - - - Missing SourceColumn mapping for '{0}'. - - - The {0} enumeration value, {1}, is not supported by the {2} method. - - - The {0} enumeration value, {1}, is not supported by the .NET Framework Odbc Data Provider. - - - The {0} enumeration value, {1}, is not supported by the .NET Framework OleDb Data Provider. - - - The {0} enumeration value, {1}, is not supported by the .NET Framework SqlClient Data Provider. - - - Unable to retrieve the ComputerNameDnsFullyQualified, {0}. - - - Missing the '{0}' DataTable for the '{1}' SourceTable. - - - SourceTable is required to be a non-empty string - - - Missing SourceTable mapping: '{0}' - - - {0}: CommandText property has not been initialized - - - {0}: Connection property has not been initialized. - - - {0} requires an open and available Connection. {1} - - - Fill: SelectCommand.Connection property has not been initialized. - - - FillPage: SelectCommand.Connection property has not been initialized. - - - FillSchema: SelectCommand.Connection property has not been initialized. - - - Update requires the InsertCommand to have a connection object. The Connection property of the InsertCommand has not been initialized. - - - Update requires the UpdateCommand to have a connection object. The Connection property of the UpdateCommand has not been initialized. - - - Update requires the DeleteCommand to have a connection object. The Connection property of the DeleteCommand has not been initialized. - - - Update requires a connection object. The Connection property has not been initialized. - - - Update requires the command clone to have a connection object. The Connection property of the command clone has not been initialized. - - - Update requires a connection. - - - Update requires the {0}Command to have an open connection object. {1} - - - Update requires the {0}Command to have an open connection object. {1} - - - Update requires the {0}Command to have an open connection object. {1} - - - Update requires the updating command to have an open connection object. {1} - - - The stored procedure '{0}' doesn't exist. - - - The transaction assigned to this command must be the most nested pending local transaction. - - - The transaction is either not associated with the current connection or has been completed. - - - The transaction associated with the current connection has completed but has not been disposed. The transaction must be disposed before the connection can be used to execute SQL statements. - - - {0} requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized. - - - There is already an open SqlResultSet associated with this command which must be closed first. - - - There is already an open DataReader associated with this {0} which must be closed first. - - - {0} DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{1}. - - - The method '{0}' cannot be called more than once for the same execution. - - - Incorrect async result. - - - The SelectCommand property has not been initialized before calling '{0}'. - - - The StatementType {0} is not expected here. - - - FillSchema: expected a non-empty string for the SourceTable name. - - - The MaxRecords value of {0} is invalid; the value must be >= 0. - - - The StartRecord value of {0} is invalid; the value must be >= 0. - - - Fill: expected a non-empty string for the SourceTable name. - - - Hierarchical chapter columns must map to an AutoIncrement DataColumn. - - - DataReader.GetFieldType({0}) returned null. - - - Only specify one item in the dataTables array when using non-zero values for startRecords or maxRecords. - - - Update unable to find TableMapping['{0}'] or DataTable '{0}'. - - - Update: expected a non-empty SourceTable name. - - - Missing TableMapping when TableMapping.DataSetTable='{0}'. - - - Update requires the command clone to be valid. - - - Auto SQL generation during Update requires a valid SelectCommand. - - - Update requires a valid InsertCommand when passed DataRow collection with new rows. - - - Update requires a valid UpdateCommand when passed DataRow collection with modified rows. - - - Update requires a valid DeleteCommand when passed DataRow collection with deleted rows. - - - DataRow[{0}] is from a different DataTable than DataRow[0]. - - - RowUpdatedEvent: Errors occurred; no additional is information available. - - - RowUpdatingEvent: Errors occurred; no additional is information available. - - - When batching, the command's UpdatedRowSource property value of UpdateRowSource.FirstReturnedRecord or UpdateRowSource.Both is invalid. - - - Concurrency violation: the UpdateCommand affected {0} of the expected {1} records. - - - Concurrency violation: the DeleteCommand affected {0} of the expected {1} records. - - - Concurrency violation: the batched command affected {0} of the expected {1} records. + + The method '{0}' cannot be called more than once for the same execution. Invalid CommandTimeout value {0}; the value must be >= 0. - - {1}[{0}]: the Size property has an invalid size of 0. - - - {0}.Prepare method requires all parameters to have an explicitly set type. - - - {0}.Prepare method requires all variable length parameters to have an explicitly set non-zero Size. - - - {0}.Prepare method requires parameters of type '{1}' have an explicitly set Precision and Scale. - - - Mismatched end method call for asyncResult. Expected call to {0} but {1} was called instead. - - - Invalid operation. The connection is closed. - - - The connection has been disabled. - - - Cannot enlist in the transaction because a local transaction is in progress on the connection. Finish local transaction and retry. - - - Connection currently has transaction enlisted. Finish current transaction and retry. - - - Database cannot be null, the empty string, or string of only whitespace. - - - The argument is too long. - - - Invalid 'Connect Timeout' value which must be an integer >= 0. - - - Invalid source buffer (size of {0}) offset: {1} - - - Invalid destination buffer (size of {0}) offset: {1} - - - The numerical value is too large to fit into a 96 bit decimal. - - - Invalid attempt to {0} when stream is closed. - - - Specified SeekOrigin value is invalid. - - - Dynamic SQL generation is not supported against multiple base tables. - - - Dynamic SQL generation is not supported against a SelectCommand that does not return any base table information. - - - Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information. - - - Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information. - - - Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not contain a row version column. - - - Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not contain a row version column. - - - Dynamic SQL generation not supported against table names '{0}' that contain the QuotePrefix or QuoteSuffix character '{1}'. - - - Invalid attempt to read from column ordinal '{0}'. With CommandBehavior.SequentialAccess, you may only read from column ordinal '{1}' or greater. - - - Data type '{0}' can not be formatted as a literal because it has an invalid date time digits. - - - The value can not be formatted as a literal of the requested type. - - - Data type '{0}' can not be formatted as a literal because it has an invalid maximum scale. - - - The literal value provided is not a valid literal for the data type '{0}'. - - - '{0}':The length of the literal value must be even. - - - '{0}':The literal value must be a string with hexadecimal digits - - - {0} requires open connection when the quote prefix has not been set. - - - Can not determine the correct boolean literal values. Boolean literals can not be created. - - - Literals of the native data type associated with data type '{0}' are not supported. - - - The parameter data type of {0} is invalid. - - - No mapping exists from object type {0} to a known managed provider native type. - - - Unable to handle an unknown TypeCode {0} returned by Type {1}. - - - No mapping exists from DbType {0} to a known {1}. - - - The version of SQL Server in use does not support datatype '{0}'. - - - Parameter value '{0}' is out of range. - - - Specified parameter name '{0}' is not valid. - - - Multiple return value parameters are not supported. - - - Invalid parameter Size value '{0}'. The value must be greater than or equal to 0. - - - Invalid value for argument '{0}'. The value must be greater than or equal to 0. - - - Invalid value for this metadata. - - - Metadata must be SqlDbType.Row - - - Failed to convert parameter value from a {0} to a {1}. - - - {0} does not support parallel transactions. - - - This {0} has completed; it is no longer usable. - - - '{0}' cannot be called when the record is read only. - - - '{0}' cannot be called when the DbDataRecord is read only. - - - Implicit conversion of object type '{0}' to data type '{1}' is not supported. - - - Buffer offset '{1}' plus the bytes available '{0}' is greater than the length of the passed in buffer. - - - Data length '{0}' is less than 0. - - - Specified length '{0}' is out of range. - - - Invalid {2} attempt at dataIndex '{0}'. With CommandBehavior.SequentialAccess, you may only read from dataIndex '{1}' or greater. - - - Offset must refer to a location within the value. - - - "The ODBC managed provider requires that the TABLE_NAME restriction be specified and non-null for the GetSchema indexes collection. - - - Invalid argument value for method '{0}'. - - - The ODBC provider did not return results from SQLGETTYPEINFO. - - - Unexpected null DataTable argument - - - Unexpected null DataSet argument. - - - Connection string exceeds maximum allowed length of {0}. - - - {0} - unable to map type. - - - Unknown SQL type - {0}. - - - Unknown URT type - {0}. - - - Invalid negative argument! - - - Can't set property on an open connection. - - - No valid mapping for a SQL_TRANSACTION '{0}' to a System.Data.IsolationLevel enumeration value. - - - {0} - unable to enable connection pooling... - - - {0} - unable to allocate an environment handle. - - - {0} - unable to get descriptor handle. - - - Not in a transaction - - - Invalid OdbcType enumeration value={0}. - - - Use IsDBNull when DBNull.Value data is expected. - - - {0} [{1}] {2} - - - {0} - no error information available - - - The connection is closed. - - - An internal connection does not have an owner. - - - The .NET Framework Odbc Data Provider requires Microsoft Data Access Components(MDAC) version 2.6 or later. Version {0} was found currently installed. - - - The .NET Framework OleDb Data Provider requires Microsoft Data Access Components(MDAC) version 2.6 or later. Version {0} was found currently installed. - - - '{0}' interface is not supported by the '{1}' provider. GetOleDbSchemaTable is unavailable with the current provider. - - - '{0}' failed with no error message available, result code: {1}. - - - No error message available, result code: {0}. - - - The .NET Framework Data Providers require Microsoft Data Access Components(MDAC). Please install Microsoft Data Access Components(MDAC) version 2.6 or later. - - - The .NET Framework Data Provider for OLEDB (Microsoft.Data.OleDb) does not support the Microsoft OLE DB Provider for ODBC Drivers (MSDASQL). Use the .NET Framework Data Provider for ODBC (System.Data.Odbc). - - - The .NET Framework Data Provider for OLEDB will not allow the OLE DB Provider to prompt the user in a non-interactive environment. - - - The '{0}' provider is not registered on the local machine. - - - The ICommandText interface is not supported by the '{0}' provider. Use CommandType.TableDirect instead. - - - The ITransactionLocal interface is not supported by the '{0}' provider. Local transactions are unavailable with the current provider. - - - Format of the initialization string does not conform to the OLE DB specification. Starting around char[{0}] in the connection string. - - - 'Asynchronous Processing' is not a supported feature of the .NET Framework Data OLE DB Provider(Microsoft.Data.OleDb). - - - An OLE DB Provider was not specified in the ConnectionString. An example would be, 'Provider=SQLOLEDB;'. - - - The OLE DB Provider specified in the ConnectionString is too long. - - - No restrictions are expected for the DbInfoKeywords OleDbSchemaGuid. - - - No restrictions are expected for the DbInfoLiterals OleDbSchemaGuid. - - - No restrictions are expected for the schema guid OleDbSchemaGuid. - - - The {0} OleDbSchemaGuid is not a supported schema by the '{1}' provider. - - - The '{0}' configuration setting has the wrong number of values. - - - Unable to load the XML file specified in configuration setting '{0}'. - - - Command parameter[{0}] '{1}' is invalid. - - - Command parameter[{0}] '{1}' data value could not be converted for reasons other than sign mismatch or data overflow. - - - Conversion failed for command parameter[{0}] '{1}' because the data value was signed and the type used by the provider was unsigned. - - - Conversion failed for command parameter[{0}] '{1}' because the data value overflowed the type used by the provider. - - - Provider encountered an error while sending command parameter[{0}] '{1}' value and stopped processing. - - - Parameter[{0}] '{1}' has no default value. - - - Error occurred with parameter[{0}]: {1}. - - - Microsoft.Data.OleDb.OleDbDataAdapter internal error: invalid parameter accessor: {0} {1}. - - - Parameter[{0}]: the OleDbType property is uninitialized: OleDbType.{1}. - - - The ICommandWithParameters interface is not supported by the '{0}' provider. Command parameters are unsupported with the current provider. - - - Retrieving procedure parameter information is not supported by the '{0}' provider. - - - Can not determine the server's decimal separator. Non-integer numeric literals can not be created. - - - Object is not an ADODB.RecordSet or an ADODB.Record. - - - Unable to retrieve the '{0}' interface from the ADODB.RecordSet object. + + {1}[{0}]: the Size property has an invalid size of 0. - - Unable to retrieve the IRow interface from the ADODB.Record object. + + {0}.Prepare method requires all parameters to have an explicitly set type. - - Type does not support the OLE DB interface ISourcesRowset + + {0}.Prepare method requires all variable length parameters to have an explicitly set non-zero Size. - - Cannot construct the ReservedWords schema collection because the provider does not support IDBInfo. + + {0}.Prepare method requires parameters of type '{1}' have an explicitly set Precision and Scale. - - The property's value was not set because the provider did not support the '{0}' property, or the consumer attempted to get or set values of properties not in the Initialization property group and the data source object is uninitialized. + + Mismatched end method call for asyncResult. Expected call to {0} but {1} was called instead. - - Failed to initialize the '{0}' property for one of the following reasons: - The value data type was not the data type of the property or was not null. For example, the property was DBPROP_MEMORYUSAGE, which has a data type of Int32, and the data type was Int64. - The value was not a valid value. For example, the property was DBPROP_MEMORYUSAGE and the value was negative. - The value was a valid value for the property and the provider supports the property as a settable property, but the provider does not support the value specified. This includes the case where the value was added to the property in OLE DB after the provider was written. + + Invalid operation. The connection is closed. - - The value of Options was invalid. + + Cannot enlist in the transaction because a local transaction is in progress on the connection. Finish local transaction and retry. - - The ColumnID element was invalid. + + Connection currently has transaction enlisted. Finish current transaction and retry. - - A '{0}' property was specified to be applied to all columns but could not be applied to one or more of them. + + Database cannot be null, the empty string, or string of only whitespace. - - The '{0}' property was read-only, or the consumer attempted to set values of properties in the Initialization property group after the data source object was initialized. Consumers can set the value of a read-only property to its current value. This status is also returned if a settable column property could not be set for the particular column. + + Invalid source buffer (size of {0}) offset: {1} - - The optional '{0}' property's value was not set to the specified value and setting the property to the specified value was not possible. + + Invalid destination buffer (size of {0}) offset: {1} - - The '{0}'property's value was not set because doing so would have conflicted with an existing property. + + Invalid attempt to {0} when stream is closed. - - (Reserved). + + Specified SeekOrigin value is invalid. - - The provider returned an unknown DBPROPSTATUS_ value '{0}'. + + Invalid attempt to read from column ordinal '{0}'. With CommandBehavior.SequentialAccess, you may only read from column ordinal '{1}' or greater. - - Accessor validation was deferred and was performed while the method returned data. The binding was invalid for this column or parameter. + + The parameter data type of {0} is invalid. - - OleDbDataAdapter internal error: invalid row set accessor: Ordinal={0} Status={1}. + + No mapping exists from object type {0} to a known managed provider native type. - - The data value could not be converted for reasons other than sign mismatch or data overflow. For example, the data was corrupted in the data store but the row was still retrievable. + + Unable to handle an unknown TypeCode {0} returned by Type {1}. - - The provider could not allocate memory in which to return {0} data. + + No mapping exists from DbType {0} to a known {1}. - - Conversion failed because the {0} data value overflowed the type specified for the {0} value part in the consumer's buffer. + + The version of SQL Server in use does not support datatype '{0}'. - - OleDbDataAdapter internal error: [get] Unknown OLE DB data type: 0x{0} ({1}). + + Parameter value '{0}' is out of range. - - Conversion failed because the {0} data value was signed and the type specified for the {0} value part in the consumer's buffer was unsigned. + + Specified parameter name '{0}' is not valid. - - OleDbDataAdapter internal error: [set] Unknown OLE DB data type: 0x{0} ({1}). + + Invalid parameter Size value '{0}'. The value must be greater than or equal to 0. - - The provider could not determine the {0} value. For example, the row was just created, the default for the {0} column was not available, and the consumer had not yet set a new {0} value. + + Invalid value for argument '{0}'. The value must be greater than or equal to 0. - - OLE DB Provider returned an unexpected status value of {0}. + + Invalid value for this metadata. - - The OleDbDataReader.Read must be used from the same thread on which is was created if that thread's ApartmentState was not ApartmentState.MTA. + + Failed to convert parameter value from a {0} to a {1}. - - Unspecified error: {0} + + {0} does not support parallel transactions. - - IErrorInfo.GetDescription failed with {0}. + + This {0} has completed; it is no longer usable. - - IErrorInfo.GetSource failed with {0}. + + Specified length '{0}' is out of range. - - DBTYPE_VECTOR data is not supported by the .NET Framework Data OLE DB Provider(Microsoft.Data.OleDb). + + Invalid {2} attempt at dataIndex '{0}'. With CommandBehavior.SequentialAccess, you may only read from dataIndex '{1}' or greater. Invalid min or max pool size values, min pool size cannot be greater than the max pool size. - - The '{0}' keyword is obsolete. Use '{1}' instead. - Unable to get the address of the distributed transaction coordinator for the server, from the server. Is DTC enabled on the server? - - The length of the value for the connection parameter <{0}> exceeds the maximum allowed 65535 characters. - Invalid 'Packet Size'. The value must be an integer >= 512 and <= 32768. Invalid transaction or invalid name for a point at which to save within the transaction. - - The {0} enumeration value, {1}, is not supported by SQL Server 7.0 or SQL Server 2000. - User Instance and Failover are not compatible options. Please choose only one of the two in the connection string. @@ -2499,33 +540,21 @@ Cannot set the Credential property if 'Authentication=Active Directory Integrated' has been specified in the connection string. - - Incorrect authentication parameters specified with certificate authentication. - The instance of SQL Server you attempted to connect to requires encryption but this machine does not support it. The instance of SQL Server you attempted to connect to does not support encryption. - - The instance of SQL Server you attempted to connect to does not support CTAIP. - Unsupported SQL Server version. The .NET Framework SqlClient Data Provider can only be used with SQL Server versions 7.0 and later. {0} cannot be changed while async operation is in progress. - - 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. - Instance failure. @@ -2556,27 +585,18 @@ Error code 0x{0} - - ChangePassword requires SQL Server 9.0 or later. - SSE Instance re-direction is not supported for non-local user instances. The authentication '{0}' is not supported. - - SQL authentication method '{0}' is not supported. - Failed to instantiate an authentication provider with type '{1}' for '{0}'. Failed to instantiate a SqlAuthenticationInitializer with type '{0}'. - - The provider '{0}' threw an exception while initializing. - The provider '{0}' does not support authentication '{1}'. @@ -2586,24 +606,15 @@ Cannot find an authentication provider for '{0}'. Install the 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet package (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) to use Active Directory (Entra ID) authentication methods. - - Failed to read the config section for authentication providers. - Parameter '{0}' cannot be null or empty. - - The asynchronous operation has already completed. - The command execution cannot proceed due to a pending asynchronous operation already in progress. Invalid command sent to ExecuteXmlReader. The command must return an Xml result. - - Notifications require SQL Server 9.0 or later. - Invalid 3 part name format for UdtTypeName. @@ -2697,9 +708,6 @@ Cannot initialize SSPI package. - - Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. - Connection Timeout Expired. The timeout period elapsed at the start of the pre-login phase. This could be because of insufficient time provided for connection timeout. @@ -2745,12 +753,6 @@ A user instance was requested in the connection string but the server specified does not support this option. - - The server certificate failed application validation. - - - Specified data length {0} exceeds the allowed maximum length of {1}. - Invalid attempt to read when no data is present. @@ -2775,9 +777,6 @@ Data length '{0}' is less than 0. - - '{0}' cannot be called when the SqlDataRecord is read only. - The mapped collection is in use and cannot be accessed at this time; @@ -2847,21 +846,6 @@ Connecting to the context connection using Microsoft.Data.SqlClient is not supported. - - Nested TransactionScopes are not supported. - - - Unexpected server event: {0}. - - - The length of '{0}' must match the length of '{1}'. - - - The SqlDbType '{0}' is invalid for {1}. Only {2} is supported. - - - Too many values. - The Stream does not support writing. @@ -2886,192 +870,36 @@ Null - - Message - - - Arithmetic Overflow. - - - Divide by zero error encountered. - - - Data is Null. This method or property cannot be called on Null values. - - - Numeric arithmetic causes truncation. - SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - - Two strings to be concatenated have different collation. - - - Two strings to be compared have different collation. - - - Invalid flag value. - - - Conversion from SqlDecimal to Decimal overflows. - Conversion overflows. - - Invalid SqlDateTime. - - - A time zone was specified. SqlDateTime does not support time zones. - Invalid array size. Invalid numeric precision/scale. - - The input wasn't in a correct format. - - - SqlType error. - - - The SqlBytes and SqlChars don't support length of more than 2GB in this version. - An error occurred while reading. - - Stream has been closed or disposed. - - - There is no buffer. Read or write operation failed. - - - Cannot set to non-zero length, because current value is Null. - - - The buffer is insufficient. Read or write operation failed. - - - Cannot write to non-zero offset, because current value is Null. - - - Cannot write from an offset that is larger than current length. It would leave uninitialized data in the buffer. - Data returned is larger than 2Gb in size. Use SequentialAccess command behavior in order to get all of the data. - - Argument to GetDayOfWeek must be integer between 1 and 7. - - - SQL Type has not been loaded with data. - - - SQL Type has already been loaded with data. - - - Invalid attempt to access a closed XmlReader. - - - Invalid attempt to call {0} when the stream is closed. - - - Invalid attempt to call {0} when the stream non-writable. - - - Invalid attempt to call {0} when the stream non-readable. - - - Invalid attempt to call {0} when the stream is non-seekable. - - - Subclass did not override a required method. - Cannot create normalizer for '{0}'. Internal Error - - Command parameter must have a non null and non empty command text. - - - MetaData parameter array must have length equivalent to ParameterDirection array argument. - - - Data adapter mapping error. - - - Data adapter error. - - - DB concurrency violation. - Operation aborted. Operation aborted due to an exception (see InnerException for details). - - Whether or not Fill will call DataRow.AcceptChanges. - - - Whether or not Update will call DataRow.AcceptChanges. - - - Whether or not to continue to the next DataRow when the Update events, RowUpdating and RowUpdated, Status is UpdateStatus.ErrorsOccurred. - - - How the adapter fills the DataTable from the DataReader. - - - The action taken when a table or column in the TableMappings is missing. - - - The action taken when a table or column in the DataSet is missing. - - - How to map source table to DataSet table. - - - Event triggered when a recoverable error occurs during Fill. - - - Should Fill return provider specific values or common CLSCompliant values. - - - DataColumn.ColumnName - - - Source column name - case sensitive. - - - The number of items in the collection - - - The specified DataColumnMapping object. - - - Individual columns mappings when this table mapping is matched. - - - DataTable.TableName - - - The DataTableMapping source table name. This name is case sensitive. - - - The number of items in the collection - - - The specified DataTableMapping object - Used during Update for deleted rows in DataSet. @@ -3090,63 +918,9 @@ Event triggered after every DataRow during Update. - - Number of rows to batch together before executing against the data source. - - - Connection used if the the Select/Insert/Update/DeleteCommands do not already have a connection. - - - Used during Update for deleted rows in the DataTable. - - - Used during Update for new rows in the DataTable. - - - Used during Fill. - - - Used during Update for modified rows in the DataTable. - - - Should Fill return provider specific values or common CLSCompliant values. - - - How to map source table to DataTable. - - - How are the Insert/Update/DeleteCommands generated when not set by the user. - - - Number of rows to batch together before executing against the data source. - - - The connection string used to connect to the Data Source. - - - The name of the ODBC Driver to use when connecting to the Data Source. - - - The DSN to use when connecting to the Data Source. - - - When true, indicates that managed connection pooling should be used. - - - The UDL file to use when connecting to the Data Source. - - - Specifies which OLE DB Services to enable or disable with the OleDb Provider. - - - The name of the OLE DB Provider to use when connecting to the Data Source. - The name of the application. - - When true, enables usage of the Asynchronous functionality in the .NET Framework Data Provider. - The name of the primary file, including the full path name, of an attachable database. @@ -3156,9 +930,6 @@ When true, indicates the connection state is reset when removed from the pool. - - When true, indicates the connection should be from the Sql Server context. Available only when running in the Sql Server process. - The SQL Server Language record name. @@ -3252,54 +1023,9 @@ Specifies the method of authenticating with SQL Server. - - Specified client certificate for authenticating with SQL Server. - - - Information used to connect to a Data Source. - - - Current connection timeout value, not settable in the ConnectionString. - - - Current data source catalog value, 'Database=X' in the connection string. - - - Current data source, 'Server=X' in the connection string. - - - Current ODBC driver. - - - Version of the product accessed by the ODBC Driver. - - - Information used to connect to a Data Source. - - - Current connection timeout value, 'Connect Timeout=X' in the ConnectionString. - - - Current data source catalog value, 'Initial Catalog=X' in the connection string. - - - Current data source, 'Data Source=X' in the connection string. - - - Current OLE DB provider ProgID, 'Provider=X' in the connection string. - - - Version of the product accessed by the OLE DB Provider. - Access token to use for authentication. - - State of connection, synchronous or asynchronous. 'Asynchronous Processing=x' in the connection string. - - - Information used to connect for replication. - Information used to connect to a DataSource, such as 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'. @@ -3324,9 +1050,6 @@ Collect statistics for this connection. - - Custom column encryption key store providers. - A guid to represent the physical connection. @@ -3363,99 +1086,9 @@ Automatic enlistment in notifications used by Microsoft SQL Server. - - How the where clause is auto-generated for the Update and Delete commands when not specified by the user. - - - Indicates the position of the catalog name in a qualified table name in a text command. - - - The character that separates the catalog name from the rest of the identifier in a text command. - - - The character that separates the schema name from the rest of the identifier in a text command. - - - The prefix string wrapped around sql objects. - - - The suffix string wrapped around sql objects. - - - The DataAdapter for which to automatically generate Commands. - - - Use schema from DataTable or the SelectCommand. - - - How the set clause is auto-generated for the Update command when not specified by the user. - - - The DataAdapter for which to automatically generate OdbcCommands - - - The character used in a text command as the opening quote for quoting identifiers that contain special characters. - - - The character used in a text command as the closing quote for quoting identifiers that contain special characters. - - - The DataAdapter for which to automatically generate OleDbCommands - - - The decimal separator used in numeric literals. - - - The prefix string wrapped around sql objects - - - The suffix string wrapped around sql objects - The DataAdapter for which to automatically generate SqlCommands - - The decimal separator used in numeric literals. - - - The character used in a text command as the opening quote for quoting identifiers that contain special characters. - - - The character used in a text command as the closing quote for quoting identifiers that contain special characters. - - - Only necessary to set for decimal and numeric parameters when using with Prepare, FillSchema and CommandBuilder scenarios. - - - Only necessary to set for decimal and numeric parameters when using with Prepare, FillSchema and CommandBuilder scenarios. - - - The parameter native type. - - - The parameter native type. - - - Name of the parameter, like '@p1' - - - The parameter native type. - - - The server's name for the type. - - - Offset in variable length data types. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - ParameterDirection '{0}' specified for parameter '{1}' is not supported. Table-valued parameters only support ParameterDirection.Input. @@ -3468,9 +1101,6 @@ TypeName specified for parameter '{0}'. TypeName must only be set for Structured parameters. - - SqlDbType.Structured type is only supported for multiple valued types. - DateType column for field '{0}' in schema table is null. DataType must be non-null. @@ -3483,18 +1113,9 @@ Number of fields in record '{0}' does not match the number in the original record. - - range: 0-8000 - unexpected error encountered in SqlClient data provider. {0} - - Unable to get Type Info for {0},{1} - - - The provider has failed to load the following assembly: {0} - UdtTypeName property must be set for UDT parameters. @@ -3504,72 +1125,9 @@ Specified type is not registered on the target server.{0}. - - UDT parameters not permitted in the where clause unless part of the primary key. - - - '{0}' is an invalid user defined type, reason: {1}. - - - supports both in-memory and user-defined formats - - - Native format can't be supported. - - - does not implement IBinarySerialize - - - not serializable - - - no public constructors - - - does not implement INullable - - - does not have a public constructor - no UDT attribute - - Serialization without mapping is not yet supported. - - - 'public static x Parse(System.Data.SqlTypes.SqlString)' method is missing - - - 'public override string ToString()' method is missing - - - 'public static x Null { get; }' method is missing - - - Native format does not support fields (directly or through another field) of type '{0}' - - - Type is not public - - - Native UDT not sequential layout due to type '{0}' - - - Native UDT specifies a max byte size - - - field '{0}' is marked non-serialized - - - The type of field '{0}' is marked as explicit layout which is not allowed in Native format - - - Multiple valued assembly references must have a nonzero Assembly Id. - - - Structured, multiple-valued types can only be used for parameters, and cannot be nested within another type. - The path name is not valid. @@ -3585,9 +1143,6 @@ Failure while attempting to promote transaction. - - Dependency object used to receive query notifications. - Property to indicate if this dependency is invalid. @@ -3600,9 +1155,6 @@ To add a command to existing dependency object. - - Command is already associated with another dependency object. Can not overwrite. - This SqlCommand object is already associated with another SqlDependency object. @@ -3630,12 +1182,6 @@ Timeout specified is invalid. Timeout cannot be < 0. - - Notification Error. Type={0}, Info={1}, Source={2}. - - - GetMetaData is not valid for this SqlDbType. - The dbType {0} is invalid for this constructor. @@ -4104,9 +1650,6 @@ Internal error. Plaintext value cannot be null. - - Cannot encrypt. Encrypting resulted in {0} bytes of ciphertext which exceeds the maximum allowed limit of {1} bytes. The specified plaintext value is likely too large (plaintext size is: {2} bytes). - Internal error. Ciphertext value cannot be null. @@ -4224,9 +1767,6 @@ Attestation information was not returned by SQL Server. Enclave type is '{0}' and enclave attestation URL is '{1}'. - - Unable to establish secure channel. Error Message: {0} - Internal Error. Null argument '{0}' specified when constructing an object of type '{1}'. '{0}' cannot be null. @@ -4242,15 +1782,6 @@ Specifies an endpoint of an enclave attestation service, which will be used to verify whether the enclave, configured in the SQL Server instance for computations on database columns encrypted using Always Encrypted, is valid and secure. - - Failed to read the configuration section for enclave providers. Make sure the section is correctly formatted in your application configuration file. Error Message: {0} - - - Failed to instantiate an enclave provider with type '{1}' for name '{0}'. Error message: {2} - - - Internal Error. SqlColumnEncryptionEnclaveProviderName cannot be null or empty. - Error occurred when reading '{0}' resultset. Attestation URL has not been specified in the connection string, but the query requires enclave computations. Enclave type is '{1}'. @@ -4260,9 +1791,6 @@ Internal Error. Enclave type received from SQL Server is null or empty when executing a query requiring enclave computations. - - Executing a query requires enclave computations, but the application configuration is missing the enclave provider section. - No enclave provider found for enclave type '{0}' and attestation protocol '{1}'. Please specify the correct attestation protocol in the connection string. @@ -4320,9 +1848,6 @@ Default column encryption setting for all the commands on the connection. - - Forces parameter to be encrypted before sending sensitive data to server. - Dictionary object containing SQL Server names and their trusted column master key paths. @@ -4332,9 +1857,6 @@ Connecting to a mirrored SQL Server instance using the ApplicationIntent ReadOnly connection option is not supported. - - Unexpected routing information received. - Invalid routing information received. @@ -4485,9 +2007,6 @@ Unsupported authentication specified in this context: {0} - - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. @@ -4497,9 +2016,6 @@ Server Process Id (SPID) of the active connection. - - Active Directory Device Code Flow authentication timed out. The user took too long to respond to the authentication request. - Cannot set the Credential property if 'Authentication=Active Directory Device Code Flow' has been specified in the connection string. @@ -4533,9 +2049,6 @@ Only single dimensional arrays are supported for the requested action. - - Cannot remove the specified item because it was not found in the specified Collection. - Non-negative number required. @@ -4563,87 +2076,36 @@ Cannot set the AccessTokenCallback property if 'Authentication=Active Directory Default' has been specified in the connection string. - - {0} returned {1}. - - - Protocol error: A received message contains a valid signature but it was not encrypted as required by the effective Protection Level. - - - {0} failed with error {1}. - This method is not implemented by this class. - - The requested security package is not supported. - Incorrect physicalConnection type. Exception occurred while trying to set the AppContext Switch '{0}'={1}. - - Type {0} is not supported on this platform. - Globalization Invariant Mode is not supported. - - 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. - The keyword 'Network Library' is not supported on this platform, prefix the 'Data Source' with the protocol desired instead ('tcp:' for a TCP connection, or 'np:' for a Named Pipe connection). Socket did not throw expected '{0}' with error code '{1}'. - - The server is attempting to use a feature that is not supported on this platform. - The keyword '{0}' is not supported on this platform. The currently loaded System.Transactions.dll does not support Global Transactions. - - Received an unsupported token '{0}' while reading data from the server. - - - '{0}' is not a supported handle type. - SqlFileStream is not supported on this platform. LocalDB is not supported on this platform. - - Insufficient buffer space. Required: {0} Actual: {1}. - - - GSSAPI operation failed with status: {0} (Minor status: {1}). - - - GSSAPI operation failed with error - {0} ({1}). - - - No support for channel binding on operating systems other than Windows. - - - Target name should be non empty if default credentials are passed. - - - Requested protection level is not supported with the GSSAPI implementation currently installed. - - - Server implementation is not supported. - - - NTLM authentication is not possible with default credentials on this platform. - When used by DataAdapter.Update, the parameter value is changed from DBNull.Value into (Int32)1 or (Int32)0 if non-null. @@ -4668,9 +2130,6 @@ Buffer maximum size exceeded: {0} - - Missing the DataColumn '{0}' for the SourceColumn '{2}'. - SqlBatchCommand list has not been initialized. 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 118f709d89..01c539043c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -497,8 +497,7 @@ private static void TypeRead(string connectionString) s = rdr.GetString(9); //ShipAddres; s = rdr.GetString(10); //ShipCity; // should get an exception here - string errorMessage = SystemDataResourceManager.Instance.SqlMisc_NullValueMessage; - DataTestUtility.AssertThrows(() => rdr.GetString(11), errorMessage); + DataTestUtility.AssertThrows(() => rdr.GetString(11)); s = rdr.GetString(12); //ShipPostalCode; s = rdr.GetString(13); //ShipCountry; @@ -516,8 +515,6 @@ private static void GetValueOfTRead(string connectionString) using (SqlCommand cmd = new SqlCommand(sqlBatch, conn)) using (SqlDataReader rdr = cmd.ExecuteReader()) { - string errorMessage = SystemDataResourceManager.Instance.SqlMisc_NullValueMessage; - rdr.Read(); // read data out of buffer rdr.GetFieldValue(0); //order id @@ -534,7 +531,7 @@ private static void GetValueOfTRead(string connectionString) rdr.IsDBNull(10); rdr.GetFieldValue(10); //ShipCity; // should get an exception here - DataTestUtility.AssertThrows(() => rdr.GetFieldValue(11), errorMessage); + DataTestUtility.AssertThrows(() => rdr.GetFieldValue(11)); rdr.IsDBNull(11); rdr.GetFieldValue(11); rdr.IsDBNull(11); @@ -543,7 +540,7 @@ private static void GetValueOfTRead(string connectionString) rdr.IsDBNull(12); rdr.GetFieldValue(13);//ShipCountry; rdr.GetFieldValue(14); - DataTestUtility.AssertThrows(() => rdr.GetFieldValue(15), errorMessage); + DataTestUtility.AssertThrows(() => rdr.GetFieldValue(15)); rdr.Read(); // read data out of buffer @@ -560,7 +557,7 @@ private static void GetValueOfTRead(string connectionString) Assert.False(rdr.IsDBNullAsync(10).Result, "FAILED: IsDBNull was true for a non-null value"); rdr.GetFieldValueAsync(10).Wait(); //ShipCity; // should get an exception here - DataTestUtility.AssertThrowsInner(() => rdr.GetFieldValueAsync(11).Wait(), innerExceptionMessage: errorMessage); + DataTestUtility.AssertThrowsInner(() => rdr.GetFieldValueAsync(11).Wait()); Assert.True(rdr.IsDBNullAsync(11).Result, "FAILED: IsDBNull was false for a null value"); rdr.IsDBNullAsync(11).Wait(); From baacd3ba50460f20958073af51e45455094bface Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Fri, 24 Apr 2026 14:36:31 +0100 Subject: [PATCH 064/298] Cleanup | Always Encrypted cryptographic algorithm factories (#4138) --- .../AeadAes256CbcHmac256Factory.cs | 84 +++++++++++++++++++ .../EncryptionAlgorithmFactory.cs | 24 ++++++ .../EncryptionAlgorithmFactoryList.cs | 37 ++++++++ .../Data/SqlClient/EnclaveDelegate.cs | 6 +- .../SqlAeadAes256CbcHmac256Algorithm.cs | 12 ++- .../SqlAeadAes256CbcHmac256Factory.cs | 81 ------------------ .../SqlClientEncryptionAlgorithmFactory.cs | 23 ----- ...SqlClientEncryptionAlgorithmFactoryList.cs | 84 ------------------- .../Data/SqlClient/SqlSecurityUtility.cs | 5 +- .../AlwaysEncryptedTests/Utility.cs | 13 +-- .../AlwaysEncrypted/NativeAeadBaseline.cs | 7 +- 11 files changed, 172 insertions(+), 204 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionAlgorithmFactory.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionAlgorithmFactoryList.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs new file mode 100644 index 0000000000..2a8ebcd6b0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/AeadAes256CbcHmac256Factory.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Text; + +#nullable enable + +namespace Microsoft.Data.SqlClient.AlwaysEncrypted +{ + /// + /// This is a factory class for the AEAD_AES_256_CBC_HMAC_SHA256 encryption algorithm. + /// + /// + /// Access this factory directly via its property or by passing + /// the algorithm name defined in + /// to the method. + /// + internal sealed class AeadAes256CbcHmac256Factory : EncryptionAlgorithmFactory + { + /// + /// Factory classes cache the objects to avoid recomputation of the derived keys. + /// + private readonly ConcurrentDictionary _encryptionAlgorithms = + new(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary */, capacity: 2); + + private AeadAes256CbcHmac256Factory() { } + + /// + /// Access the instance of the factory class for the AEAD_AES_256_CBC_HMAC_SHA256 encryption algorithm. + /// + public static AeadAes256CbcHmac256Factory Instance => field ??= new(); + + /// + /// Creates an instance of the class with a given root key. + /// + /// Root key. + /// Encryption type. Expected values are either Deterministic or Randomized. + /// Cryptographic algorithm. + /// An implementation of the AEAD_AES_256_CBC_HMAC_SHA256 cryptographic algorithm. + internal override SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm) + { + // Callers should have validated the encryption algorithm and the encryption key + Debug.Assert(string.Equals(encryptionAlgorithm, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, StringComparison.OrdinalIgnoreCase)); + + // Validate encryption type + if (encryptionType is not SqlClientEncryptionType.Deterministic and not SqlClientEncryptionType.Randomized) + { + throw SQL.InvalidEncryptionType(SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, + encryptionType, + SqlClientEncryptionType.Deterministic, + SqlClientEncryptionType.Randomized); + } + + // Get the cached cryptographic algorithm if one exists or create a new one, add it to cache and use it + int capacity = SqlSecurityUtility.GetBase64LengthFromByteLength(encryptionKey.RootKey.Length) + 4 /* Separators, type and version */; + StringBuilder algorithmKeyBuilder = new(Convert.ToBase64String(encryptionKey.RootKey), capacity); + + algorithmKeyBuilder.Append(':'); + algorithmKeyBuilder.Append((int)encryptionType); + algorithmKeyBuilder.Append(':'); + algorithmKeyBuilder.Append(SqlAeadAes256CbcHmac256Algorithm.CurrentVersion); + + string algorithmKey = algorithmKeyBuilder.ToString(); + + Debug.Assert(algorithmKey.Length <= capacity, "We needed to allocate a larger array"); + + if (!_encryptionAlgorithms.TryGetValue(algorithmKey, out SqlAeadAes256CbcHmac256Algorithm? aesAlgorithm)) + { + SqlAeadAes256CbcHmac256EncryptionKey encryptedKey = new(encryptionKey.RootKey, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName); + aesAlgorithm = new SqlAeadAes256CbcHmac256Algorithm(encryptedKey, encryptionType, SqlAeadAes256CbcHmac256Algorithm.CurrentVersion); + + // In case multiple threads reach here at the same time, the first one adds the value + // the second one will be a no-op, the allocated memory will be claimed by Garbage Collector. + _encryptionAlgorithms.TryAdd(algorithmKey, aesAlgorithm); + } + + return aesAlgorithm; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.cs new file mode 100644 index 0000000000..545a581883 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactory.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. + +#nullable enable + +namespace Microsoft.Data.SqlClient.AlwaysEncrypted; + +/// +/// Abstract base class for all TCE cryptographic algorithm factory classes. Factory classes create instances of a cryptographic algorithm +/// with a given key. At runtime when we determine a particular column is marked for TCE, based on the cryptographic algorithm we invoke +/// the corresponding factory class and retrieve an implementation of an cryptographic algorithm. +/// +internal abstract class EncryptionAlgorithmFactory +{ + /// + /// Creates a cryptographic algorithm with a given key. + /// + /// Root key that should be passed to the cryptographic algorithm to be created + /// Encryption Type, some algorithms will need this + /// Cryptographic algorithm name. Needed for extracting version bits + /// Return a newly created SqlClientEncryptionAlgorithm instance + internal abstract SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm); +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs new file mode 100644 index 0000000000..0b28dd9ef1 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptionAlgorithmFactoryList.cs @@ -0,0 +1,37 @@ +// 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.AlwaysEncrypted; + +/// +/// Implements a global mapping from a SQL Server cryptographic algorithm's name to its implementation. +/// +internal static class EncryptionAlgorithmFactoryList +{ + /// + /// Get the available list of algorithms as a comma separated list with algorithm names + /// wrapped in single quotes. + /// + public const string RegisteredCipherAlgorithmNames = $"'{SqlAeadAes256CbcHmac256Algorithm.AlgorithmName}'"; + + /// + /// Gets the implementation for a given algorithm and instantiates it using the provided root key and the encryption type. + /// + /// The root key to use. + /// Encryption type (read from SQL Server.) + /// Name of the cryptographic algorithm. + /// Specified cryptographic algorithm's implementation. + public static void GetAlgorithm(SqlClientSymmetricKey key, byte type, string algorithmName, out SqlClientEncryptionAlgorithm encryptionAlgorithm) + { + EncryptionAlgorithmFactory factory = algorithmName switch + { + SqlAeadAes256CbcHmac256Algorithm.AlgorithmName => AeadAes256CbcHmac256Factory.Instance, + _ => throw SQL.UnknownColumnEncryptionAlgorithm(algorithmName, RegisteredCipherAlgorithmNames) + }; + + encryptionAlgorithm = factory.Create(key, (SqlClientEncryptionType)type, algorithmName); + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs index 79b88d6788..dcd5fd200a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.AlwaysEncrypted; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -16,7 +17,6 @@ namespace Microsoft.Data.SqlClient // @TODO: This isn't a delegate... it's a utility class internal sealed partial class EnclaveDelegate { - private static readonly SqlAeadAes256CbcHmac256Factory s_sqlAeadAes256CbcHmac256Factory = new SqlAeadAes256CbcHmac256Factory(); private static readonly EnclaveDelegate s_enclaveDelegate = new EnclaveDelegate(); private readonly object _lock; @@ -152,7 +152,7 @@ private byte[] EncryptBytePackage(byte[] bytePackage, byte[] sessionKey, string try { SqlClientSymmetricKey symmetricKey = new SqlClientSymmetricKey(sessionKey); - SqlClientEncryptionAlgorithm sqlClientEncryptionAlgorithm = s_sqlAeadAes256CbcHmac256Factory.Create( + SqlClientEncryptionAlgorithm sqlClientEncryptionAlgorithm = AeadAes256CbcHmac256Factory.Instance.Create( symmetricKey, SqlClientEncryptionType.Randomized, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs index 1035d87062..307330af0a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Algorithm.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -22,6 +22,14 @@ internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm /// internal const string AlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA256"; + /// + /// Algorithm version + /// + /// + /// For now, only one version exists. In future, this may be derived from the algorithm name. + /// + public const byte CurrentVersion = 0x01; + /// /// Key size in bytes /// @@ -77,7 +85,7 @@ internal class SqlAeadAes256CbcHmac256Algorithm : SqlClientEncryptionAlgorithm /// /// Byte array with algorithm version used for authentication tag computation. /// - private static readonly byte[] _version = new byte[] { 0x01 }; + private static readonly byte[] _version = [ CurrentVersion ]; /// /// Byte array with algorithm version size used for authentication tag computation. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.cs deleted file mode 100644 index 7501e7ba08..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAeadAes256CbcHmac256Factory.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. - -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Text; - -namespace Microsoft.Data.SqlClient -{ - /// - /// This is a factory class for AEAD_AES_256_CBC_HMAC_SHA256 - /// - internal class SqlAeadAes256CbcHmac256Factory : SqlClientEncryptionAlgorithmFactory - { - /// - /// Factory classes caches the SqlAeadAes256CbcHmac256EncryptionKey objects to avoid computation of the derived keys - /// - private readonly ConcurrentDictionary _encryptionAlgorithms = - new ConcurrentDictionary(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, capacity: 2); - - /// - /// Creates an instance of AeadAes256CbcHmac256Algorithm class with a given key - /// - /// Root key - /// Encryption Type. Expected values are either Deterministic or Randomized. - /// Encryption Algorithm. - /// - internal override SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm) - { - // Callers should have validated the encryption algorithm and the encryption key - Debug.Assert(encryptionKey != null); - Debug.Assert(string.Equals(encryptionAlgorithm, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, StringComparison.OrdinalIgnoreCase) == true); - - // Validate encryption type - if (!((encryptionType == SqlClientEncryptionType.Deterministic) || (encryptionType == SqlClientEncryptionType.Randomized))) - { - throw SQL.InvalidEncryptionType(SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, - encryptionType, - SqlClientEncryptionType.Deterministic, - SqlClientEncryptionType.Randomized); - } - - // Get the cached encryption algorithm if one exists or create a new one, add it to cache and use it - // - // For now, we only have one version. In future, we may need to parse the algorithm names to derive the version byte. - const byte algorithmVersion = 0x1; - - StringBuilder algorithmKeyBuilder = new StringBuilder(Convert.ToBase64String(encryptionKey.RootKey), SqlSecurityUtility.GetBase64LengthFromByteLength(encryptionKey.RootKey.Length) + 4/*separators, type and version*/); - -#if DEBUG - int capacity = algorithmKeyBuilder.Capacity; -#endif //DEBUG - - algorithmKeyBuilder.Append(":"); - algorithmKeyBuilder.Append((int)encryptionType); - algorithmKeyBuilder.Append(":"); - algorithmKeyBuilder.Append(algorithmVersion); - - string algorithmKey = algorithmKeyBuilder.ToString(); - -#if DEBUG - Debug.Assert(algorithmKey.Length <= capacity, "We needed to allocate a larger array"); -#endif //DEBUG - - SqlAeadAes256CbcHmac256Algorithm aesAlgorithm; - if (!_encryptionAlgorithms.TryGetValue(algorithmKey, out aesAlgorithm)) - { - SqlAeadAes256CbcHmac256EncryptionKey encryptedKey = new SqlAeadAes256CbcHmac256EncryptionKey(encryptionKey.RootKey, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName); - aesAlgorithm = new SqlAeadAes256CbcHmac256Algorithm(encryptedKey, encryptionType, algorithmVersion); - - // In case multiple threads reach here at the same time, the first one adds the value - // the second one will be a no-op, the allocated memory will be claimed by Garbage Collector. - _encryptionAlgorithms.TryAdd(algorithmKey, aesAlgorithm); - } - - return aesAlgorithm; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionAlgorithmFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionAlgorithmFactory.cs deleted file mode 100644 index 14f0f9816b..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionAlgorithmFactory.cs +++ /dev/null @@ -1,23 +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 -{ - /// - /// Abstract base class for all TCE encryption algorithm factory classes. Factory classes create instances of an encryption algorithm - /// with a given key. At runtime when we determine a particular column is marked for TCE, based on the encryption algorithm we invoke - /// the corresponding factory class and retrieve an object to an encryption algorithm. - /// - internal abstract class SqlClientEncryptionAlgorithmFactory - { - /// - /// Creates an encryption algorithm with a given key. - /// - /// encryption key that should be passed to the encryption algorithm to be created - /// Encryption Type, some algorithms will need this - /// Encryption algorithm name. Needed for extracting version bits - /// Return a newly created SqlClientEncryptionAlgorithm instance - internal abstract SqlClientEncryptionAlgorithm Create(SqlClientSymmetricKey encryptionKey, SqlClientEncryptionType encryptionType, string encryptionAlgorithm); - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionAlgorithmFactoryList.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionAlgorithmFactoryList.cs deleted file mode 100644 index 8266b5ce15..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEncryptionAlgorithmFactoryList.cs +++ /dev/null @@ -1,84 +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.Concurrent; -using System.Diagnostics; -using System.Text; - -namespace Microsoft.Data.SqlClient -{ - /// - /// Implements a global directory of all the encryption algorithms registered with client. - /// - sealed internal class SqlClientEncryptionAlgorithmFactoryList - { - private readonly ConcurrentDictionary _encryptionAlgoFactoryList; - private static readonly SqlClientEncryptionAlgorithmFactoryList _singletonInstance = new SqlClientEncryptionAlgorithmFactoryList(); - - private SqlClientEncryptionAlgorithmFactoryList() - { - _encryptionAlgoFactoryList = new ConcurrentDictionary(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, capacity: 2); - - // Add wellknown algorithm - _encryptionAlgoFactoryList.TryAdd(SqlAeadAes256CbcHmac256Algorithm.AlgorithmName, new SqlAeadAes256CbcHmac256Factory()); - } - - internal static SqlClientEncryptionAlgorithmFactoryList GetInstance() - { - return _singletonInstance; - } - - /// - /// Get the registered list of algorithms as a comma separated list with algorithm names - /// wrapped in single quotes. - /// - internal string GetRegisteredCipherAlgorithmNames() - { - StringBuilder builder = new StringBuilder(); - bool firstElem = true; - foreach (string key in _encryptionAlgoFactoryList.Keys) - { - if (firstElem) - { - builder.Append("'"); - firstElem = false; - } - else - { - builder.Append(", '"); - } - builder.Append(key); - builder.Append("'"); - } - - return builder.ToString(); - } - - /// - /// Gets the algorithm handle instance for a given algorithm and instantiates it using the provided key and the encryption type. - /// - /// - /// - /// - /// - internal void GetAlgorithm(SqlClientSymmetricKey key, byte type, string algorithmName, out SqlClientEncryptionAlgorithm encryptionAlgorithm) - { - encryptionAlgorithm = null; - - SqlClientEncryptionAlgorithmFactory factory = null; - if (!_encryptionAlgoFactoryList.TryGetValue(algorithmName, out factory)) - { - throw SQL.UnknownColumnEncryptionAlgorithm(algorithmName, - SqlClientEncryptionAlgorithmFactoryList.GetInstance().GetRegisteredCipherAlgorithmNames()); - } - - Debug.Assert(factory != null, "Null Algorithm Factory class detected"); - - // If the factory exists, following method will Create an algorithm object. If this fails, - // it will raise an exception. - encryptionAlgorithm = factory.Create(key, (SqlClientEncryptionType)type, algorithmName); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs index ae20c27254..253ab92db8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs @@ -9,6 +9,7 @@ using System.Security.Cryptography; using System.Text; using Microsoft.Data.Common; +using Microsoft.Data.SqlClient.AlwaysEncrypted; namespace Microsoft.Data.SqlClient { @@ -98,7 +99,7 @@ private static string ValidateAndGetEncryptionAlgorithmName(byte cipherAlgorithm { if (cipherAlgorithmName == null) { - throw SQL.NullColumnEncryptionAlgorithm(SqlClientEncryptionAlgorithmFactoryList.GetInstance().GetRegisteredCipherAlgorithmNames()); + throw SQL.NullColumnEncryptionAlgorithm(EncryptionAlgorithmFactoryList.RegisteredCipherAlgorithmNames); } return cipherAlgorithmName; @@ -206,7 +207,7 @@ internal static void DecryptSymmetricKey(SqlCipherMetadata md, SqlConnection con md.CipherAlgorithm = null; SqlClientEncryptionAlgorithm cipherAlgorithm = null; string algorithmName = ValidateAndGetEncryptionAlgorithmName(md.CipherAlgorithmId, md.CipherAlgorithmName); // may throw - SqlClientEncryptionAlgorithmFactoryList.GetInstance().GetAlgorithm(symKey, md.EncryptionType, algorithmName, out cipherAlgorithm); // will validate algorithm name and type + EncryptionAlgorithmFactoryList.GetAlgorithm(symKey, md.EncryptionType, algorithmName, out cipherAlgorithm); // will validate algorithm name and type Debug.Assert(cipherAlgorithm is not null); md.CipherAlgorithm = cipherAlgorithm; md.EncryptionKeyInfo = encryptionkeyInfoChosen; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs index a8a76b9e7f..e53fa6491e 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs @@ -21,9 +21,10 @@ public static class Utility // reflections public static Assembly systemData = Assembly.GetAssembly(typeof(SqlConnection)); public static Type sqlClientSymmetricKey = systemData.GetType("Microsoft.Data.SqlClient.SqlClientSymmetricKey"); - public static Type sqlAeadAes256CbcHmac256Factory = systemData.GetType("Microsoft.Data.SqlClient.SqlAeadAes256CbcHmac256Factory"); + public static Type aeadAes256CbcHmac256Factory = systemData.GetType("Microsoft.Data.SqlClient.AlwaysEncrypted.AeadAes256CbcHmac256Factory"); + public static PropertyInfo aeadAes256CbcHmac256FactoryInstance = aeadAes256CbcHmac256Factory.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); public static ConstructorInfo sqlColumnEncryptionKeyConstructor = sqlClientSymmetricKey.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(byte[]) }, null); - public static MethodInfo sqlAeadAes256CbcHmac256FactoryCreate = sqlAeadAes256CbcHmac256Factory.GetMethod("Create", BindingFlags.Instance | BindingFlags.NonPublic); + public static MethodInfo aeadAes256CbcHmac256FactoryCreate = aeadAes256CbcHmac256Factory.GetMethod("Create", BindingFlags.Instance | BindingFlags.NonPublic); public static Type sqlClientEncryptionAlgorithm = systemData.GetType("Microsoft.Data.SqlClient.SqlClientEncryptionAlgorithm"); public static MethodInfo sqlClientEncryptionAlgorithmEncryptData = sqlClientEncryptionAlgorithm.GetMethod("EncryptData", BindingFlags.Instance | BindingFlags.NonPublic); public static Type SqlCipherMetadata = systemData.GetType("Microsoft.Data.SqlClient.SqlCipherMetadata"); @@ -191,11 +192,11 @@ internal static byte[] EncryptDataUsingAED(byte[] plainTextData, byte[] key, CCo Object columnEncryptionKey = sqlColumnEncryptionKeyConstructor.Invoke(new object[] { key }); Assert.True(columnEncryptionKey != null); - Object aesFactory = Activator.CreateInstance(sqlAeadAes256CbcHmac256Factory); + Object aesFactory = aeadAes256CbcHmac256FactoryInstance.GetValue(null); Assert.True(aesFactory != null); object[] parameters = new object[] { columnEncryptionKey, encryptionType, ColumnEncryptionAlgorithmName }; - Object authenticatedAES = sqlAeadAes256CbcHmac256FactoryCreate.Invoke(aesFactory, parameters); + Object authenticatedAES = aeadAes256CbcHmac256FactoryCreate.Invoke(aesFactory, parameters); Assert.True(authenticatedAES != null); parameters = new object[] { plainTextData }; @@ -259,11 +260,11 @@ internal static byte[] DecryptDataUsingAED(byte[] encryptedCellBlob, byte[] key, Object columnEncryptionKey = sqlColumnEncryptionKeyConstructor.Invoke(new object[] { key }); Assert.True(columnEncryptionKey != null); - Object aesFactory = Activator.CreateInstance(sqlAeadAes256CbcHmac256Factory); + Object aesFactory = aeadAes256CbcHmac256FactoryInstance.GetValue(null); Assert.True(aesFactory != null); object[] parameters = new object[] { columnEncryptionKey, encryptionType, ColumnEncryptionAlgorithmName }; - Object authenticatedAES = sqlAeadAes256CbcHmac256FactoryCreate.Invoke(aesFactory, parameters); + Object authenticatedAES = aeadAes256CbcHmac256FactoryCreate.Invoke(aesFactory, parameters); Assert.True(authenticatedAES != null); parameters = new object[] { encryptedCellBlob }; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs index 945351056f..f869bd768a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/NativeAeadBaseline.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.AlwaysEncrypted; using System; using System.Collections.Generic; using System.Linq; @@ -111,7 +112,7 @@ public class NativeAeadBaseline public void Known_Plaintext_Encrypts_To_Known_FinalCell(byte[] plainText, byte[] rootKey, byte[] expectedFinalCell) { SqlClientSymmetricKey cek = new(rootKey); - SqlAeadAes256CbcHmac256Factory aeadFactory = new(); + AeadAes256CbcHmac256Factory aeadFactory = AeadAes256CbcHmac256Factory.Instance; SqlClientEncryptionAlgorithm aeadAlgorithm = aeadFactory.Create(cek, SqlClientEncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName); byte[] encryptedData = aeadAlgorithm.EncryptData(plainText); @@ -130,7 +131,7 @@ public void Known_Plaintext_Encrypts_To_Known_FinalCell(byte[] plainText, byte[] public void Known_FinalCell_Decrypts_To_Known_Plaintext(byte[] expectedPlaintext, byte[] rootKey, byte[] finalCell) { SqlClientSymmetricKey cek = new(rootKey); - SqlAeadAes256CbcHmac256Factory aeadFactory = new(); + AeadAes256CbcHmac256Factory aeadFactory = AeadAes256CbcHmac256Factory.Instance; SqlClientEncryptionAlgorithm aeadAlgorithm = aeadFactory.Create(cek, SqlClientEncryptionType.Deterministic, SqlAeadAes256CbcHmac256Algorithm.AlgorithmName); byte[] decryptedData = aeadAlgorithm.DecryptData(finalCell); From ad22647943e8369cfaa0c19575f6b116d5674a71 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Fri, 24 Apr 2026 11:32:17 -0700 Subject: [PATCH 065/298] Revert "Update to latest Azure.Core package with Azure.Identity types (#4200)" (#4236) This reverts commit c5da95dbebf897c895485bc67012f2b3e9c6db6b. --- Directory.Packages.props | 8 ++++--- doc/Directory.Packages.props | 8 +++---- .../Directory.Packages.props | 12 +++++----- doc/apps/AzureAuthentication/README.md | 22 +++++++++---------- .../Microsoft.Data.SqlClient.Samples.csproj | 1 + .../Azure/src/Azure.csproj | 1 + ...Microsoft.Data.SqlClient.TestCommon.csproj | 4 ++-- ...soft.Data.SqlClient.FunctionalTests.csproj | 4 ++-- ...icrosoft.Data.SqlClient.ManualTests.csproj | 4 ++-- 9 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d18da0127a..b1a858bf72 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -50,8 +50,8 @@ - - + + @@ -81,7 +81,9 @@ - + + + diff --git a/doc/Directory.Packages.props b/doc/Directory.Packages.props index 3f34943e76..2ead6c1757 100644 --- a/doc/Directory.Packages.props +++ b/doc/Directory.Packages.props @@ -2,11 +2,9 @@ - - - - - + + + diff --git a/doc/apps/AzureAuthentication/Directory.Packages.props b/doc/apps/AzureAuthentication/Directory.Packages.props index 1456264d7a..b512683765 100644 --- a/doc/apps/AzureAuthentication/Directory.Packages.props +++ b/doc/apps/AzureAuthentication/Directory.Packages.props @@ -4,11 +4,11 @@ - - 7.0.0 + + 7.0.0-preview4.26064.3 - - 7.0.0 + + 7.0.0-preview1.26064.3 @@ -25,8 +25,8 @@ - - + + diff --git a/doc/apps/AzureAuthentication/README.md b/doc/apps/AzureAuthentication/README.md index 2a3bf16b86..a0d9d8b7ba 100644 --- a/doc/apps/AzureAuthentication/README.md +++ b/doc/apps/AzureAuthentication/README.md @@ -33,8 +33,8 @@ Package versions are controlled through MSBuild properties. Pass them on the com | Property | Default | Description | | --- | --- | --- | -| `SqlClientVersion` | `7.0.0` | Version of `Microsoft.Data.SqlClient` to reference. | -| `AkvProviderVersion` | `7.0.0` | Version of `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` to reference. | +| `SqlClientVersion` | `7.0.0-preview4.26064.3` | Version of `Microsoft.Data.SqlClient` to reference. | +| `AkvProviderVersion` | `7.0.0-preview1.26064.3` | Version of `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` to reference. | | `AzureVersion` | None | Version of `Microsoft.Data.SqlClient.Extensions.Azure` to reference. When omitted, the `Azure` package will not be referenced. | ## Local Package Source @@ -68,9 +68,9 @@ Description: Supply specific package versions when building to test different versions of the SqlClient suite, for example: - -p:SqlClientVersion=7.1.0.preview1 - -p:AkvProviderVersion=7.0.0 - -p:AzureVersion=1.1.0-preview1 + -p:SqlClientVersion=7.0.0.preview4 + -p:AkvProviderVersion=7.0.1-preview2 + -p:AzureVersion=1.0.0-preview1 Usage: AzureAuthentication [options] @@ -104,9 +104,9 @@ Azure Authentication Tester --------------------------- Packages used: - SqlClient: 7.0.0 - AKV Provider: 7.0.0 - Azure: 1.0.0 + SqlClient: 7.0.0-preview4.26055.1 + AKV Provider: 6.1.2 + Azure: 1.0.0-preview1.26055.1 Connection details: Data Source: adotest.database.windows.net @@ -142,19 +142,19 @@ authentication provider. Run against locally-built packages (drop `.nupkg` files into the `packages/` folder first): ```bash -dotnet run -p:SqlClientVersion=7.1.0-preview1 -- -c "" +dotnet run -p:SqlClientVersion=7.0.0-preview4 -- -c "" ``` Run including the `Azure` extensions package: ```bash -dotnet run -p:AzureVersion=1.0.0 -- -c "" +dotnet run -p:AzureVersion=1.0.0-preview1 -- -c "" ``` Override all three versions at once: ```bash -dotnet run -p:SqlClientVersion=7.1.0-preview1 -p:AkvProviderVersion=7.1.0-preview1 -p:AzureVersion=1.0.0 -- -c "" +dotnet run -p:SqlClientVersion=7.0.0-preview1 -p:AkvProviderVersion=7.0.0-preview1 -p:AzureVersion=1.0.0-preview1 -- -c "" ``` ## Prerequisites diff --git a/doc/samples/Microsoft.Data.SqlClient.Samples.csproj b/doc/samples/Microsoft.Data.SqlClient.Samples.csproj index db1d5bd997..c194fac4d0 100644 --- a/doc/samples/Microsoft.Data.SqlClient.Samples.csproj +++ b/doc/samples/Microsoft.Data.SqlClient.Samples.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj index 07caed5129..b244dd467e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj @@ -129,6 +129,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj index 82d5b2513d..3f014f9fbe 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj +++ b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj @@ -30,14 +30,14 @@ - + - + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 0e9cb7b785..7045eecc65 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -66,7 +66,7 @@ - + @@ -97,7 +97,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index c4f0f2bf5b..110547a5de 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -355,7 +355,7 @@ - + @@ -391,7 +391,7 @@ - + From 6e81f5924d1705e976c4bc35078e54e1fe0ba17f Mon Sep 17 00:00:00 2001 From: SqlClient DevOps Date: Fri, 24 Apr 2026 19:21:44 +0000 Subject: [PATCH 066/298] [Scheduled Run] Localized resource files from OneLocBuild --- .../src/Resources/Strings.cs.resx | 2785 +---------------- .../src/Resources/Strings.de.resx | 2785 +---------------- .../src/Resources/Strings.es.resx | 2785 +---------------- .../src/Resources/Strings.fr.resx | 2785 +---------------- .../src/Resources/Strings.it.resx | 2785 +---------------- .../src/Resources/Strings.ja.resx | 2785 +---------------- .../src/Resources/Strings.ko.resx | 2785 +---------------- .../src/Resources/Strings.pl.resx | 2785 +---------------- .../src/Resources/Strings.pt-BR.resx | 2785 +---------------- .../src/Resources/Strings.ru.resx | 2785 +---------------- .../src/Resources/Strings.tr.resx | 2785 +---------------- .../src/Resources/Strings.zh-Hans.resx | 2785 +---------------- .../src/Resources/Strings.zh-Hant.resx | 2785 +---------------- 13 files changed, 1586 insertions(+), 34619 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx index 82d586d022..65914fdf4c 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Vzestupně - Index {0} pro {1} je neplatný (Count={2}). @@ -132,18 +129,12 @@ {0} už obsahuje jiná kolekce {1}. - - Index {0} je již v této kolekci {1} obsažen. - {0} přijímá pouze objekty typu {1}, které nejsou null. Došlo k pokusu o odebrání objektu {0}, který není obsažen v této kolekci {1}. - - Hodnota {0}.{1} musí být jedinečná. Hodnota {2} již v kolekci existuje. - Připojení nebylo zavřeno. {0} @@ -171,12 +162,6 @@ Pokus o volání {0} je neplatný, je-li zavřeno čtecí zařízení. - - Nelze zařadit do transakce, protože připojení je primárním připojením pro delegovanou nebo povýšenou transakci. - - - Sestupně - Byl očekáván neprázdný řetězec pro parametr {0}. @@ -204,9 +189,6 @@ Vlastnost ConnectionString nebyla inicializována. - - Byla zachycena výjimka Exception neodpovídající specifikaci CLS. - Daný element zabezpečení není elementem oprávnění. @@ -252,9 +234,6 @@ Data - - StateChange - Aktualizovat @@ -264,48 +243,12 @@ Vlastnost ConnectionState udávající, zda je připojení otevřeno nebo zavřeno. - - Událost aktivovaná při změně stavu připojení. - - - Obecný typ parametru. - - - Vstupní, výstupní nebo obousměrný parametr - - - vlastnost existující v době návrhu použitá pro generování kódu silného typu. - - - Posun v datových typech proměnné délky. - - - Název parametru - - - Velikost datových typů proměnné délky (řetězce a pole) - - - Při použití v metodě DataAdapter.Update se jedná o název zdrojového sloupce, který slouží k vyhledání názvu DataSetColumn ve vlastnosti ColumnMappings. Používá se ke kopírování hodnoty mezi parametrem a řádkem dat. - - - Při použití v metodě DataAdapter.Update (pouze vlastnost UpdateCommand) se jedná o verzi hodnoty objektu DataRow, která slouží k aktualizaci zdroje dat. - - - Při použití v metodě DataAdapter.Update je hodnota parametru změněna z DBNull.Value na (Int32)1 nebo (Int32)0, pokud je hodnota jiná než null. - - - Hodnota parametru. - Kolekci {0} se nepovedlo sestavit, protože se nepovedlo spustit příkaz jazyka SQL. Podrobnosti naleznete v popisu vnitřní výjimky. Bylo zadáno více omezení, než podporuje požadované schéma ({0}). - - {2} není platná hodnota pro omezení {1} kolekce schématu {0}. - Požadovaná kolekce ({0}) není definována. @@ -315,15 +258,6 @@ Požadovaná kolekce ({0}) není podporována touto verzí zprostředkovatele. - - Chybí jeden z požadovaných sloupců tabulek DataSourceInformation. - - - Tabulka DataSourceInformation musí obsahovat přesně jeden řádek. - - - Chybí minimálně jeden z požadovaných sloupců kolekce omezení. - Existuje omezení, pro které v kolekci omezení neexistuje odpovídající řádek. @@ -342,42 +276,21 @@ V kódu XML metadat chybí kolekce {0}. - - Jazyk XML metadat je neplatný. - - - Jazyk XML metadat je neplatný. Kolekce {0} musí obsahovat sloupec {1} a musí se jednat o sloupec řetězce. - Jazyk XML metadat je neplatný. Sloupec {1} kolekce {0} musí obsahovat neprázdný řetězec. - - Akce - - - Chování - Výplň InfoMessage - - Mapování - StatementCompleted - - UDT - Oznámení - - Schéma - XML @@ -393,9 +306,6 @@ Sdružování - - Pojmenovaný připojovací řetězec ConnectionString - Zabezpečení @@ -408,2073 +318,204 @@ Odolnost připojení - - Kolekce obsahující vlastní uživatelské informace. - - - Určuje, zda se při porovnání řetězců v rámci objektu DataSet rozlišují malá a velká písmena. - - - Název tohoto objektu DataSet - - - Určuje vlastní zobrazení dat obsažených v objektu DataSet. Toto zobrazení umožňuje filtrování, hledání a navigaci ve vlastním zobrazení dat. - - - Určuje, zda musí být dodržována pravidla omezení. - - - Určuje, zda objekt DataSet obsahuje chyby. - - - Určuje národní prostředí, ve kterém bude probíhat porovnávání řetězců v rámci objektu DataSet. - - - Určuje obor názvů jazyka XML určený identifikátorem URI pro kořenový element, na který odkazuje tento objekt DataSet. - - - Určuje předponu oboru názvů použitého pro tento objekt DataSet. - - - Kolekce obsahující relace pro tento objekt DataSet. - - - Kolekce obsahující tabulky pro tento objekt DataSet - - - Vyvolá se, když není možné sloučit schémata pro dvě tabulky se stejným názvem. - - - Proběhne po dokončení Initialization. - - - Představuje mezipaměť dat v paměti. - - - Určuje, zda se při porovnávání řetězců v rámci tabulky rozlišují malá a velká písmena. - - - Vrací podřízené relace pro tuto tabulku. - - - Kolekce obsahující sloupce této tabulky - - - Kolekce obsahující omezení této tabulky - - - Určuje objekt DataSet, do kterého patří tato tabulka - - - Toto je výchozí objekt DataView pro tabulku. - - - Výraz určený k výpočtu datově závislé hodnoty pro tento řádek - - - Určuje, zda tato tabulka obsahuje chyby. - - - Určuje národní prostředí, v němž probíhá porovnání řetězců v rámci tabulky. - - - Určuje počáteční velikost této tabulky. - - - Určuje obor názvů jazyka XML určený identifikátorem URI pro elementy obsažené v této tabulce. - - - Určuje vlastnost Prefix oboru názvů použitého pro tuto tabulku v reprezentaci XML. - - - Vrací nadřazené relace pro tuto tabulku. - - - Určuje sloupce představující primární klíč pro tuto tabulku. - - - Určuje kolekci obsahující řádky dat pro tuto tabulku. - - - Určuje název, který se bude používat při hledání této tabulky v kolekci Tables objektu DataSet. - - - Vyvolá se po úspěšné úpravě řádku v tabulce. - - - Vyvolá se při změně řádku, aby mohla obslužná rutina události změnu změnit nebo zrušit. Uživatel může upravit hodnoty v řádku a pro zrušení úpravy by měl vyvolat výjimku. - - - Vyvolá se po úspěšném odstranění řádku v tabulce. - - - Vyvolá se v případě, že je řádek v tabulce označen k odstranění. Vyvoláním výjimky zrušíte odstranění. - - - Vyvolá se při odeslání hodnoty pro tento sloupec. Uživatel může změnit navrhovanou hodnotu a zrušit úpravy vyvoláním výjimky. - - - Vyvolá se při změně hodnoty tohoto sloupce. - - - Proběhne před vymazáním všech řádků z tabulky. - - - Proběhne po úspěšném vymazání všech řádků v tabulce. - - - Proběhne po vytvoření nové instance objektu DataRow. - - - Název použitý při vyhledávání této relace v kolekci Relations tohoto objektu DataSet - - - Určuje podřízené sloupce této relace. - - - Určuje nadřazené sloupce této relace. - - - Určuje, zda jsou relace vnořené. - - - Určuje, jaká kaskádová akce má být provedena nad touto relací při odstranění. - - - Určuje, jaká kaskádová akce má být provedena nad touto relací při aktualizaci. - - - Určuje, jaká kaskádová akce má být provedena nad touto relací při potvrzení nebo zamítnutí změn. - - - Určuje podřízené sloupce tohoto omezení. - - - Určuje nadřazené sloupce tohoto omezení. - - - Určuje podřízenou tabulku tohoto omezení. - - - Určuje sloupce tohoto omezení. - - - Určuje, zda je toto omezení primárním klíčem. - - - Určuje název tohoto omezení. - - - Určuje tabulku tohoto omezení. - - - Určuje, zda jsou v tomto sloupci povoleny hodnoty null. - - - Označuje, zda se sloupec pro nové řádky přidané do tabulky automaticky sám inkrementuje. Typ tohoto sloupce musí být Int16, Int32 nebo Int64. - - - Určuje počáteční hodnotu pro sloupec AutoIncrement. - - - Určuje přírůstek pro sloupec AutoIncrement. - - - Určuje výchozí titulek pro tento sloupec zobrazovaný v uživatelském rozhraní. - - - Určuje název, který se použije při hledání tohoto sloupce v kolekci Columns objektu DataTable. - - - Vrací objekt DataTable, do kterého patří tento sloupec. - - - Určuje typ dat uložených v tomto sloupci. - - - Určuje výchozí hodnotu sloupce, která se použije při přidání nových řádků do tabulky. - - - Určuje hodnotu, která je pro každý řádek tohoto sloupce vypočtena na základě hodnot ostatních sloupců a která nahradí zadání uživatele. - - - Určuje způsob, jakým je tento sloupec uložen v kódu XML: jako atribut, element, uzel s jednoduchým obsahem nebo bez uložení. - - - Určuje identifikátor URI v kódu XML pro elementy nebo atributy uložené v tomto sloupci. - - - Určuje vlastnost Prefix, která se použije pro tento objekt DataColumn v reprezentaci xml. - - - Určuje index tohoto sloupce v kolekci Columns. - - - Určuje, zda tento sloupec povoluje změny po přidání řádku do tabulky. - - - Určuje, zda tento sloupec vynutí jedinečnost svých hodnot v jednotlivých řádcích tabulky. - - - Určuje maximální délku hodnoty povolenou tímto sloupcem. - - - Označuje vlastnost DateTimeMode tohoto objektu DataColumn. - - - Určuje, zda tento objekt DataView a jemu přidružené uživatelské rozhraní povolují odstranění. - - - Určuje, zda tento objekt DataView a jemu přidružené uživatelské rozhraní povolují úpravy. - - - Určuje, zda tento objekt DataView a jemu přidružené uživatelské rozhraní povolují přidání nových řádků. - - - Vrací počet položek aktuálně obsažených v tomto zobrazení. - - - Vrací ukazatel na objekt DataViewManager, který je vlastníkem tohoto objektu DataSet (pokud existuje). - - - Určuje, zda je toto zobrazení otevřeno. - - - Určuje výraz, který se použije k filtrování dat vrácených tímto objektem DataView. - - - Určuje verzi dat vrácených tímto objektem DataView. - - - Určuje názvy sloupců a řazení, v němž jsou tímto objektem DataView vracena data. - - - Určuje, zda se v případě, že není nastavena vlastnost Sort, použije výchozí řazení. - - - Určuje tabulku, kterou tento objekt DataView použije k získání dat. - - - Určuje, zda data vrácená tímto objektem DataView byla nějakým způsobem změněna. - - - Určuje zdroj dat pro tento objekt DataViewManager. - - - Určuje nastavení řazení, filtrování a stavu pro jakoukoli tabulku v odpovídajícím objektu DataSet. - - - DataSet nepodporuje pro simpleType atributy union a list. - - - Neplatná syntaxe {0}: Chybí povinný atribut {1}. - - - Hodnota {1} je pro atribut {0} neplatná. - - - Hodnota atributu {0} musí být {1} nebo {2}. - - - Název vlastnosti ElementType name={0} nebyl nalezen. - - - V relaci {0} chybí název nadřazené tabulky. - - - V relaci {0} chybí název podřízené tabulky. - - - V relaci {0} chybí klíč nadřazené tabulky. - - - V relaci {0} chybí klíč podřízené tabulky. - - - Nedefinovaný datový typ: {0} - - - Není definován datový typ. - - - Neplatný výběr XPath uvnitř uzlu pole. Nelze najít: {0}. - - - Neplatný výběr XPath uvnitř uzlu selektoru: {0} - - - Neplatný uzel Key uvnitř omezení s názvem {0}. - - - Název omezení {0} je již ve schématu použit. - - - Typ {0} nejde převést na typ {1}. - - - Chybí část {0} v omezení {1} s názvem {2}. - - - Neplatná definice relace: Délky klíčů jsou různé. - - - DataSet nepovoluje cyklický odkaz v elementu ComplexType s názvem {0}. - - - DataSet nemůže vytvářet instance abstraktního elementu ComplexType pro uzel {0}. - - - Při zápisu schématu XML došlo k chybě s převaděčem více cílů. Podrobnosti nejdete v popisu vnitřní výjimky. - - - Při zápisu schématu XML došlo k chybě s převaděčem více cílů. Byl vrácen řetězec s hodnotou null nebo prázdný řetězec. - - - Duplicitní deklarace {0} - - - Nelze načíst diffGram. V cílové datové sadě chybí tabulka {0}. - - - Nelze načíst diffGram. Chybí uzel sql. - - - Název sloupce {0} je definován pro jiné typy mapování. - - - Předpona {0} je neplatná, protože obsahuje speciální znaky. - - - V tabulce {0} vnořené sama do sebe byl zjištěn cyklický odkaz. - - - DataSet nemůže rozšířit entity. Použijte objekt XmlValidatingReader a nastavte vlastnost EntityHandling příslušným způsobem. - - - Typ {0} neimplementuje rozhraní IXmlSerializable. Nelze proto pokračovat v serializaci. - - - Nejde pokračovat v deserializaci. Data neimplementují rozhraní IXMLSerializable, není proto podporován polymorfismus. - - - DataTable nepodporuje odvozování schématu z kódu Xml. - - - Nelze pokračovat v serializaci objektu DataTable {0}. Obsahuje objekt DataRow, který má několik nadřazených řádků na stejném cizím klíči. - - - Ve vstupním schématu chybí atribut IsDataSet. - - - Nelze určit element DataSet. Atribut IsDataSet existuje více než jednou. - - - DataSet nebude serializovat typy, které implementují rozhraní IDynamicMetaObjectProvider, ale neimplementují také rozhraní IXmlSerializable. - - - Funkce není implementována. {0}. - - - Chyba syntaxe: Za operátorem {0} chybí operand. - - - Ve výrazu {0} byla zjištěna neshoda typů. - - - Výraz je příliš složitý. - - - Sloupec [{0}] nelze najít. - - - Výraz obsahuje neplatnou řetězcovou konstantu: {0}. - - - Výraz obsahuje nedefinované volání funkce {0}(). - - - Ve výrazu byla zjištěna chyba syntaxe. - - - Neplatný počet argumentů: funkce {0}(). - - - Ve výrazu chybí pravá závorka. - - - Token {0} na pozici {1} nelze interpretovat. - - - Token na pozici {2} je {1} místo očekávaného {0}. - - - Nelze převést z {0} na {1}. - - - Hodnotu {0} nejde převést na typ {1}. - - - Neplatný název sloupce: [{0}]. - - - Výraz obsahuje neplatnou konstantu typu Date {0}. - - - V seznamu výrazů pro operátor IN jsou povoleny pouze konstantní výrazy. - - - Chyba v operátoru Like: Řetězcový vzorek {0} je neplatný. - - - Chyba syntaxe: Položky následující po klíčovém slovu IN musí být odděleny čárkami a uzavřeny v závorkách. - - - Neshoda typů v argumentu funkce: {0}(), argument: {1}, očekáváno: {2}. - - - Neshoda typů v argumentu funkce: {0}(), argument {1}. Byl očekáván některý z typů integer. - - - Operaci {0} nelze provést na typech {1} a {2}. - - - Operátor {0} je nejednoznačný na operandech typu {1} a {2}. Nelze kombinovat typy se znaménkem a bez něj. Použijte prosím explicitní funkci Convert(). - - - Chyba syntaxe: Po klíčovém slovu IN musí následovat neprázdný seznam výrazů, které jsou oddělené čárkami a uzavřené v závorkách. - - - Výraz obsahuje nepodporovaný operátor {0}. - - - Výraz obsahuje neplatný název: {0}. - - - Chyba syntaxe: Před operátorem {0} chybí operand. - - - Výraz obsahuje příliš mnoho pravých závorek. - - - Tabulka [{0}] je zapojená do více než jedné relace. Ve výrazu {1} je nutné explicitně zmínit název relace. - - - Chyba syntaxe v agregačním argumentu: Jako argument je očekáván jediný sloupec s případným kvalifikátorem Child. - - - Nevázaný odkaz v agregačním výrazu {0}. - - - Nelze vyhodnotit nekonstantní výraz bez aktuálního řádku. - - - Ve výrazu {0} byl zjištěn nevázaný odkaz. - - - Nejde vyhodnotit. Výraz {0} není agregace. - - - Výraz filtru {0} není vyhodnocen jako výraz typu Boolean. - - - Neplatný název typu {0}. - - - Chyba syntaxe ve výrazu Lookup: Očekává se klíčové slovo Parent následované argumentem s jediným sloupcem a případným kvalifikátorem relace: Parent[(<název_relace>)].<název_sloupce>. - - - Metoda Invoke objektu DataFilter vyžaduje řádek nebo tabulku. - - - Argument {0}() je mimo rozsah. + + Posun a délka byly mimo rozsah pro dané pole nebo je počet vyšší než počet elementů z indexu do konce zdrojové kolekce. - - Chyba syntaxe: Neplatné použití operátoru Is. Správná syntaxe: <expression> [není] Null. + + Objekt typu {0} není možné převést na objekt typu {1}. - - Hodnota je pro typ {0} buď příliš velká, nebo příliš malá. + + Jedinou platnou hodnotu pro vlastnost {0} je {1}. - - Došlo k chybě dělení nulou. + + Povolené hodnoty pro vlastnost {0} jsou {1} nebo {2}. - - Nadřazená relace {0} nebyla nalezena. + + Zadané hodnoty QuotePrefix a QuoteSuffix nejsou stejné. - - Argument hours je mimo rozsah. Hodnota musí být v rozsahu od -14 do +14. + + Délka argumentu {0} překračuje limit {1}. - - Argument minutes je mimo rozsah. Hodnota musí být v rozsahu od -59 do +59. + + Hodnota klíče {0} není platná. - - Zadaný rozsah času jedna překračuje 14 hodin. + + Aby bylo možné použít klíč {0}, musí existovat klíč {1}. - - Vlastnost druh zadaného argumentu DateTime nesouhlasí s argumenty 'hodiny' a 'minuty'. + + Délka hodnoty pro klíč {0} překračuje limit délky {1}. - - DataColumn typu {0} nepodporuje výraz. + + Klíčové slovo není podporované: {0}. - - Nepovedlo se povolit omezení. Jeden nebo více řádků obsahuje hodnoty porušující tato omezení: hodnota jiná než null, jedinečná hodnota nebo cizí klíč. + + Došlo k vnitřní chybě zprostředkovatele dat .NET Framework {0}. - - Samotnou kolekci nelze upravovat. + + {0} "{1}". - - Zadaný název {0} odpovídá nejméně dvěma názvům v objektu kolekce, pokud nejsou rozlišována malá a velká písmena, ale neodpovídá ani jednomu z nich, pokud malá a velká písmena rozlišována jsou. + + {0} "{1}", nesprávné použití uvozovek - - Daný název {0} se shoduje s nejméně dvěma názvy v objektu kolekce s různými obory názvů. + + {0} "{1}", aktuální limit {2} je nedostatečný. - - Posun a délka byly mimo rozsah pro dané pole nebo je počet vyšší než počet elementů z indexu do konce zdrojové kolekce. + + Metoda SqlBulkCopy.WriteToServer se nezdařila, protože SqlBulkCopy.DestinationTableName je neplatný název s více částmi. - - Argument {0} je mimo rozsah. + + Zpracování výsledků ze systému SQL Server se nezdařilo kvůli neplatnému názvu s více částmi. - - Argument {0} nemůže být null. + + SqlParameter.UdtTypeName je neplatný název s více částmi. - - Argument {0} obsahuje hodnotu null. + + SqlParameter.TypeName je neplatný název více součástí. - - Sloupec {0} nebyl nalezen. + + Metoda SqlCommand.DeriveParameters se nezdařila, protože hodnota vlastnosti SqlCommand.CommandText představuje neplatný název s více částmi. - - Sloupec {0} již patří do této tabulky DataTable. + + Připojení k zrcadlené instanci systému SQL Server s použitím možnosti připojení MultiSubnetFailover není podporováno. - - Sloupec {0} již patří do jiné tabulky DataTable. + + Hodnota výčtu {0}, {1}, není podporována metodou {2}. - - Objekt DataTable nemůže obsahovat více než jeden sloupec SimpleContent. + + Hodnota výčtu {0}, {1}, není podporována zprostředkovatelem dat .NET Framework SqlClient. - - Do tabulky obsahující sloupce elementů nebo vnořené relace nelze přidat sloupec SimpleContent. + + {0}: Vlastnost CommandText nebyla inicializována. - - Sloupec s názvem {0} již patří do této tabulky DataTable. + + {0}: Vlastnost Connection nebyla inicializována. - - Sloupec s názvem {0} nelze přidat. Do tohoto objektu DataTable již patří vnořená tabulka se stejným názvem. + + {0} vyžaduje otevřený a dostupný element Connection. {1} - - Sloupec s názvem {0} již patří do tohoto objektu DataTable. Název vnořené tabulky nelze nastavit na stejný název. + + Uložená procedura {0} neexistuje. - - Nelze odebrat sloupec, který nepatří do této tabulky. + + Transakce není přidružena k aktuálnímu připojení nebo byla dokončena. - - Tento sloupec nelze odebrat, protože je částí primárního klíče. + + Transakce přidružená k aktuálnímu připojení byla dokončena, ale nebyla vyřazena. Aby mohlo být připojení použito k provedení příkazů SQL, musí být transakce nejprve vyřazena. - - Tento sloupec nelze odebrat, protože je součástí nadřazeného klíče pro relaci {0}. + + {0} vyžaduje, aby měl příkaz transakci, pokud je připojení přiřazeno k příkazu v místní transakci čekající na dokončení. Vlastnost Transaction příkazu nebyla inicializována. - - Tento sloupec nelze odebrat, protože je součástí omezení {0} v tabulce {1}. + + K tomuto objektu {0} je již přidružen otevřený objekt DataReader, který musí být nejprve zavřen. - - Tento sloupec nelze odebrat, protože je součástí výrazu: {0} = {1}. + + Metoda {0} DeriveParameters podporuje pouze objekt CommandType.StoredProcedure, nikoli objekt CommandType.{1}. - - Vlastnost AutoIncrement nelze nastavit pro vypočítaný sloupec. - - - Vlastnost AutoIncrement nelze nastavit pro sloupec s nastavenou vlastností DefaultValue. - - - Nelze nastavit hodnotu DefaultValue pro sloupec AutoIncrement. - - - Hodnota AutoIncrementStep musí být nenulová. - - - Vlastnost ColumnName je požadována v případě, že je součástí objektu DataTable. - - - Jakmile sloupec obsahuje data, nelze vlastnost DataType sloupce již změnit. - - - Sloupec vyžaduje platnou vlastnost DataType. - - - Vlastnost DefaultValue sloupce {0} je typu {1} a nelze ji převést na typ {2}. - - - Vlastnost DefaultValue sloupce je typu {0} a nelze ji převést na typ {1}. - - - Vlastnost DefaultValue sloupce {0} je typu {1}, ale sloupec je typu {2}. - - - Vlastnost ReadOnly sloupce výrazu nelze změnit. - - - Nelze změnit vlastnost Unique pro sloupec výrazu. - - - Nelze vytvořit výraz v sloupci typu AutoIncrement nebo Unique. - - - Výraz nelze nastavit, protože sloupec nemůže být typu ReadOnly. - - - Vlastnost Expression pro sloupec {0} nelze nastavit, protože sloupec je součástí omezení. - - - Omezení založené na sloupci Expression {0} nelze vytvořit. - - - Vlastnost Expression nelze nastavit, protože výraz obsahuje cyklický odkaz. - - - Sloupec {0} obsahuje hodnoty null. - - - Sloupec {0} nesmí obsahovat hodnoty null. - - - Sloupec {0} je jen pro čtení. - - - Sloupec {0} obsahuje hodnoty, které nejsou jedinečné. - - - Sloupec {0} nepatří do tabulky {1}. - - - Sloupec musí patřit do tabulky. - - - Nepovedlo se uložit <{0}> do sloupce {1}. Očekávaný typ je {2}. - - - Sloupec {0} nelze nastavit na hodnotu null. Místo toho prosím použijte DBNull. - - - Sloupec {0} nelze nastavit. Hodnota překračuje omezení MaxLength tohoto sloupce. - - - Vlastnost MaxLength platí pouze pro datový typ řetězce. Nelze nastavit vlastnost MaxLength sloupce {0} na nezáporné celé číslo. - - - Vlastnost MaxLength sloupce {0} nelze nastavit na {1}. V tabulce se nachází alespoň jeden řetězec delší než nové omezení. - - - Vlastnost MaxLength sloupce {0} nelze nastavit. Jde o sloupec SimpleContent. - - - Vlastnost DataType sloupce {0} nelze nastavit na hodnotu {1}. Jde o sloupec SimpleContent. - - - Vlastnost MappingType sloupce {0} nelze nastavit na hodnotu SimpleContent. Datový typ sloupce je {1}. - - - Sloupec {0} překračuje omezení MaxLength. - - - Sloupec {0} nepovoluje hodnotu DBNull.Value. - - - Vlastnost Namespace sloupce {0} nelze změnit. Jde o sloupec SimpleContent. - - - Vlastnost AutoIncrement objektu DataColumn s typem {0} nelze změnit, jakmile obsahuje data. - - - Sloupec {0} nepatří do příslušné tabulky {1}. - - - Objekt DataColumn s typem {0} je typu complexType. Hodnotu složitého typu nelze serializovat jako Attribute. - - - Vlastnost DateTimeMode lze nastavit pouze u objektů DataColumns typu DateTime. - - - {0} je neplatná hodnota DataSetDateTime. - - - Vlastnost DateTimeMode nelze změnit z {0} na {1}, jakmile tabulka obsahuje data. - - - Typ {0} neobsahuje statické vlastnosti nebo pole Null. - - - Typ {0} neimplementuje rozhraní IRevertibleChangeTracking. Nelze proto pokračovat s metodou RejectChanges(). - - - Metody SetAdded a SetModified lze volat pouze u objektů DataRows s nezměněnou hodnotou DataRowState. - - - Řadová číslovka {0} je větší než maximální číslo. - - - Objekt DataSet nepodporuje strukturu System.Nullable<>. - - - Název omezení nelze změnit na prázdný řetězec, pokud je omezení obsaženo v objektu ConstraintCollection. - - - Nelze vynutit omezení pro omezení {0}. - - - Sloupec {0} je omezen na jedinečné hodnoty. Hodnota {1} již existuje. - - - Omezení {0} nepatří do této tabulky DataTable. - - - Nelze najít omezení {0}. - - - Omezení se shoduje s jiným omezením (název: {0}), které se již nachází v kolekci. - - - Omezení s názvem {0} již patří do této tabulky DataTable. - - - Tyto sloupce nyní nemají jedinečné hodnoty. - - - Tyto sloupce neodkazují na tuto tabulku. - - - Toto omezení nelze povolit, protože některé z hodnot nemají odpovídající nadřazené hodnoty. - - - Toto omezení nelze přidat, protože klíč ForeignKey nepatří do tabulky {0}. - - - Nelze odebrat omezení, které nepatří do této tabulky. - - - Nelze odebrat unikátní omezení '{0}' nelze odebrat. Odeberte nejprve omezení cizího klíče '{1}'. - - - Tento řádek nelze odstranit, protože pro relaci {0} jsou vynucena omezení a odstranění tohoto řádku by způsobilo osiření podřízených řádků. - - - Tuto změnu nelze provést, protože pro relaci {0} jsou vynucena omezení a změna této hodnoty by způsobila osiření podřízených řádků. - - - Tabulku {0} nelze vymazat, protože objekt ForeignKeyConstraint {1} vynucuje omezení a {2} obsahuje podřízené řádky. - - - Objekt ForeignKeyConstraint {0} vyžaduje, aby v nadřazené tabulce existovaly hodnoty podřízeného klíče ({1}). - - - Vlastnost není k dispozici. Důvod: {0} - - - Tento řádek nelze odebrat, protože má podřízené řádky a pro relaci {0} jsou vynucena omezení. - - - Omezení primárního klíče nelze přidat, protože v tabulce je již nastaven primární klíč. - - - Nelze přidat omezení do objektu DataTable {0}, který představuje podřízenou tabulku ve dvou vnořených relacích. - - - Klíč nelze vytvořit ze sloupců, které patří jiným tabulkám. - - - Počet sloupců nemůže být 0. - - - Počet sloupců nemůže být větší než {0}. - - - Klíč nelze vytvořit, pokud je tentýž sloupec uveden více než jednou: {0}. - - - Jedinečné omezení nelze odebrat, protože představuje primární klíč tabulky. - - - Jedinečné omezení nelze odebrat, protože představuje primární klíč tabulky {0}. - - - Nadřazené a podřízené sloupce nemají shodné typy. - - - Vlastnosti ParentKey a ChildKey jsou identické. - - - Vlastnosti ParentColumns a ChildColumns musí mít stejnou délku. - - - Vlastnosti ParentColumns a ChildColumns nesmí mít nulovou délku. - - - Řádek nepatří do stejného objektu DataSet jako tato relace. - - - Vlastnost RelationName je vyžadována, pokud je součástí objektu DataSet. - - - Metoda GetChildRows vyžaduje řádek z tabulky {0}, ale byla zadána tabulka Table {1}. - - - Relace by měla propojit dvě tabulky v tomto objektu DataSet a přidat je do objektu DataSet. - - - Metoda GetParentRow vyžaduje řádek z tabulky {0}, ale byla zadána tabulka {1}. - - - Metoda SetParentRow vyžaduje podřízený řádek z tabulky {0}, ale pro řádek byla zadána tabulka {1}. - - - Nelze vytvořit relaci mezi tabulkami v různých objektech DataSets. - - - Mezi tabulkami v různých objektech DataSets nelze vytvořit relaci. - - - Relace pro tyto podřízené sloupce již existuje. - - - Relace nepatří do této kolekce relací. - - - Tato relace již patří jinému objektu DataSet. - - - Tato relace již patří tomuto objektu DataSet. - - - Relace s názvem {0} již patří do této datové sady DataSet. - - - Relace {0} nepatří do tohoto objektu DataSet. - - - Relace {0} nebyla nalezena. - - - Na prázdné tabulce s hodnotou null nelze vytvořit kolekci. - - - Tabulka, pro kterou tato kolekce zobrazuje relace, byla ze svého objektu DataSet odebrána. - - - Do kolekce ParentRelation této tabulky lze přidat pouze takovou relaci, kde tato tabulka vystupuje jako podřízená tabulka. - - - Do kolekce ChildRelation této tabulky lze přidat pouze relaci, ve které tato tabulka vystupuje jako nadřazená tabulka. - - - Vlastnost Nested této relace nelze nastavit na hodnotu false. - - - Stejná tabulka {0} nemůže být podřízenou tabulkou ve dvou podřízených relacích. - - - Tabulka ({0}) nemůže být ve vnořených relací podřízenou tabulkou sama sobě. - - - Nelze přidat objekt DataRelation nebo Constraint, který obsahuje jiné nastavení Locale nebo CaseSensitive mezi svými nadřazenými a podřízenými tabulkami. - - - Nelze vytvořit objekt DataRelation, pokud objekt DataSet neobsahuje nadřazený a podřízený sloupec. - - - Vnořená tabulka {0}, která dědí svůj obor názvů, nemůže mít více nadřazených tabulek v různých oborech názvů. - - - Vnořená tabulka {0} s prázdným oborem názvů nemůže mít více nadřazených tabulek v různých oborech názvů. - - - Řádek nepatří do stejného objektu DataSet jako tato relace. - - - Tuto operaci nelze provést na řádku, který není v tabulce. - - - Tato relace a podřízený řádek nepatří do stejného objektu DataSet. - - - Navrhovanou hodnotu v události RowChanging nelze změnit. - - - Metodu EndEdit() nelze volat v rámci události OnRowChanging. - - - Metodu BeginEdit() nelze volat v rámci události RowChanging. - - - Metodu CancelEdit() nelze volat v rámci události OnRowChanging. Aktualizaci lze zrušit vyvoláním výjimky. - - - Metodu Delete nelze volat v rámci události OnRowDeleting. Odstranění lze zrušit vyvoláním výjimky. - - - Vstupní pole je delší než počet sloupců této tabulky. - - - Nelze získat přístup k žádným aktuálním datům. - - - Nelze získat přístup k žádným původním datům. - - - Nelze získat přístup k žádným navrhovaným datům. - - - Tento řádek byl z tabulky odebrán a neobsahuje žádná data. Metoda BeginEdit() umožní v tomto řádku vytvořit nová data. - - - K informacím o odstraněném řádku nelze přistupovat prostřednictvím řádku. - - - Pole Version musí mít hodnotu Original, Current nebo Proposed. - - - Na pozici {0} se nenachází žádný řádek. - - - Pozice {0} vložení řádku je neplatná. - - - Hodnota atributu rowOrder ({0}) byla v tabulce s názvem {1} nalezena dvakrát. - - - Hodnoty v pořadí rowOrder pro tabulku {0} nebyly nalezeny. - - - Objekt DataRow není obsažen v aktuální kolekci DataRowCollection. - - - Tento řádek již patří do jiné tabulky. - - - Tento řádek již patří do této tabulky. - - - Tento řádek nelze odstranit, protože již byl odstraněn. - - - Tento řádek je prázdný. - - - Nelze odebrat řádek, který již byl odebrán. - - - Podřízený řádek má více nadřazených objektů. - - - Nerozpoznaný bitový vzorek stavu řádků - - - Nelze změnit název objektu DataSet na prázdný řetězec. - - - Název {0} je neplatný. Objekt DataSet nemůže mít stejný název jako objekt DataTable. - - - Obor názvů schématu je neplatný. Doporučený obor: {0} - - - Vlastnost CaseSensitive ani Locale nelze změnit. Tato změna by způsobila, že by minimálně jeden objekt DataRelation nebo Constraint měl různá nastavení vlastností Locale nebo CaseSensitive pro odpovídající tabulky. - - - Vlastnost SchemaSerializationMode lze nastavit pouze v případě, že je přepsána odvozeným objektem DataSet. - - - Sloupce PrimaryKey nepatří do této tabulky. - - - Do tabulky obsahující sloupec SimpleContent nelze přidat vnořenou relaci ani sloupec elementů. - - - Vlastnost TableName je vyžadována, pokud je částí objektu DataSet. - - - Objekt DataTable již obsahuje sloupec s jednoduchým obsahem. - - - Tabulka neobsahuje primární klíč. - - - {0} není platnou položkou řetězce Sort. - - - DataTable není možné serializovat. Objekt DataTable používaný minimálně v jednom výrazu DataColumn není potomkem aktuálního objektu DataTable. - - - Tento objekt DataTable lze vzdáleně použít pouze jako součást objektu DataSet. Nejméně jeden sloupec výrazu odkazuje na jiné objekty DataTable. - - - Pro objekt DataSet a DataTable nemohou existovat různé hodnoty vlastnosti vzdáleného formátu. - - - DataTable není možné serializovat. Název objektu DataTable není nastaven. - - - Objekt DataTable s názvem {0} již patří do tohoto objektu DataSet. - - - Objekt DataTable s názvem {0} se stejným oborem názvů Namespace {1} již do tohoto objektu DataSet náleží. - - - Tabulka ({0}) nemůže ve vnořené relaci vystupovat jako tabulka podřízená sobě samé – název objektu DataSet je v konfliktu s názvem tabulky. - - - Název {0} je neplatný. Objekt DataTable nemůže mít stejný název objektu DataSet. - - - Objekt DataTable již patří jinému objektu DataSet. - - - Objekt DataTable již patří tomuto objektu DataSet. - - - Tabulka {0} nepatří do tohoto objektu DataSet. - - - Tabulka {0} nebyla nalezena. - - - Tabulku, která má existující relace, nelze odebrat. Nejprve odeberte relace. - - - Tabulku {0} nelze odebrat, protože na ni odkazuje omezení ForeignKeyConstraint {1}. Nejprve odeberte omezení. - - - Objekt DataTable {0} neodpovídá žádnému objektu DataTable ve zdroji. - - - V cílovém objektu DataSet nebyla nalezena definice pro {0}. - - - V cílovém objektu DataSet nebylo nalezeno omezení {0} {1}. - - - <target>.{0} a <source>.{0} mají konfliktní vlastnosti: Vlastnost DataType se neshoduje. - - - Vlastnosti <target>.PrimaryKey a <source>.PrimaryKey mají různou hodnotu Length. - - - Neshoda sloupců ve vlastnosti PrimaryKey: <target>.{0} oproti <source>.{1} - - - Vztah {0} nelze sloučit, protože klíče obsahují neshodné sloupce. - - - V cílové tabulce {0} nebyla nalezena definice sloupce {1}. - - - Sloupec PrimaryKey {0} neexistuje ve zdrojové tabulce. - - - Parametr RowStates musí být nastaven na platnou kombinaci hodnot z výčtu DataViewRowState. - - - Funkce Find vyhledává řádek na základě pořadí řazení. Pořadí řazení však nebylo určeno. - - - Pro indexovaný klíč se očekával tento počet hodnot: {0}, ale byl přijat tento počet hodnot: {1}. - - - Neplatné použití agregační funkce {0}() a typu: {1} - - - Neplatný typ úložiště: {0} - - - Při trvalém uložení objektu DataSet Xml není hodnota {0} podporována jako typ Char. Uložte ji jako typ Byte. - - - Typ hodnoty neodpovídá typu sloupce. - - - Typ {0} neimplementuje rozhraní IComparable. Porovnání nelze provést. - - - Nelze nastavit {0}. - - - Nelze změnit nastavení DataSet objektu DataViewManager aktuálně představující výchozí zobrazení objektu DataSet. - - - Vlastnost RowStateFilter nemůže najednou zobrazit položky ModifiedOriginals a ModifiedCurrents. - - - Nelze změnit vlastnost Table objektu DefaultView nebo DataView pocházející z objektu DataViewManager. - - - Jakmile je vlastnost DataSet nastavena, nelze ji změnit. - - - Před použitím objektu DataViewManager je nutné nastavit objekt DataSet. - - - Jakmile je vlastnost Table nastavena, nelze ji změnit. - - - Před použitím objektu DataView je nutné nastavit vlastnost DataTable. - - - Nelze vytvořit vazbu s objektem DataTable, který nemá název. - - - Do tohoto seznamu nelze nastavit objekt. - - - Pro objekt DataView nelze volat metodu AddNew, pokud vlastnost AllowNew má hodnotu false. - - - Objekt DataView není otevřen. - - - Relace není nadřazena tabulce, na kterou ukazuje tento objekt DataView. - - - Vlastnost DataSource nelze odstranit, pokud vlastnost AllowDelete má hodnotu false. - - - Vlastnost DataSource nelze upravit, pokud vlastnost AllowEdit má hodnotu false. - - - Index {0} je záporný nebo je počet jeho položek vyšší než počet řádků. - - - Do tohoto seznamu nelze přidávat externí objekty. - - - Tento seznam nelze vymazat. - - - Do tohoto seznamu nelze vkládat externí objekty. - - - Odebírat lze pouze objekty, které jsou uvedeny v seznamu. - - - Vlastnost {0} není pro tabulku {1} objektem DataColumn ani DataRelation. - - - V objektu Range musí být hodnota Min ({0}) menší nebo rovna hodnotě max ({1}). - - - Tento rozsah má hodnotu null. - - - Vlastnost MinimumCapacity musí mít nezápornou hodnotu. - - - Identifikátor pro název '{0}' nelze generovat. - - - Existuje více tabulek se stejným názvem {0} (přestože obor názvů se může lišit). - - - Sloupec {0}: Typ {1} nemůže nabývat hodnoty NULL. - - - Sloupec {0}: Typ {1} neobsahuje konstruktor bez parametrů. - - - Sloupec {0}: Typ {1} neobsahuje konstruktor s řetězcovým argumentem. - - - Objekt typu {0} není možné převést na objekt typu {1}. - - - Data Exception. - - - Constraint Exception. - - - Odstraněné řádky jsou nedostupné. - - - Duplicitní název není povolen. - - - Operace není podporována v události RowChanging. - - - Neplatné omezení - - - Chybí primární klíč. - - - Hodnota Null není povolena. - - - Sloupec je označen jen pro čtení. - - - Řádek nebyl v tabulce nalezen. - - - Verze nebyla nalezena. - - - Byla změněna data ReadOnly. - - - Třída DataTableReader je neplatná pro aktuální objekt DataTable {0}. - - - Schéma aktuálního objektu DataTable {0} ve třídě DataTableReader se změnilo, třída DataTableReader je neplatná. - - - DataTableReader nelze vytvořit. V objektu DataSet není žádný objekt DataTable. - - - DataTableReader není možné vytvořit. Argument je prázdný. - - - DataTableReader není možné vytvořit. Argumenty obsahují hodnotu null. - - - Aktuální objekt DataRow je ve stavu Deleted nebo Detached. - - - Aktuální objekt DataTable {0} je prázdný. V objektu DataTable není žádný objekt DataRow. - - - Vnitřní index DataTable je poškozen: {0}. - - - Kolekce byla změněna, operace výčtu pravděpodobně nebude spuštěna. - - - Jednoduchý typ {0} již byl deklarován s jinou hodnotou {1}. - - - Neplatné číslování listů - - - Jakmile je přidružený objekt DataSet mapován na načtený dokument XML, název tabulky nelze změnit. - - - Jakmile je přidružený objekt DataSet mapován na načtený dokument XML, obor názvů tabulky nelze změnit. - - - Jakmile je přidružený objekt DataSet mapován na načtený dokument XML, název sloupce nelze změnit. - - - Jakmile je přidružený objekt DataSet mapován na načtený dokument XML, obor názvů sloupce nelze změnit. - - - Jakmile je přidružený objekt DataSet mapován na načtený dokument XML, vlastnost ColumnMapping nelze změnit. - - - Jakmile je objekt DataSet mapován na načtený dokument XML, nelze v tabulce přidat ani odebrat sloupec. - - - Jakmile je objekt DataSet mapován na načtený dokument XML, v objektu DataSet nelze přidat ani odebrat tabulku. - - - Jakmile je objekt DataSet mapován na načtený dokument XML, nelze v objektu DataSet přidat, odebrat ani měnit vnořené relace. - - - Parametr DataSet je neplatný. Nemůže být null. - - - Jakmile je objekt DataSet mapován na načtený dokument XML, název objektu DataSet nelze změnit. - - - Tento typ uzlu nelze klonovat: {0}. - - - Objekt XmlDataDocument nelze načíst, pokud již obsahuje data. Použijte prosím nový objekt XmlDataDocument. - - - Objekt DataSet lze přidružit nejvýše k jednomu objektu XmlDataDocument. Objekt DataSet nelze přidružit k aktuálnímu objektu XmlDataDocument, protože objekt DataSet je již přidružen k jinému objektu XmlDataDocument. - - - Před úpravou objektu XmlDataDocument pomocí operací XML je nutné nastavit vlastnost DataSet.EnforceConstraints == false. - - - Metoda GetElementById() není podporována v objektu DataDocument. - - - Nelze vytvořit odkazy na entity v objektu DataDocument. - - - Třída XmlDataDocument nepodporuje funkci Clear u objektu DateSet a DataTable. - - - Sloupec nelze odebrat, protože je do této sady dataSet integrován. - - - Relaci nelze odebrat, protože je do tohoto elementu dataSet integrována. - - - Vyvolá se, když dojde ke změně vlastnosti tohoto ovládacího prvku. - - - Vyvolá se vždy, když je změněno členství této kolekce. - - - Nelze získat hodnotu, protože je DBNull. - - - Vlastnost {0} vyžaduje operační systém Microsoft Windows NT nebo operační systém typu WindowsNT. - - - Nelze najít požadovaného zprostředkovatele dat .NET Framework. Je možné, že není nainstalovaný. - - - Požadovaná implementace zprostředkovatele dat .NET Framework nemá pole Instance odvozeného typu System.Data.Common.DbProviderFactory. - - - Nelze najít nebo načíst registrovaného zprostředkovatele dat .NET Framework. - - - Chybí požadovaný kvalifikovaný název sestavení zprostředkovatele dat .NET Framework. - - - Povoleny jsou pouze elementy. - - - Podřízené uzly nejsou povoleny. - - - Nerozpoznaný atribut{0} - - - Nerozpoznaný element - - - Oddíl {0} může být v konfiguračním souboru pouze jednou. - - - Nenašel se povinný atribut {0}. - - - Požadovaný atribut {0} nesmí být prázdný. - - - Bylo očekáváno neprázdné pole pro parametr {0}. - - - Jedinou platnou hodnotu pro vlastnost {0} je {1}. - - - Povolené hodnoty pro vlastnost {0} jsou {1} nebo {2}. - - - Zadané hodnoty QuotePrefix a QuoteSuffix nejsou stejné. - - - Délka argumentu {0} překračuje limit {1}. - - - Byl očekáván argument typu {1}, byl však přijat typ {0}. - - - Hodnota klíče {0} není platná. - - - Aby bylo možné použít klíč {0}, musí existovat klíč {1}. - - - Délka hodnoty pro klíč {0} překračuje limit délky {1}. - - - Klíčové slovo není podporované: {0}. - - - Soubor UDL nelze načíst. - - - Soubor UDL je neplatný. - - - Došlo k vnitřní chybě zprostředkovatele dat .NET Framework {0}. - - - Po vygenerování příkazu Insert, Update nebo Delete již nelze vlastnosti QuotePrefix a QuoteSuffix změnit. - - - Vlastnost DataAdapter.SelectCommand musí být inicializována. - - - Vlastnost DataAdapter.SelectCommand.Connection musí být inicializována; - - - {0} "{1}". - - - {0} "{1}", nesprávné použití uvozovek - - - {0} "{1}", aktuální limit {2} je nedostatečný. - - - Metoda SqlBulkCopy.WriteToServer se nezdařila, protože SqlBulkCopy.DestinationTableName je neplatný název s více částmi. - - - Zpracování výsledků ze systému SQL Server se nezdařilo kvůli neplatnému názvu s více částmi. - - - SqlParameter.UdtTypeName je neplatný název s více částmi. - - - SqlParameter.TypeName je neplatný název více součástí. - - - Metoda SqlCommand.DeriveParameters se nezdařila, protože hodnota vlastnosti SqlCommand.CommandText představuje neplatný název s více částmi. - - - Metoda OdbcCommandBuilder.DeriveParameters se nezdařila, protože hodnota vlastnosti OdbcCommand.CommandText představuje neplatný název s více částmi. - - - Metoda OleDbCommandBuilder.DeriveParameters se nezdařila, protože hodnota vlastnosti OleDbCommandBuilder.CommandText představuje neplatný název s více částmi. - - - Připojení k zrcadlené instanci systému SQL Server s použitím možnosti připojení MultiSubnetFailover není podporováno. - - - Mapování sloupce ze SourceColumn {0} se nezdařilo, protože DataColumn {1} je vypočítaný sloupec. - - - Neshodné typy SourceColumn {0} z(e) {1} a DataColumn {2} z(e) {3} nelze převést. - - - V objektu DataTable {1} pro vlastnost SourceColumn {2} chybí objekt DataColumn {0}. - - - Hodnota SourceColumn musí být neprázdný řetězec. - - - Mapování vlastnosti SourceColumn pro {0} nebylo nalezeno. - - - Hodnota výčtu {0}, {1}, není podporována metodou {2}. - - - Hodnota výčtu {0}, {1}, není podporována zprostředkovatelem dat .Net Framework Odbc. - - - Hodnota výčtu {0}, {1}, není podporována zprostředkovatelem dat .Net Framework OleDb. - - - Hodnota výčtu {0}, {1}, není podporována zprostředkovatelem dat .NET Framework SqlClient. - - - Nelze načíst ComputerNameDnsFullyQualified, {0}. - - - Chybí objekt DataTable {0} pro SourceTable {1}. - - - Hodnota SourceTable musí být neprázdný řetězec. - - - Mapování vlastnosti SourceTable {0} nebylo nalezeno. - - - {0}: Vlastnost CommandText nebyla inicializována. - - - {0}: Vlastnost Connection nebyla inicializována. - - - {0} vyžaduje otevřený a dostupný element Connection. {1} - - - Fill: Vlastnost SelectCommand.Connection nebyla inicializována. - - - FillPage: Vlastnost SelectCommand.Connection nebyla inicializována. - - - FillSchema: Vlastnost SelectCommand.Connection nebyla inicializována. - - - Metoda Update vyžaduje, aby událost InsertCommand měla objekt připojení. Vlastnost Connection události InsertCommand nebyla inicializována. - - - Metoda Update vyžaduje, aby událost UpdateCommand měla objekt připojení. Vlastnost Connection události UpdateCommand nebyla inicializována. - - - Metoda Update vyžaduje, aby událost DeleteCommand měla objekt připojení. Vlastnost Connection události DeleteCommand nebyla inicializována. - - - Metoda Update vyžaduje objekt připojení. Vlastnost Connection nebyla inicializována. - - - Metoda Update vyžaduje, aby klon příkazu obsahoval objekt připojení. Vlastnost Connection klonu příkazu nebyla inicializována. - - - Aktualizace vyžaduje připojení. - - - Metoda Update vyžaduje, aby událost {0}Command měla otevřený objekt připojení. {1} - - - Metoda Update vyžaduje, aby událost {0}Command měla otevřený objekt připojení. {1} - - - Metoda Update vyžaduje, aby událost {0}Command měla otevřený objekt připojení. {1} - - - Metoda Update vyžaduje, aby aktualizační příkaz disponoval objektem otevřeného připojení. {1} - - - Uložená procedura {0} neexistuje. - - - Transakce přiřazená tomuto příkazu musí být nejvíce vnořená čekající místní transakce. - - - Transakce není přidružena k aktuálnímu připojení nebo byla dokončena. - - - Transakce přidružená k aktuálnímu připojení byla dokončena, ale nebyla vyřazena. Aby mohlo být připojení použito k provedení příkazů SQL, musí být transakce nejprve vyřazena. - - - {0} vyžaduje, aby měl příkaz transakci, pokud je připojení přiřazeno k příkazu v místní transakci čekající na dokončení. Vlastnost Transaction příkazu nebyla inicializována. - - - Již existuje otevřená třída SqlResultSet asociovaná s tímto příkazem, kterou je nutné nejdříve zavřít. - - - K tomuto objektu {0} je již přidružen otevřený objekt DataReader, který musí být nejprve zavřen. - - - Metoda {0} DeriveParameters podporuje pouze objekt CommandType.StoredProcedure, nikoli objekt CommandType.{1}. - - - Metodu {0} lze pro stejné provádění volat pouze jednou. - - - Nesprávný asynchronní výsledek - - - Vlastnost SelectCommand nebyla před voláním {0} inicializována. - - - Třída StatementType {0} zde není očekávána. - - - FillSchema: V názvu SourceTable byl očekáván neprázdný řetězec. - - - Hodnota MaxRecords {0} je neplatná. Hodnota musí být větší nebo rovna 0. - - - Hodnota StartRecord {0} je neplatná. Hodnota musí být větší nebo rovna 0. - - - Fill: V názvu SourceTable byl očekáván neprázdný řetězec. - - - Hierarchické sloupce kapitol musí být mapovány na vlastnost AutoIncrement objektu DataColumn. - - - Metoda DataReader.GetFieldType({0}) vrátila hodnotu null. - - - Do pole dataTables zadejte při použití nenulových hodnot pro vlastnost startRecords nebo maxRecords pouze jednu položku. - - - Metoda Update nenalezla element TableMapping[{0}] ani objekt DataTable {0}. - - - Update: Byl očekáván neprázdný název SourceTable. - - - Vlastnost TableMapping při hodnotě TableMapping.DataSetTable={0} nebyla nalezena. - - - Metoda Update vyžaduje, aby klon příkazu byl platný. - - - Automatické generování SQL v průběhu metody Update vyžaduje platnou událost SelectCommand. - - - Metoda Update vyžaduje při předání kolekce DataRow s novými řádky platnou událost InsertCommand. - - - Metoda Update vyžaduje při předání kolekce DataRow se změněnými řádky platnou událost UpdateCommand. - - - Metoda Update vyžaduje při předání kolekce DataRow s odstraněnými řádky platnou událost DeleteCommand. - - - Element DataRow[{0}] pochází z jiného objektu DataTable než element DataRow[0]. - - - RowUpdatedEvent: Došlo k chybám. Další informace nejsou k dispozici. - - - RowUpdatingEvent: Došlo k chybám. Další informace nejsou k dispozici. - - - Při dávkovém zpracování není hodnota vlastnosti UpdatedRowSource UpdateRowSource.FirstReturnedRecord nebo UpdateRowSource.Both příkazu platná. - - - Narušení souběžného zpracování: Událost UpdateCommand ovlivnila {0} z(e) {1} očekávaných záznamů. - - - Narušení souběžného zpracování: Událost DeleteCommand ovlivnila {0} z(e) {1} očekávaných záznamů. - - - Narušení souběžného zpracování: Dávkový příkaz ovlivnil {0} z(e) {1} očekávaných záznamů. + + Metodu {0} lze pro stejné provádění volat pouze jednou. Neplatná hodnota CommandTimeout {0}. Hodnota musí být větší nebo rovna 0. - - {1}[{0}]: Vlastnost Size má neplatnou velikost 0. - - - Metoda {0}.Prepare vyžaduje, aby měly všechny parametry explicitně nastaven typ. - - - Metoda {0}.Prepare vyžaduje, aby všechny parametry s proměnnou délkou měly explicitně nastavenu nenulovou hodnotu Size. - - - Metoda {0}.Prepare vyžaduje, aby parametry typu {1} měly explicitně nastaveny hodnoty Precision a Scale. - - - Neodpovídající volání koncové metody pro třídu asyncResult. Bylo očekáváno volání metody {0}, místo toho však byla volána metoda {1}. - - - Neplatná operace Připojení je zavřené. - - - Připojení bylo zakázáno. - - - Nelze zařadit do transakce, protože v rámci připojení probíhá místní transakce. Dokončete místní transakci a zkuste to znovu. - - - Pro připojení je momentálně zařazena transakce. Dokončete aktuální transakci a potom akci opakujte. - - - Databáze nemůže nabývat hodnoty NULL, prázdného řetězce ani řetězce, který obsahuje pouze prázdné znaky. - - - Argument je příliš dlouhý. - - - Neplatná hodnota Connect Timeout. Hodnota musí být celočíselná a větší nebo rovna 0. - - - Neplatný posun zdrojové vyrovnávací paměti (velikost: {0}, posun: {1}) - - - Neplatný posun cílové vyrovnávací paměti (velikost: {0}, posun: {1}) - - - Číselná hodnota je příliš velká. Nemůže ji obsahovat desítková 96bitová hodnota. - - - Pokus o {0} při zavřeném datovém proudu je neplatný. - - - Zadaná hodnota SeekOrigin je neplatná. - - - Dynamické generování SQL není podporováno pro více základních tabulek. - - - Dynamické generování SQL není podporováno pro vlastnost SelectCommand, která nevrací žádné informace o základní tabulce. - - - Dynamické generování SQL pro vlastnost DeleteCommand není podporováno pro vlastnost SelectCommand, která nevrací žádné informace o sloupci klíče. - - - Dynamické generování SQL pro vlastnost UpdateCommand není podporováno pro vlastnost SelectCommand, která nevrací žádné informace o sloupci klíče. - - - Dynamické generování SQL pro vlastnost DeleteCommand není podporováno pro vlastnost SelectCommand, která neobsahuje sloupec verze řádku. - - - Dynamické generování SQL pro vlastnost UpdateCommand není podporováno pro vlastnost SelectCommand, která neobsahuje sloupec verze řádku. - - - Dynamické generování SQL není podporováno pro názvy tabulek {0} obsahující znak {1} QuotePrefix nebo QuoteSuffix. - - - Pokus o čtení ze sloupce s ordinálním číslem {0} se nezdařil. S vlastností CommandBehavior.SequentialAccess lze číst pouze ze sloupce s pořadovým číslem{1} a vyšším. - - - Datový typ {0} nelze formátovat jako literál, protože má neplatné číslice data a času. - - - Hodnotu nelze formátovat jako literál požadovaného typu. - - - Datový typ {0} nelze formátovat jako literál, protože obsahuje neplatný maximální rozsah. - - - Zadaná hodnota literálu není platná hodnota literálu pro datový typ {0}. - - - {0}:Délka hodnoty literálu musí být sudá. - - - {0}:Hodnota literálu musí být řetězec s šestnáctkovými číslicemi. - - - {0} vyžaduje otevřené připojení, pokud nebyla nastavena předpona řetězce v uvozovkách. - - - Nelze určit správné logické hodnoty literálu. Nelze vytvářet logické literály. - - - Literály nativního datového typu přidružené k datovému typu {0} nejsou podporovány. - - - Datový typ parametru {0} je neplatný. - - - Neexistuje žádné mapování z objektu typu {0} na známý nativní typ spravovaného zprostředkovatele. - - - Nelze ošetřit neznámý typ objektu TypeCode {0} vrácený objektem Type {1}. - - - Neexistuje žádné mapování vlastnosti DbType {0} na známý typ {1}. - - - Používaná verze SQL Serveru nepodporuje datový typ {0}. - - - Hodnota parametru {0} je mimo rozsah. - - - Zadaný název parametru {0} není platný. - - - Více parametrů vrácené hodnoty není podporováno. - - - Neplatná hodnota {0} parametru Size. Hodnota musí být větší nebo rovna 0. - - - Hodnota argumentu {0} je neplatná. Hodnota musí být větší nebo rovna 0. - - - Neplatná hodnota pro tato metadata - - - Metadata musí být SqlDbType.Row - - - Hodnotu parametru nelze převést z {0} na {1}. - - - {0} nepodporuje paralelní transakce. - - - Tato transakce {0} byla dokončena a nelze ji již použít. - - - {0} nelze volat, pokud je záznam určen jen pro čtení. - - - {0} nelze volat, pokud je třída DbDataRecord určena jen pro čtení. - - - Implicitní převod objektu typu {0} na datový typ {1} není podporován. - - - Posun vyrovnávací paměti {1} zvýšený o počet dostupných bajtů {0} je vyšší než délka předané vyrovnávací paměti. - - - Délka dat {0} je menší než 0. - - - Zadaná délka {0} je mimo rozsah. - - - Pokus {2} (dataIndex:{0}) je neplatný. Pomocí pole CommandBehavior.SequentialAccess lze číst pouze od hodnoty dataIndex {1} nebo vyšší. - - - Hodnota posunu musí odkazovat na umístění ve stejné hodnotě. - - - Zprostředkovatel spravovaný rozhraním ODBC vyžaduje, aby bylo zadáno omezení TABLE_NAME a nebylo null pro kolekci indexů GetSchema. - - - Neplatná hodnota argumentu pro metodu {0} - - - Zprostředkovatel dat ODBC nevrátil výsledky z rozhraní SQLGETTYPEINFO. - - - Neočekávaný argument DataTable s hodnotou null - - - Neočekávaný argument DataSet s hodnotou null - - - Připojovací řetězec překročil maximální povolenou délku {0}. - - - {0} – nelze mapovat typ. - - - Neznámý typ SQL: {0}. - - - Neznámý typ URT: {0}. - - - Neplatný záporný argument - - - Nelze nastavit vlastnost otevřeného připojení. - - - Neexistuje platné mapování pro SQL_TRANSACTION {0} na hodnotu výčtu System.Data.IsolationLevel. - - - {0} – nelze povolit sdružování připojení… - - - {0} – nelze přidělit popisovač prostředí. - - - {0} – nelze získat popisovač popisovače. - - - Bez účasti v transakci - - - Neplatná hodnota výčtu OdbcType: {0}. - - - Použijte metodu IsDBNull, pokud jsou očekávána data DBNull.Value. - - - {0} [{1}] {2} - - - {0} – informace o chybě není k dispozici. - - - Připojení je zavřené. - - - Interní připojení nemá vlastníka. - - - Zprostředkovatel dat .NET Framework rozhraní ODBC vyžaduje součásti rozhraní MDAC (Microsoft Data Access Components) verze 2.6 nebo novější. Našla se aktuálně nainstalovaná verze {0}. - - - Zprostředkovatel dat .NET Framework rozhraní OLEDB vyžaduje součásti rozhraní MDAC (Microsoft Data Access Components) verze 2.6 nebo novější. Našla se aktuálně nainstalovaná verze {0}. - - - Rozhraní {0} není zprostředkovatelem {1} podporováno. GetOleDbSchemaTable není u aktuálního zprostředkovatele k dispozici. - - - Došlo k chybě {0}. K dispozici není žádná chybová zpráva, kód výsledku: {1}. - - - K dispozici není žádná chybová zpráva, kód výsledku: {0}. - - - Zprostředkovatelé dat .NET Framework vyžadují rozhraní MDAC (Microsoft Data Access Components). Nainstalujte rozhraní MDAC (Microsoft Data Access Components) verze 2.6 nebo novější. - - - Zprostředkovatel dat .NET Framework pro OLEDB (Microsoft.Data.OleDb) nepodporuje zprostředkovatele Microsoft OLE DB pro ovladače ODBC (MSDASQL). Použijte zprostředkovatele dat .NET Framework pro ODBC (System.Data.Odbc). - - - Zprostředkovatel dat .NET Framework pro OLEDB nepovolí zprostředkovateli OLE DB Provider zobrazovat uživateli výzvy v neinteraktivním prostředí. - - - Zprostředkovatel {0} není registrován v místním počítači. - - - Rozhraní ICommandText není podporováno zprostředkovatelem {0}. Místo toho použijte CommandType.TableDirect. - - - Rozhraní ITransactionLocal není podporováno zprostředkovatelem {0}. Místní transakce nejsou u aktuálního zprostředkovatele k dispozici. - - - Formát inicializačního řetězce neodpovídá specifikaci OLE DB. Začíná kolem char[{0}] v připojovacím řetězci. - - - Asynchronní zpracování není podporovanou funkcí zprostředkovatele dat .NET Framework OLE DB (Microsoft.Data.OleDb). - - - V ConnectionString nebyl zadán zprostředkovatel OLE DB. Příkladem může být Provider=SQLOLEDB;. - - - Zprostředkovatel dat OLE DB zadaný ve vlastnosti ConnectionString je příliš dlouhý. - - - Pro pole DbInfoKeywords objektu OleDbSchemaGuid se neočekávají žádná omezení. - - - Pro pole DbInfoLiterals objektu OleDbSchemaGuid se neočekávají žádná omezení. - - - Pro objekt OleDbSchemaGuid identifikátoru guid schématu se neočekávají žádná omezení. - - - Zprostředkovatel {1} nepodporuje schéma OleDbSchemaGuid {0}. - - - Nastavení konfigurace {0} má nesprávný počet hodnot. - - - Nelze načíst soubor XML zadaný v nastavení konfigurace {0}. - - - Parametr příkazu [{0}] {1} je neplatný. - - - Hodnotu dat parametru příkazu [{0}] {1} nebylo možné převést z jiných důvodů, než je neshoda ve znaménku nebo přetečení dat. - - - Převod parametru příkazu [{0}] {1} se nezdařil, protože hodnota dat je typu se znaménkem a typ použitý zprostředkovatelem je bez znaménka. - - - Převod parametru příkazu [{0}] {1} se nezdařil, protože hodnota dat přesáhla typ použitý zprostředkovatelem. - - - Zprostředkovatel zjistil chybu při odesílání hodnoty parametru příkazu [{0}] {1} a zastavil zpracování. - - - Parametr [{0}] {1} nemá výchozí hodnotu. - - - Došlo k chybě v parametru [{0}]: {1}. - - - Vnitřní chyba objektu Microsoft.Data.OleDb.OleDbDataAdapter: neplatný přistupující objekt parametru: {0} {1}. - - - Parameter[{0}]: Vlastnost OleDbType není inicializována: OleDbType.{1}. - - - Rozhraní ICommandWithParameters není podporováno zprostředkovatelem {0}. Parametry příkazu nejsou u aktuálního zprostředkovatele podporovány. - - - Zprostředkovatel {0} nepodporuje načítání informací o parametrech procedury. - - - Nelze určit oddělovač desetinných míst serveru. Číselné literály, které nejsou typu integer, nelze vytvořit. - - - Objekt není typu ADODB.RecordSet ani ADODB.Record. - - - Z objektu ADODB.RecordSet nelze získat rozhraní {0}. + + {1}[{0}]: Vlastnost Size má neplatnou velikost 0. - - Z objektu ADODB.Record nelze získat rozhraní IRow. + + Metoda {0}.Prepare vyžaduje, aby měly všechny parametry explicitně nastaven typ. - - Typ nepodporuje rozhraní OLE DB ISourcesRowset. + + Metoda {0}.Prepare vyžaduje, aby všechny parametry s proměnnou délkou měly explicitně nastavenu nenulovou hodnotu Size. - - Nelze vytvořit kolekci schématu ReservedWords, protože zprostředkovatel nepodporuje rozhraní IDBInfo. + + Metoda {0}.Prepare vyžaduje, aby parametry typu {1} měly explicitně nastaveny hodnoty Precision a Scale. - - Hodnota vlastnosti nebyla nastavena, protože zprostředkovatel nepodporoval vlastnost {0} nebo se uživatel pokusil získat nebo nastavit hodnoty vlastností neexistující ve skupině vlastností Initialization a objekt zdroje dat není inicializován. + + Neodpovídající volání koncové metody pro třídu asyncResult. Bylo očekáváno volání metody {0}, místo toho však byla volána metoda {1}. - - Inicializace vlastnosti {0} se nezdařila z jednoho z následujících důvodů: - Datový typ hodnoty nebyl datovým typem vlastnosti nebo nebyl null. Vlastnost byla například DBPROP_MEMORYUSAGE, která má datový typ Int32 a datový typ byl Int64. - Hodnota nebyla platná. Vlastnost byla například DBPROP_MEMORYUSAGE a hodnota byla záporná. - Hodnota byla platnou hodnotou vlastnosti a zprostředkovatel podporuje vlastnost jako nastavitelnou vlastnost, ale zprostředkovatel zadanou hodnotu nepodporuje. To zahrnuje případ, kdy byla hodnota přidána do vlastnosti v OLE DB po zápisu zprostředkovatele. + + Neplatná operace Připojení je zavřené. - - Hodnota Options byla neplatná. + + Nelze zařadit do transakce, protože v rámci připojení probíhá místní transakce. Dokončete místní transakci a zkuste to znovu. - - Element ColumnID byl neplatný. + + Pro připojení je momentálně zařazena transakce. Dokončete aktuální transakci a potom akci opakujte. - - Byla zadána vlastnost {0}, která by měla být použita pro všechny sloupce, minimálně u jednoho z nich však být použita nemohla. + + Databáze nemůže nabývat hodnoty NULL, prázdného řetězce ani řetězce, který obsahuje pouze prázdné znaky. - - Vlastnost {0} byla jen pro čtení nebo se uživatel pokusil nastavit hodnoty vlastností ve skupině vlastností Initialization po inicializaci objektu zdroje dat. Uživatelé mohou nastavit hodnotu vlastnosti jen pro čtení na aktuální hodnotu. Tento stav se vrátí také v případě, že nebylo možné pro daný sloupec nastavit nastavitelnou hodnotu sloupce. + + Neplatný posun zdrojové vyrovnávací paměti (velikost: {0}, posun: {1}) - - Volitelná hodnota vlastnosti {0} nebyla nastavena na zadanou hodnotu. Vlastnost tedy nebylo možné nastavit na zadanou hodnotu. + + Neplatný posun cílové vyrovnávací paměti (velikost: {0}, posun: {1}) - - Hodnota vlastnosti {0} nebyla nastavena, protože tím by došlo ke konfliktu s existující vlastností. + + Pokus o {0} při zavřeném datovém proudu je neplatný. - - (Rezervováno). + + Zadaná hodnota SeekOrigin je neplatná. - - Zprostředkovatel vrátil neznámou hodnotu DBPROPSTATUS_ {0}. + + Pokus o čtení ze sloupce s ordinálním číslem {0} se nezdařil. S vlastností CommandBehavior.SequentialAccess lze číst pouze ze sloupce s pořadovým číslem{1} a vyšším. - - Ověření přistupujícího objektu bylo odloženo a bylo provedeno v době, kdy metoda vrátila data. Vazba byla pro tento sloupec nebo parametr neplatná. + + Datový typ parametru {0} je neplatný. - - Vnitřní chyba objektu OleDbDataAdapter: Neplatný přístupový objekt k sadě řádků: Ordinal={0} Status={1}. + + Neexistuje žádné mapování z objektu typu {0} na známý nativní typ spravovaného zprostředkovatele. - - Hodnotu dat nebylo možné převést z jiných důvodů, než je neshoda ve znaménku nebo přetečení dat. Data byla například poškozena v úložišti dat, ale řádek bylo stále možné načíst. + + Nelze ošetřit neznámý typ objektu TypeCode {0} vrácený objektem Type {1}. - - Zprostředkovatel nemohl přidělit paměť ke vrácení dat {0}. + + Neexistuje žádné mapování vlastnosti DbType {0} na známý typ {1}. - - Převod se nezdařil, protože hodnota dat {0} přesáhla typ určený pro část hodnoty {0} ve vyrovnávací paměti uživatele. + + Používaná verze SQL Serveru nepodporuje datový typ {0}. - - Vnitřní chyba objektu OleDbDataAdapter: [get] Neznámý datový typ OLE DB: 0x{0} ({1}). + + Hodnota parametru {0} je mimo rozsah. - - Převod se nezdařil, protože hodnota dat {0} je typu se znaménkem a typ zadaný pro část hodnoty {0} ve vyrovnávací paměti uživatele je bez znaménka. + + Zadaný název parametru {0} není platný. - - Vnitřní chyba objektu OleDbDataAdapter: [set] Neznámý datový typ OLE DB: 0x{0} ({1}). + + Neplatná hodnota {0} parametru Size. Hodnota musí být větší nebo rovna 0. - - Zprostředkovatel nedokázal určit hodnotu {0}. Řádek byl například právě vytvořen, výchozí hodnota pro sloupec {0} nebyla k dispozici a uživatel ještě nenastavil novou hodnotu {0}. + + Hodnota argumentu {0} je neplatná. Hodnota musí být větší nebo rovna 0. - - Zprostředkovatel dat OLE DB vrátil neočekávanou stavovou hodnotu {0}. + + Neplatná hodnota pro tato metadata - - Metoda OleDbDataReader.Read musí být volána ze stejného vlákna, ve kterém byla vytvořena. To platí v případě, že vlastnost ApartmentState tohoto vlákna nemá hodnotu ApartmentState.MTA. + + Hodnotu parametru nelze převést z {0} na {1}. - - Nespecifikovaná chyba: {0} + + {0} nepodporuje paralelní transakce. - - Funkce IErrorInfo.GetDescription selhala – {0}. + + Tato transakce {0} byla dokončena a nelze ji již použít. - - Funkce IErrorInfo.GetSource selhala – {0}. + + Zadaná délka {0} je mimo rozsah. - - Zprostředkovatel dat OLE DB .NET Framework (Microsoft.Data.OleDb) nepodporuje data typu DBTYPE_VECTOR. + + Pokus {2} (dataIndex:{0}) je neplatný. Pomocí pole CommandBehavior.SequentialAccess lze číst pouze od hodnoty dataIndex {1} nebo vyšší. Neplatné hodnoty maximální a minimální velikosti fondu. Minimální velikost fondu nemůže být větší než maximální. - - Klíčové slovo {0} je zastaralé. Místo toho použijte možnost {1}. - Ze serveru nelze získat adresu DTC (Distributed Transaction Coordinator) pro server. Je na serveru povolená služba DTC? - - Délka hodnoty parametru připojení <{0}> překročila povolený počet 65535 znaků. - Neplatná hodnota velikosti paketu. Hodnota musí být celé číslo >= 512 a <= 32768. Neplatná transakce nebo neplatný název bodu pro uložení v rámci transakce. - - Hodnota výčtu {0}, {1}, není podporována systémem SQL Server 7.0 ani systémem SQL Server 2000. - User Instance a Failover nejsou kompatibilní možnosti. V připojovacím řetězci zvolte pouze jednu z obou možností. @@ -2499,33 +540,21 @@ Pokud je v připojovacím řetězci nastavená možnost Authentication=Active Directory Integrated, nejde nastavit vlastnost Credential. - - Byly zadány nesprávné parametry ověřování pro ověřování certifikátem. - Instance systému SQL Server, ke které jste se pokusili připojit, vyžaduje šifrování, ale tento počítač ho nepodporuje. Instance systému SQL Server, ke které jste se pokusili připojit, nepodporuje šifrování. - - Instance serveru SQL Server, ke které jste se pokusili připojit, nepodporuje CTAIP. - Verze systému SQL Server není podporovaná. Zprostředkovatele dat .NET Framework SqlClient lze použít pouze se systémem SQL Server verze 7.0 nebo novější. {0} nelze změnit, pokud probíhá asynchronní operace. - - 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. - Chyba instance @@ -2556,27 +585,18 @@ Kód chyby: 0x{0} - - ChangePassword vyžaduje SQL Server 9.0 nebo vyšší verzi. - Přesměrování instance SSE není podporováno u uživatelských instancí, které nejsou místní. Ověřování {0} není podporované. - - Metoda ověřování SQL {0} není podporována. - Pro {0} se nepodařilo vytvořit instanci zprostředkovatele ověřování s typem {1}. Nepodařilo se vytvořit instanci SqlAuthenticationInitializer s typem {0}. - - Při inicializaci zprostředkovatele {0} došlo k výjimce. - Zprostředkovatel {0} nepodporuje ověřování {1}. @@ -2586,24 +606,15 @@ Pro {0} nelze najít zprostředkovatele ověřování. Nainstalujte balíček NuGet Microsoft.Data.SqlClient.Extensions.Azure (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), abyste mohli používat metody ověřování Active Directory (Entra ID). - - Nepodařilo se přečíst konfigurační oddíl zprostředkovatelů ověřování. - Parametr {0} nesmí obsahovat hodnotu Null ani nesmí být prázdný. - - Asynchronní operace již byla dokončena. - V provádění příkazu nelze pokračovat, protože probíhá asynchronní operace čekající na dokončení. Metodě ExecuteXmlReader byl odeslán neplatný příkaz. Příkaz musí vracet výsledek Xml. - - Upozornění vyžadují SQL Server 9.0 nebo vyšší verze. - Neplatný formát názvu sestávajícího ze 3 částí pro vlastnost UdtTypeName. @@ -2697,9 +708,6 @@ Balíček SSPI nelze inicializovat. - - Časový limit vypršel. Před dokončením operace vypršel časový limit nebo server neodpovídá. - Vypršel časový limit připojení. Časový limit vypršel na začátku fáze před přihlášením. Důvodem může být to, že pro časový limit připojení byl určen nedostatečný časový interval. @@ -2745,12 +753,6 @@ V připojovacím řetězci byla vyžadována uživatelská instance, ale zadaný server tuto možnost nepodporuje. - - Certifikát serveru neprošel ověřením aplikace. - - - Zadaná délka dat {0} překračuje maximální povolenou délku {1}. - Došlo k neplatnému pokusu o čtení, protože data nebyla k dispozici. @@ -2775,9 +777,6 @@ Délka dat {0} je menší než 0. - - {0} nelze volat, pokud je třída SqlDataRecord určena jen pro čtení. - Namapovaná kolekce se používá a nelze k ní momentálně získat přístup; @@ -2847,21 +846,6 @@ Připojení ke kontextu pomocí Microsoft.Data.SqlClient není podporováno. - - Vnořené objekty TransactionScopes nejsou podporovány. - - - Neočekávaná událost serveru: {0} - - - Délka argumentu {0} se musí shodovat s délkou argumentu {1}. - - - Hodnota SqlDbType {0} je pro {1} neplatná. Podporuje se pouze {2}. - - - Příliš mnoho hodnot - Stream nepodporuje zápis. @@ -2886,192 +870,36 @@ Null - - Zpráva - - - Aritmetické přetečení - - - Došlo k chybě dělení nulou. - - - Data mají hodnotu Null. Tuto metodu nebo vlastnost nelze volat s hodnotami Null. - - - Číselná aritmetika způsobila zkrácení. - Přetečení SqlDateTime. Hodnota musí být v rozsahu od 1.1.1753 12:00:00 do 31.12.9999 23:59:59. - - Dva řetězce určené ke zřetězení mají různé kolace. - - - Dva řetězce určené k porovnání mají různé kolace. - - - Neplatná hodnota příznaku - - - Přetečení převodu z typu SqlDecimal na typ Decimal - Přetečení převodu - - Neplatná hodnota SqlDateTime - - - Bylo zadáno časové pásmo. SqlDateTime nepodporuje časová pásma. - Neplatná velikost pole Neplatná číselná přesnost nebo exponent - - Vstup nebyl ve správném formátu. - - - Chyba SqlType - - - Třída SqlBytes ani třída SqlChars v této verzi nepodporuje délku větší než 2 GB. - Při čtení došlo k chybě. - - Stream byl zavřen nebo zrušen. - - - Není k dispozici žádná vyrovnávací paměť. Operace čtení nebo zápisu se nezdařila. - - - Nelze nastavit na nenulovou délku, protože aktuální hodnota je Null. - - - Velikost vyrovnávací paměti není dostatečná. Operace čtení nebo zápisu se nezdařila. - - - Nelze zapisovat do nenulového posunu, protože aktuální hodnota je Null. - - - Nelze zapisovat z posunu, který je větší než aktuální délka. Neinicializovaná data by zůstala ve vyrovnávací paměti. - Vrácená data mají velikost větší než 2 GB. Pokud chcete získat všechna data, použijte chování příkazu SequentialAccess. - - Argument pro metodu GetDayOfWeek musí být celé číslo v rozmezí od 1 do 7. - - - Pro datový typ SQL ještě nebyla načtena data. - - - Pro datový typ SQL již byla načtena data. - - - Neplatný pokus o přístup k zavřenému objektu XmlReader - - - Pokus o volání metody {0} při zavřeném streamu je neplatný. - - - Pokus o volání metody {0} při nezapisovatelném streamu je neplatný. - - - Pokus o volání metody {0} při nečitelném streamu je neplatný. - - - Pokus o volání metody {0} při neprohledávatelném streamu je neplatný. - - - Podtřída nepřepsala požadovanou metodu. - Nelze vytvořit normalizátor pro {0}. Vnitřní chyba - - Parametr příkazu musí mít text příkazu, který není null a není prázdný. - - - Pole parametru MetaData musí mít délku odpovídající argumentu pole ParameterDirection. - - - Chyba mapování datového adaptéru. - - - Chyba datového adaptéru. - - - Porušení souběžnosti databáze. - Operace byla přerušena. Operace byla přerušena z důvodu výjimky. (Podrobnosti naleznete u vlastnosti InnerException.) - - Určuje, zda bude vlastnost Fill volat metodu DataRow.AcceptChanges. - - - Zda bude metoda Update volat metodu DataRow.AcceptChanges - - - Určuje, zda bude aktualizace pokračovat na dalším řádku DataRow v případě, že vlastnost Status událostí RowUpdating a RowUpdated metody Update je UpdateStatus.ErrorsOccurred. - - - Jakým způsobem adaptér naplňuje objekt DataTable z objektu DataReader - - - Akce provedená v případě, že ve vlastnosti TableMappings chybí tabulka nebo sloupec - - - Akce provedená v případě, že v objektu DataSet chybí tabulka nebo sloupec - - - Způsob mapování zdrojové tabulky na tabulku DataSet - - - Událost aktivovaná při výskytu opravitelné chyby během příkazu Fill - - - Zda by měl příkaz Fill vrátit hodnoty pro konkrétního zprostředkovatele nebo běžné hodnoty CLSCompliant. - - - DataColumn.ColumnName - - - Název zdrojového sloupce (s rozlišením malých a velkých písmen) - - - Počet položek v kolekci - - - Zadaný objekt DataColumnMapping - - - Jednotlivá mapování sloupců v případě, že dojde ke shodě mapování této tabulky. - - - DataTable.TableName - - - Název zdrojové tabulky objektu DataTableMapping. V tomto názvu se rozlišují velká a malá písmena. - - - Počet položek v kolekci - - - Zadaný objekt DataTableMapping - Používá se při provádění metody Update pro odstraněné řádky v objektu DataSet. @@ -3090,63 +918,9 @@ Událost aktivovaná při provádění metody Update po každém objektu DataRow. - - Počet řádků, které mají být společně zpracovány v dávce před spuštěním s použitím zdroje dat. - - - Připojení použité v případě, že události Select/Insert/Update/DeleteCommands ještě nemají připojení. - - - Používá se během události Update pro odstraněné řádky v objektu DataTable. - - - Používá se během události Update pro nové řádky v objektu DataTable. - - - Používá se během příkazu Fill. - - - Používá se během události Update pro upravené řádky v objektu DataTable. - - - Zda by měl příkaz Fill vrátit hodnoty pro konkrétního zprostředkovatele nebo běžné hodnoty CLSCompliant. - - - Jakým způsobem má být mapována zdrojová tabulka na objekt DataTable. - - - Jakým způsobem jsou generovány události Insert/Update/DeleteCommands, pokud uživatel nezadá nastavení. - - - Počet řádků, které mají být společně zpracovány v dávce před spuštěním s použitím zdroje dat. - - - Řetězec připojení používaný k připojení k objektu Data Source. - - - Název ovladače ODBC, který má být použit při připojování k objektu Data Source. - - - Název DSN, který má být použit při připojování k objektu Data Source. - - - V případě hodnoty true označuje, že je potřeba používat fond spravovaného připojení. - - - Soubor UDL, který se má používat při připojování k objektu Data Source. - - - Určuje, které služby OLE DB Services má zprostředkovatel OleDb Provider povolit nebo zakázat. - - - Název zprostředkovatele OLE DB, který má být použit při připojování k objektu Data Source. - Název aplikace. - - V případě hodnoty true umožňuje použití asynchronní funkce ve zprostředkovateli dat .NET Framework. - Název primárního souboru, včetně úplné cesty, připojitelné databáze. @@ -3156,9 +930,6 @@ V případě hodnoty true označuje, že stav připojení je při odebrání z fondu obnoven. - - Při hodnotě true označuje, že připojení by mělo pocházet z kontextu serveru SQL Server. K dispozici pouze při spuštění v procesu serveru SQL Server. - Název záznamu jazyka serveru SQL Server. @@ -3252,54 +1023,9 @@ Určuje metodu ověřování pomocí serveru SQL Server. - - Zadaný klientský certifikát pro ověřování pomocí serveru SQL Server. - - - Informace použitá pro připojení ke zdroji dat. - - - Aktuální hodnota časového limitu připojení. Tuto hodnotu nelze nastavit ve vlastnosti ConnectionString. - - - Katalogová hodnota aktuálního zdroje dat, část Database=X připojovacího řetězce. - - - Aktuální zdroj dat, část Server=X připojovacího řetězce. - - - Aktuální ovladač ODBC. - - - Verze produktu, ke kterému přistupuje ovladač ODBC. - - - Informace použitá pro připojení ke zdroji dat. - - - Hodnota časového limitu aktuálního připojení, část Connect Timeout=X v řetězci ConnectionString. - - - Katalogová hodnota aktuálního zdroje dat, část Initial Catalog=X připojovacího řetězce. - - - Aktuální zdroj dat, část Data Source=X připojovacího řetězce. - - - Identifikátor ProgID aktuálního zprostředkovatele dat OLE DB, část Provider=X připojovacího řetězce. - - - Verze produktu, k němuž přistupuje zprostředkovatel dat OLE DB. - Přístupový token pro ověření - - Stav připojení, synchronní nebo asynchronní. Asynchronous Processing=x v připojovacím řetězci - - - Informace použité k připojení pro replikaci - Informace použité pro připojení k objektu DataSource, například Data Source=x;Initial Catalog=x;Integrated Security=SSPI @@ -3324,9 +1050,6 @@ Shromáždit statistiku pro toto připojení. - - Vlastní zprostředkovatelé úložišť šifrovacích klíčů sloupců - Identifikátor GUID představující fyzické připojení @@ -3363,99 +1086,9 @@ Automatické zařazení do upozornění použitých systémem Microsoft SQL Server - - Jakým způsobem je automaticky generována klauzule where pro příkazy Update a Delete v případě, že uživatel nezadá nastavení. - - - Označuje pozici názvu katalogu v úplném názvu tabulky v textovém příkazu. - - - Znak oddělující název katalogu od zbývající části identifikátoru v textovém příkazu. - - - Znak oddělující název schématu od zbývající části identifikátoru v textovém příkazu. - - - Řetězec předpony připojený před objekty sql. - - - Řetězec přípony připojený za objekty sql. - - - Objekt DataAdapter, pro který mají být automaticky generovány příkazy. - - - Použijte schéma z objektu DataTable nebo vlastnosti SelectCommand. - - - Jakým způsobem je automaticky generována klauzule set pro příkaz Update v případě, že uživatel nezadá nastavení. - - - Vlastnost DataAdapter, pro kterou budou automaticky generovány příkazy OdbcCommands - - - Znak použitý v textovém příkazu jako úvodní uvozovka pro uvedení identifikátorů obsahujících speciální znaky. - - - Znak použitý v textovém příkazu jako koncová uvozovka pro uvádění identifikátorů obsahujících speciální znaky. - - - Vlastnost DataAdapter, pro kterou budou automaticky generovány příkazy OleDbCommands - - - Oddělovač desetinných míst použitý v číselných literálech. - - - Řetězec předpony připojený před objekty sql - - - Řetězec přípony připojený za objekty sql - Vlastnost DataAdapter, pro kterou budou automaticky generovány příkazy SqlCommands - - Oddělovač desetinných míst použitý v číselných literálech. - - - Znak použitý v textovém příkazu jako úvodní uvozovka pro uvedení identifikátorů obsahujících speciální znaky. - - - Znak použitý v textovém příkazu jako koncová uvozovka pro uvádění identifikátorů obsahujících speciální znaky. - - - Nutné nastavovat pouze pro parametry desetinných míst a číselné parametry při použití se scénáři Prepare, FillSchema a CommandBuilder. - - - Nutné nastavovat pouze pro parametry desetinných míst a číselné parametry při použití se scénáři Prepare, FillSchema a CommandBuilder. - - - Nativní typ parametru. - - - Nativní typ parametru. - - - Název parametru, například @p1 - - - Nativní typ parametru. - - - Název serveru typu. - - - Posun v datových typech proměnné délky. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - ParameterDirection {0} zadaný pro parametr {1} není podporován. Parametry s hodnotou tabulky (table-valued parameter) podporují pouze ParameterDirection.Input. @@ -3468,9 +1101,6 @@ TypeName zadaný pro parametr {0}. TypeName musí být nastaven pouze pro parametry Structured. - - Typ SqlDbType.Structured je podporován pouze u typů s více hodnotami. - Sloupec DateType pro pole {0} v tabulce schématu má hodnotu null. DataType nesmí mít hodnotu null. @@ -3483,18 +1113,9 @@ Počet polí v záznamu {0} neodpovídá počtu v původním záznamu. - - rozsah: 0 až 8000 - Ve zprostředkovateli dat SqlClient došlo k neočekávané chybě. {0} - - Nelze získat Type Info pro {0},{1} - - - Zprostředkovateli se nepodařilo načíst následující sestavení: {0}. - Vlastnost UdtTypeName musí být nastavena pro parametry UDT. @@ -3504,72 +1125,9 @@ Zadaný typ není registrován na cílovém serveru.{0}. - - Parametry UDT jsou povoleny v klauzulích where pouze v případě, že jsou součástí primárního klíče. - - - {0} je neplatný uživatelsky definovaný typ, důvod: {1}. - - - podporuje formáty v paměti i uživatelsky definované formáty. - - - Nativní formát nelze podporovat. - - - neimplementuje rozhraní IBinarySerialize. - - - neserializovatelné - - - žádné veřejné konstruktory - - - neimplementuje rozhraní INullable. - - - nemá veřejný konstruktor - žádný atribut UDT - - Serializace bez mapování ještě není podporována. - - - chybí metoda public static x Parse(System.Data.SqlTypes.SqlString). - - - chybí metoda public override string ToString(). - - - chybí metoda public static x Null { get; }. - - - Nativní formát nepodporuje pole (přímo nebo prostřednictvím jiného pole) typu {0}. - - - Typ není veřejný. - - - Parametr Native UDT nepředstavuje sekvenční rozložení vzhledem k typu {0}. - - - Parametr Native UDT určuje maximální velikost v bajtech (byte). - - - pole {0} je označeno jako neserializované. - - - Typ pole {0} je označen jako explicitní rozložení, což není ve formátu Native povoleno. - - - Odkazy na sestavení s více hodnotami musí mít nenulové ID sestavení. - - - Strukturované typy s více hodnotami lze použít pouze u parametrů a nemohou být vnořeny v jiném typu. - Název cesty není platný. @@ -3585,9 +1143,6 @@ Chyba při pokusu o zvýšení úrovně transakce - - Objekt závislosti používaný pro příjem upozornění na dotazy - Vlastnost umožňující označit, zda je tato závislost neplatná. @@ -3600,9 +1155,6 @@ K přidání příkazu k existujícímu objektu závislosti - - Objekt Command je již spojen s jiným objektem závislosti. Nastavení nelze přepsat. - Tento objekt SqlCommand je již spojen s jiným objektem SqlDependency. @@ -3630,12 +1182,6 @@ Zadaný časový limit je neplatný. Časový limit nesmí být menší než nula. - - Chyba oznámení. Typ ={0}, Informace ={1}, Zdroj ={2}. - - - Metoda GetMetaData není pro tuto hodnotu SqlDbType platná. - Hodnota dbType {0} je pro tento konstruktor neplatná. @@ -4104,9 +1650,6 @@ Vnitřní chyba. Hodnota prostého textu nemůže být null. - - Nelze zašifrovat. Výsledkem šifrování je {0} bajtů šifrovaného textu, což je více než maximální povolený limit {1} bajtů. Zadaná hodnota prostého textu je pravděpodobně příliš velká (velikost prostého textu je: {2} bajtů). - Vnitřní chyba. Hodnota šifrovaného textu nemůže být null. @@ -4224,9 +1767,6 @@ SQL Server nevrátil informace o ověření identity. Typ enklávy je {0} a adresa URL pro ověření identity enklávy je {1}. - - Nelze vytvořit zabezpečený kanál (zprostředkovatel Schannel). Chybová zpráva: {0} - Vnitřní chyba. Při vytváření objektu typu {1} se zadal argument {0} s hodnotou null. Argument {0} nemůže mít hodnotu null. @@ -4242,15 +1782,6 @@ Určuje koncový bod služby ověření identity enklávy, pomocí kterého se ověří, jestli enkláva nakonfigurovaná v instanci systému SQL Server pro výpočty se sloupci databáze šifrovanými funkcí Always Encrypted je platná a zabezpečená. - - Nepovedlo se přečíst oddíl konfigurace pro zprostředkovatele enkláv. Ujistěte se, že oddíl má v konfiguračním souboru vaší aplikace správný formát. Chybová zpráva: {0} - - - Nepovedlo se vytvořit instanci zprostředkovatele enklávy s typem {1} pro název {0}. Chybová zpráva: {2} - - - Vnitřní chyba. Parametr SqlColumnEncryptionEnclaveProviderName nemůže mít hodnotu null nebo být prázdný. - Při čtení sady výsledků dotazu {0} došlo k chybě. V připojovacím řetězci se nezadala adresa URL pro ověření identity, ale dotaz vyžaduje výpočty v enklávě. Typ enklávy je {1}. @@ -4260,9 +1791,6 @@ Vnitřní chyba. Při spouštění dotazu, který vyžaduje výpočty v enklávě, je typ enklávy přijatý ze systému SQL Server null nebo prázdný. - - Aby se dotaz mohl spustit, vyžadují se výpočty v enklávě, ale v konfiguraci aplikace chybí oddíl zprostředkovatele enklávy. - Pro typu enklávy {0} a protokol ověření identity {1} se nenašel žádný zprostředkovatel enklávy. Zadejte prosím v připojovacím řetězci správný protokol ověření identity. @@ -4320,9 +1848,6 @@ Výchozí nastavení šifrování sloupce pro všechny příkazy v rámci připojení - - Vynutí, aby se parametry před posláním citlivých dat na server zašifrovaly. - Objekt slovníku obsahující názvy systémů SQL Server a jejich důvěryhodné cesty k hlavnímu klíči sloupce @@ -4332,9 +1857,6 @@ Připojení k zrcadlené instanci systému SQL Server s použitím možnosti připojení ApplicationIntent ReadOnly není podporováno. - - Byly přijaty neočekávané informace o směrování. - Byly přijaty neplatné informace o směrování. @@ -4485,9 +2007,6 @@ V tomto kontextu je zadáno nepodporované ověřování: {0} - - Vypršel časový limit interaktivního ověřování služby Active Directory. Odpověď uživatele na žádost o ověření trvala příliš dlouho. - Pokud je nastavená vlastnost Credential, nejde použít možnost Authentication=Active Directory Interactive. @@ -4497,9 +2016,6 @@ ID procesu serveru (SPID) aktivního připojení - - Vypršel časový limit ověřování typu Active Directory Device Code Flow. Odpověď uživatele na žádost o ověření trvala příliš dlouho. - Pokud je v připojovacím řetězci nastavená možnost Authentication=Active Directory Device Code Flow, nejde nastavit vlastnost Credential. @@ -4533,9 +2049,6 @@ Pro požadovanou akci jsou podporována jen jednorozměrná pole. - - Zadanou položku nelze odebrat, protože nebyla nalezena v zadané kolekci. - Vyžaduje se nezáporné číslo. @@ -4563,87 +2076,36 @@ Pokud je v připojovacím řetězci nastavená možnost Authentication=Active Directory Default, nejde nastavit vlastnost AccessTokenCallback. - - Operace {0} vrátila {1}. - - - Chyba protokolu: Přijatá zpráva obsahuje platný podpis, ale nebyla zašifrována podle požadavků platné úrovně ochrany. - - - {0} – došlo k selhání s chybou {1}. - Tato metoda není touto třídou implementována. - - Požadovaný balíček zabezpečení není podporován. - Nesprávný typ physicalConnection. Při pokusu o nastavení přepínače AppContext {0}={1} došlo k výjimce. - - Typ {0} se na této platformě nepodporuje. - Invariantní režim globalizace není podporován. - - Nelze provést ověření pomocí protokolu Kerberos. Ujistěte se, že se v klientovi inicializoval protokol Kerberos pomocí příkazu kinit a že pro SQL Server byl zaregistrovaný hlavní název služby, aby bylo možné ověřování protokolem Kerberos. - Klíčové slovo „Network Library“ se na této platformě nepodporuje. Místo toho zadejte k „Data Source“ jako předponu požadovaný protokol (tcp: pro připojení TCP nebo np: pro připojení typu pojmenovaný kanál). Soket nevyvolal očekávanou výjimku {0} s kódem chyby {1}. - - Server se pokouší použít funkci, která není na této platformě podporována. - Klíčové slovo {0} není na této platformě podporováno. Aktuálně načtená knihovna System.Transactions.dll nepodporuje globální transakce. - - Při čtení dat ze serveru byl přijat nepodporovaný token {0}. - - - {0} není podporovaný typ popisovače. - SqlFileStream se na této platformě nepodporuje. LocalDB se na této platformě nepodporuje. - - Nedostatek místa ve vyrovnávací paměti. Vyžadováno: {0}; Skutečnost: {1}. - - - Operace GSSAPI se nezdařila se stavem: {0} (Dílčí stav: {1}). - - - Operace GSSAPI se nezdařila s chybou – {0} ({1}). - - - Podpora vazeb kanálů v jiných operačních systémech než Windows není podporována. - - - Název cíle by neměl být prázdný, pokud jsou předány výchozí přihlašovací údaje. - - - Požadovaná úroveň ochrany není podporovaná s aktuálně nainstalovanou implementací rozhraní GSSAPI. - - - Implementace serveru není podporována. - - - Ověřování NTLM není možné s výchozími přihlašovacími údaji na této platformě. - Při použití v metodě DataAdapter.Update je hodnota parametru změněna z DBNull.Value na (Int32)1 nebo (Int32)0, pokud je hodnota jiná než null. @@ -4668,9 +2130,6 @@ 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. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx index 28b564595e..fcbac77cb7 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Aufsteigend - Ungültiger Index {0} für {1} mit Count={2}. @@ -132,18 +129,12 @@ {0} ist bereits in einem/einer anderen {1} enthalten. - - {0} ist bereits in diesem/dieser {1} enthalten. - {0} akzeptiert nur nicht-Null {1}-Typenobjekte. Es wurde versucht, ein {0} zu entfernen, das nicht in {1} enthalten ist. - - {0}.{1} muss eindeutig sein. '{2}' ist in der Sammlung bereits vorhanden. - Die Verbindung wurde nicht geschlossen. {0} @@ -171,12 +162,6 @@ Ungültiger Versuch, {0} aufzurufen, da der Datenleser bereits geschlossen wurde. - - Eine Eintragung in die Transaktion ist nicht möglich, da die Verbindung die primäre Verbindung für eine delegierte oder heraufgestufte Transaktion ist. - - - Absteigend - Für den Parameter '{0}' wird eine nicht leere Zeichenfolge erwartet. @@ -204,9 +189,6 @@ Die ConnectionString-Eigenschaft wurde nicht initialisiert. - - Eine Nicht-CLS-Exception ist aufgetreten. - Das angegebene Sicherheitselement ist kein Berechtigungselement. @@ -252,9 +234,6 @@ Daten - - StateChange - Aktualisieren @@ -264,48 +243,12 @@ Der ConnectionState, der angibt, ob die Verbindung offen oder geschlossen ist. - - Das Ereignis, das ausgelöst wird, wenn der Verbindungsstatus geändert wird. - - - Der generische Parametertyp. - - - Eingabe-, Ausgabe- oder bidirektionaler Parameter. - - - Eine DesignTime-Eigenschaft wird zur Codegenerierung mit starker Typisierung verwendet. - - - Offset in Datentypen variabler Länge. - - - Name des Parameters. - - - Größe von Datentypen mit variabler Länge (Zeichenfolge & Arrays). - - - Bei Verwendung durch DataAdapter.Update der Quellspaltenname, der zum Suchen nach dem DataSetColumn-Namen in den ColumnMappings verwendet wird. Dient zum Kopieren eines Werts zwischen dem Parameter und einer Datenzeile. - - - Bei Verwendung durch ein DataAdapter.Update (nur UpdateCommand) ist dies die Version des DataRow-Werts, der zum Update der Datenquelle verwendet wird. - - - Bei Verwendung durch DataAdapter.Update wird der Parameterwert von DBNull.Value in (Int32)1 oder (Int32)0, falls nicht Null, geändert. - - - Wert des Parameters. - Die Sammlung '{0}' kann nicht erstellt werden, da die SQL-Abfrage fehlgeschlagen ist. Weitere Informationen finden Sie in der inneren Ausnahme. Es wurden mehr Einschränkungen bereitgestellt, als das angeforderte Schema ('{0}') unterstützt. - - '{2}' ist kein gültiger Wert für die Einschränkung '{1}' der Schema-Sammlung '{0}'. - Die angeforderte Sammlung ({0}) ist nicht definiert. @@ -315,15 +258,6 @@ Die angeforderte Sammlung ({0}) wird von dieser Version des Providers nicht unterstützt. - - Es fehlt eine der erforderlichen DataSourceInformation-Tabellenspalten. - - - Die DataSourceInformation-Tabelle muss exakt eine Zeile enthalten. - - - Es fehlt mindestens eine der erforderlichen Spalten für die Sammlung der Einschränkungen. - Es liegt eine Einschränkung vor, für die es keine übereinstimmende Zeile in der Sammlung der Einschränkungen gibt. @@ -342,42 +276,21 @@ Die Sammlung '{0}' fehlt im Metadaten-XML-Code. - - Der Metadaten-XML-Code ist ungültig. - - - Der Metadaten-XML-Code ist ungültig. Die Sammlung {0} muss die Spalte {1} enthalten, die eine Zeichenfolgenspalte sein muss. - Der Metadaten-XML-Code ist ungültig. Die Spalte {1} der Sammlung {0} muss eine nicht leere Zeichenfolge enthalten. - - Aktion - - - Verhalten - Füllen Informationsmeldung - - Zuordnung - StatementCompleted - - UDT - Benachrichtigung - - Schema - XML @@ -393,9 +306,6 @@ Pooling - - Benannter ConnectionString - Sicherheit @@ -408,2073 +318,204 @@ Verbindungsresilienz - - Die Sammlung die Benutzerinformationen enthält. - - - Gibt an, ob beim Vergleichen von Zeichenfolgen innerhalb des DataSets die Groß-/Kleinschreibung beachtet wird. - - - Der Name des DataSet. - - - Gibt die benutzerdefinierte Sicht der Daten an, die im DataSet enthalten sind. Diese Sicht ermöglicht das Filtern, Suchen und Navigieren in der benutzerdefinierten Sicht. - - - Gibt an, ob Einschränkungsregeln befolgt werden. - - - Gibt an, dass das DataSet Fehler enthält. - - - Gibt einen Ort an, an dem Zeichenfolgen innerhalb des DataSet verglichen werden. - - - Gibt den XML-URI-Namensbereich des Stammelements an, auf das dieses DataSet zeigt. - - - Gibt das Präfix des Namensbereichs an, der für dieses DataSet verwendet wird. - - - Die Sammlung, welche die Beziehungen für diesen DataSet enthält. - - - Die Sammlung, welche die Tabellen für dieses DataSet enthält. - - - Tritt auf, wenn Schemen für zwei Tabellen mit dem gleichen Namen nicht zusammengeführt werden können. - - - Erfolgt nach Beendigung der Initialization. - - - Stellt einen speicherresidenten Datencache dar. - - - Gibt an, ob beim Vergleichen von Zeichenfolgen innerhalb der Tabelle Groß-/Kleinschreibung beachtet wird. - - - Gibt die untergeordneten Beziehungen für diese Tabelle zurück. - - - Die Sammlung, die die Spalten für diese Tabelle enthält. - - - Die Sammlung, die die Einschränkungen für diese Tabelle enthält. - - - Gibt das DataSet an, zu dem diese Tabelle gehört. - - - Dies ist die standardmäßige DataView für diese Tabelle. - - - Der Ausdruck, der verwendet wird, um den datengebundenen Wert dieser Zeile zu berechnen. - - - Gibt zurück, ob die Tabelle Fehler enthält. - - - Gibt einen Ort an, an dem Zeichenfolgen innerhalb der Tabelle verglichen werden. - - - Gibt eine Startgröße für diese Tabelle an. - - - Gibt den XML-URI-Namensbereich für die Elemente an, die sich in dieser Tabelle befinden. - - - Gibt das Präfix des Namensbereichs an, der für diese Tabelle in der XML-Darstellung verwendet wird. - - - Gibt die übergeordneten Beziehungen für diese Tabelle zurück. - - - Gibt die Spalte(n) an, die den Primärschlüssel für diese Tabelle darstellen. - - - Gibt die Sammlung an, die die Datenzeilen für diese Tabelle enthält. - - - Gibt den Namen an, der verwendet wird, um diese Tabelle in der Tables-Sammlung eines DataSets zu suchen. - - - Tritt auf, wenn eine Zeile in der Tabelle erfolgreich bearbeitet wurde. - - - Tritt auf, wenn die Zeile geändert wird, damit der Ereignishandler die Änderung bearbeiten oder abbrechen kann. Der Benutzer kann Werte in der Zeile ändern und sollte eine Ausnahme verwenden, um den Bearbeitungsvorgang abzubrechen. - - - Tritt auf, wenn eine Zeile in der Tabelle erfolgreich gelöscht wurde. - - - Tritt auf, wenn eine Zeile in der Tabelle zum Löschen markiert ist. Löst eine Ausnahme aus, um den Löschvorgang abzubrechen. - - - Tritt auf, wenn für diese Spalte ein Wert übermittelt wurde. Der Benutzer kann den vorgeschlagenen Wert ändern und sollte eine Ausnahme verwenden, um die Bearbeitung abzubrechen. - - - Tritt auf, wenn ein Wert für diese Spalte geändert wurde. - - - Tritt vor dem Löschen aller Zeilen aus der Tabelle auf. - - - Tritt auf, nachdem alle Zeilen in der Tabelle erfolgreich gelöscht wurden. - - - Tritt auf, nachdem eine neue DataRow instanziiert wurde. - - - Der Name, der verwendet wird, um diese Beziehung in der Relations-Sammlung eines DataSets zu suchen. - - - Gibt die untergeordneten Spalten in dieser Beziehung an. - - - Gibt die übergeordneten Spalten dieser Beziehung an. - - - Gibt an, ob die Beziehungen geschachtelt sind. - - - Für Löschvorgänge. Gibt an, welche Art von Überlappung in dieser Beziehung verwendet werden soll. - - - Für Updates. Gibt an, welche Art von Überlappung in dieser Beziehung verwendet werden soll. - - - Zum Akzeptieren und Ablehnen von Änderungen. Gibt an, welche Art von Überlappung in dieser Beziehung verwendet werden soll. - - - Gibt die untergeordneten Spalten dieser Einschränkung an. - - - Gibt die übergeordneten Spalten dieser Einschränkung an. - - - Gibt die untergeordnete Tabelle dieser Einschränkung an. - - - Gibt die Spalten dieser Einschränkung an. - - - Gibt an, ob die Einschränkung ein Primärschlüssel ist. - - - Gibt den Namen dieser Einschränkung an. - - - Gibt die Tabelle dieser Einschränkung an. - - - Gibt an, ob in dieser Spalte null-Werte erlaubt sind. - - - Gibt an, ob die Spalte automatisch neue Zeilen, die zur Spalte hinzugefügt wurden, inkrementiert. Der Typ der Spalte muss Int16, Int32 oder Int64 sein. - - - Gibt den Startwert einer AutoIncrement-Spalte an. - - - Gibt die Schrittweite an, die von der AutoIncrement-Spalte verwendet wird. - - - Gibt die standardmäßige Schnittstellenbeschriftung für diese Spalte an. - - - Gibt den Namen an, der zum Suchen dieser Spalte in der Columns-Sammlung einer DataTable verwendet wurde. - - - Gibt die DataTable zurück, zu der diese Spalte gehört. - - - Gibt den Datentyp an, der in dieser Spalte gespeichert wird. - - - Gibt den Standardspaltenwert an, der beim Hinzufügen von neuen Spalten zur Tabelle verwendet wird. - - - Gibt den Wert an, den diese Spalte für alle Zeilen basierend auf anderen Spalten anstatt der Benutzereingabe berechnet. - - - Gibt an, wie diese Spalte in XML beibehalten wird: als Attribut, Element, einfacher Textknoten oder überhaupt nicht. - - - Gibt die XML-URI für Elemente oder Attribute an, die in dieser Spalte gespeichert sind. - - - Gibt das Prefix an, das für diese DataColumn in der xml-Darstellung verwendet wird. - - - Gibt den Index dieser Spalte in der Columns-Sammlung an. - - - Gibt an, ob diese Spalte Änderungen zulässt, nachdem eine Zeile zu der Tabelle hinzugefügt wurde. - - - Gibt an, ob diese Spalte die Werte in den Zeilen der Tabelle auf eindeutige Werte einschränken sollte. - - - Gibt die maximale Länge des in dieser Spalte zulässigen Werts an. - - - Gibt den DateTimeMode dieser DataColumn an. - - - Gibt an, ob diese DataView und die damit verbundene Schnittstelle Löschvorgänge zulassen. - - - Gibt an, ob diese DataView und die damit verbundene Schnittstelle Bearbeitungsvorgänge zulassen. - - - Gibt an, ob diese DataView und die damit verbundene Schnittstelle das Hinzufügen von neuen Zeilen zulassen. - - - Gibt die Anzahl der Elemente zurück, die sich momentan in dieser Sicht befinden. - - - Gibt einen Zeiger an den DataViewManager zurück, der den DataSet besitzt (falls vorhanden). - - - Gibt an, ob die Sicht geöffnet ist. - - - Gibt einen Ausdruck an, der verwendet wird, um die Daten zu filtern, die von dieser DataView zurückgegeben werden. - - - Gibt die Versionen der Daten an, die von dieser DataView zurückgegeben werden. - - - Gibt die Namen der Spalten und die Reihenfolge an, in der Daten von dieser DataView zurückgegeben werden. - - - Gibt an, ob die Standardsortierung verwendet wird, wenn die Sort-Eigenschaft nicht festgelegt wurde. - - - Gibt die Tabelle an, die diese DataView verwendet, um Daten abzurufen. - - - Gibt an, dass die Daten, die von dieser DataView zurückgegeben wurden, geändert wurden. - - - Zeigt die Datenquelle für den DataViewManager an. - - - Gibt die Sortier-/Filter-/Statuseinstellungen für jede Tabelle im entsprechenden DataSet an. - - - DataSet unterstützt 'union' oder 'list' nicht als simpleType. - - - Ungültige {0} Syntax: Fehlendes erforderliches '{1}'-Attribut. - - - Der Wert '{1}' ist für das Attribut '{0}' ungültig. - - - Der Wert des Attributs '{0}' sollte '{1}' oder '{2}' sein. - - - ElementType-name='{0}' wurde nicht gefunden. - - - Der übergeordnete Tabellenname ist nicht in der Beziehung '{0}' vorhanden. - - - Der untergeordnete Tabellenname ist nicht in der Beziehung '{0}' vorhanden. - - - Der übergeordnete Tabellenschlüssel ist nicht in der Beziehung '{0}' vorhanden. - - - Der untergeordnete Tabellenschlüssel ist nicht in der Beziehung '{0}' vorhanden. - - - Undefinierter Datentyp: '{0}'. - - - Datentyp wurde nicht definiert. - - - Ungültige XPath-Auswahl im Feldknoten. {0} kann nicht gefunden werden. - - - Ungültige XPath-Auswahl in einem Auswahlknoten: {0}. - - - Ungültiger 'Key'-Knoten innerhalb der Einschränkung: {0}. - - - Der Einschränkungsname {0} wird bereits im Schema verwendet. - - - '{0}' kann nicht in Typ {1} umgewandelt werden. - - - Fehlender '{0}'-Teil in '{1}'der Einschränkung '{2}'. - - - Ungültige Relationsdefinition: Unterschiedliche Schlüssellängen. - - - DataSet läßt den Zirkelverweis im ComplexType '{0}' nicht zu. - - - DataSet kann keinen abstrakten ComplexType für den Knoten {0} instanziieren. - - - Beim Schreiben eines Xml-Schemas hat der Konverter für mehrere Ziele einen Fehler ausgelöst. Weitere Informationen finden Sie in der inneren Ausnahme. - - - Beim Schreiben eines Xml-Schemas hat der Konverter für mehrere Ziele einen Fehler ausgelöst. Es wurde eine null- oder eine leere Zeichenfolge zurückgegeben. - - - Doppelte Deklaration '{0}'. - - - diffGram kann nicht geladen werden. In der Tabelle '{0}' fehlt das Ziel-Dataset. - - - diffGram kann nicht geladen werden. Der 'sql'-Knoten fehlt. - - - Der Spaltenname '{0}' ist für verschiedene Zuordnungstypen definiert. - - - Das Präfix '{0}' ist ungültig, da es Sonderzeichen enthält. - - - Zirkulärer Verweis in der selbstgeschachtelten Tabelle '{0}'. - - - Der DataSet kann die Einheiten nicht erweitern. Verwenden Sie XmlValidatingReader und legen Sie die EntityHandling-Eigenschaft entsprechend fest. - - - Der Typ '{0}' implementiert nicht die IXmlSerializable-Schnittstelle, weshalb die Serialisierung nicht fortgesetzt werden kann. - - - Deserialisierung kann nicht fortgesetzt werden. Die Daten implementieren nicht IXMLSerializable, weshalb keine Polymorphie unterstützt wird. - - - DataTable unterstützt keine Schemadeduktion von Xml. - - - Serialisierung von DataTable '{0}' kann nicht fortgesetzt werden. Sie enthält eine DataRow, die mehrere übergeordnete Zeilen für denselben Fremdschlüssel enthält. - - - Das 'IsDataSet'-Attribut fehlt im Eingabe-Schema. - - - Das DataSet-Element kann nicht bestimmt werden. Das IsDataSet-Attribut ist mehrmals vorhanden. - - - Typen, die IDynamicMetaObjectProvider implementieren, aber nicht gleichzeitig IXmlSerializable implementieren, werden von DataSet nicht serialisiert. - - - Diese Funktion ist nicht implementiert. {0}. - - - Syntaxfehler: Fehlender Operand nach dem Operator '{0}'. - - - Typenkonflikt im Ausdruck '{0}'. - - - Der Ausdruck ist zu komplex. - - - Die Spalte [{0}] wurde nicht gefunden. - - - Der Ausdruck enthält eine ungültige Zeichenfolgenkonstante: {0}. - - - Der Ausdruck enthält den undefinierten Funktionsaufruf {0}(). - - - Syntaxfehler im Ausdruck. - - - Ungültige Anzahl von Argumenten: Funktion {0}(). - - - Im Ausdruck fehlt die schließende Klammer. - - - Das Token '{0}' kann an Position {1} nicht interpretiert werden. - - - {0} wird erwartet. Das Token an Position {2} ist jedoch {1}. - - - Von {0} kann nicht in {1} konvertiert werden. - - - Der Wert „{0}“ kann nicht in den Typ „{1}“ konvertiert werden. - - - Ungültiger Spaltenname [{0}]. - - - Der Ausdruck enthält eine ungültige Datumskonstante '{0}'. - - - In der Ausdrucksliste für den IN-Operator sind nur konstante Ausdrücke zugelassen. - - - Fehler im Like-Operator: Das Zeichenfolgenmuster '{0}' ist ungültig. - - - Syntaxfehler: Die Elemente, die dem IN-Schlüsselwort folgen, müssen durch Kommata getrennt sein und in Klammern stehen. - - - Typenkonflikt in Funktionsargument: {0}(), Argument {1}, {2} wird erwartet. - - - Typenkonflikt in Funktionsargument: {0}(), Argument {1}, ein Ganzzahltyp wird erwartet. - - - Die Operation '{0}' kann nicht an {1} und {2} durchgeführt werden. - - - Der Operator '{0}' ist für Operanden vom Typ '{1}' und '{2}' nicht eindeutig. Signierte und unsignierte Typen können nicht gemeinsam verwendet werden. Verenden Sie eine explizite Convert()-Funktion. - - - Syntaxfehler: Das IN-Schlüsselwort muss von einer nicht leeren Liste von Ausdrücken gefolgt sein, die durch Kommata getrennt sind, und muss in Klammern stehen. - - - Der Ausdruck enthält den nicht unterstützten Operator '{0}'. - - - Der Ausdruck enthält den ungültigen Namen: '{0}'. - - - Syntaxfehler: Fehlender Operand vor dem Operator '{0}'. - - - Der Ausdruck hat zu viele schließende Klammern. - - - Die Tabelle [{0}] hat mehrere Beziehungen. Geben Sie einen eindeutigen Beziehungsnamen im Ausdruck '{1}' an. - - - Syntaxfehler im Aggregatargument: Es wird ein Einzelspaltenargument erwartet, das möglicherweise einen 'Child'-Qualifizierer hat. - - - Ungebundener Verweis im Aggregatausdruck '{0}'. - - - Ein nicht konstanter Ausdruck kann nicht ohne die aktuelle Zeile ausgewertet werden. - - - Ungebundene Verweise im Ausdruck '{0}'. - - - Auswerten nicht möglich. Der Ausdruck '{0}' ist kein Aggregat. - - - Der Filterausdruck „{0}“ wird nicht als boolescher Ausdruck ausgewertet. - - - Ungültiger Typname '{0}'. - - - Syntaxfehler in Lookup-Ausdruck: Es wird erwartet, dass das Schlüsselwort 'Parent' von nur einem Spaltenargument gefolgt ist, mit möglichem Beziehungsqualifizierer: Parent[(<Beziehung_Name>)].<Spalte_Name>. - - - Zum Aufrufen von DataFilter ist eine Zeile oder eine Tabelle erforderlich. - - - Das Argument {0}() befindet sich außerhalb des gültigen Bereichs. - - - Syntaxfehler: Ungültige Verwendung des 'Is'-Operators. Richtige Syntax: <Ausdruck> Is [Not] Null. + + Offset und Länge lagen für das Array außerhalb des gültigen Bereichs, oder die Anzahl war größer als die Anzahl der Elemente ab dem Index bis zum Ende der Quellsammlung. - - Der Wert ist zu groß oder zu klein für den Typ '{0}'. + + Ein Objekt vom Typ "{0}" kann nicht in ein Objekt vom Typ "{1}" konvertiert werden. - - Fehler aufgrund einer Division durch 0. + + Der einzige zulässige Wert für die Eigenschaft '{0}' ist '{1}'. - - Die übergeordnete Beziehung '{0}' kann nicht gefunden werden. + + Die zulässigen Werte für die Eigenschaft '{0}' sind '{1}' oder '{2}'. - - Das 'hours'-Argument liegt außerhalb des gültigen Bereichs. Der Wert muss zwischen -14 und +14 liegen. + + Die für QuotePrefix und QuoteSuffix angegebenen Werte stimmen nicht überein. - - Das 'minutes'-Argument liegt außerhalb des gültigen Bereichs. Der Wert muss zwischen -59 und +59 liegen. + + Die Länge des Arguments '{0}' überschreitet dessen Grenzwert von '{1}'. - - Der angegebene erste Zeitraum liegt über dem Gesamtwert von 14 Stunden. + + Ungültiger Wert für den {0}-Schlüssel. - - Die 'Kind'-Eigenschaft des angegebenen 'DateTime'-Arguments stimmt nicht mit den Argumenten 'hours' und 'minutes' überein. + + Für die Verwendung des Schlüssels '{0}' muss der Schlüssel '{1}' vorhanden sein. - - Eine DataColumn vom Typ '{0}' wird vom Ausdruck nicht unterstützt. + + Die Länge des Werts für Taste '{0}' überschreitet dessen Grenzwert von '{1}'. - - Einschränkungen konnten nicht aktiviert werden. Mindestens eine Zeile enthält Werte die die Einschränkungen non-null, unique or foreign-key verletzen. + + Schlüsselwort wird nicht unterstützt: '{0}'. - - Sammlung kann nicht geändert werden. + + Interner .NET Framework-Datenanbieterfehler {0}. - - Der angegebene Name '{0}' stimmt mit mindestens zwei Namen im Sammlungsobjekt mit unterschiedlicher Groß-/Kleinschreibung überein. + + {0} "{1}". - - Der angegebene Name '{0}' entspricht mindestens zwei Namen im Sammlungsobjekt mit unterschiedlichen Namespaces. + + {0} "{1}", falsche Verwendung von Anführungszeichen. - - Offset und Länge lagen für das Array außerhalb des gültigen Bereichs, oder die Anzahl war größer als die Anzahl der Elemente ab dem Index bis zum Ende der Quellsammlung. + + {0} "{1}", der aktuelle Grenzwert von "{2}" ist unzureichend. - - '{0}'-Argument liegt außerhalb des gültigen Bereichs. + + Fehler bei 'SqlBulkCopy.WriteToServer'. 'SqlBulkCopy.DestinationTableName' ist ein ungültiger mehrteiliger Name. - - '{0}'-Argument darf nicht null sein. + + Fehler bei der Verarbeitung von SQL Server-Ergebnissen aufgrund eines ungültigen mehrteiligen Namens. - - '{0}'-Argument enthält Nullwerte. + + 'SqlParameter.UdtTypeName' ist ein ungültiger mehrteiliger Name. - - Spalte {0} wurde nicht gefunden. + + SqlParameter.TypeName ist ein ungültiger mehrteiliger Name. - - Spalte '{0}' gehört bereits zu dieser DataTable. + + Fehler bei 'SqlCommand.DeriveParameters'. Der Wert der 'SqlCommand.CommandText'-Eigenschaft ist ein ungültiger mehrteiliger Name. - - Spalte '{0}' gehört bereits zu einer anderen DataTable. + + Die Verbindung mit einer gespiegelten SQL Server-Instanz unter Verwendung der MultiSubnetFailover-Verbindungsoption wird nicht unterstützt. - - Eine DataTable kann nur eine SimpleContent-Spalte enthalten. + + Der Enumerationswert von {0}, {1}, wird von der Methode {2} nicht unterstützt. - - Eine SimpleContent-Spalte kann nicht zu einer Tabelle mit Elementspalten oder geschachtelten Beziehungen hinzugefügt werden. + + Der Enumerationswert für {0} ({1}) wird vom .NET Framework-SqlClient-Datenanbieter nicht unterstützt. - - Eine Spalte names '{0}' gehört bereits zu dieser DataTable. + + {0}: CommandText-Eigenschaft wurde nicht initialisiert. - - Spalte '{0}' konnte nicht hinzugefügt werden: eine verschachtelte Tabelle mit demselben Namen gehört bereits zu dieser Tabelle 'DataTable'. + + {0}: Connection-Eigenschaft wurde nicht initialisiert. - - Spalte '{0}' gehört bereits zu dieser Tabelle 'DataTable'. Ein verschachtelter Tabellenname kann für denselben Namen nicht festgelegt werden. + + {0} erfordert eine geöffnete und verfügbare Connection. {1} - - Eine Spalte, die nicht zu dieser Tabelle gehört, kann nicht entfernt werden. + + Die gespeicherte Prozedur '{0}' ist nicht vorhanden. - - Diese Spalte kann nicht entfernt werden, da sie Teil des Primärschlüssels ist. + + Die Transaktion ist entweder nicht der aktuellen Verbindung zugeordnet oder wurde abgeschlossen. - - Diese Spalte kann nicht entfernt werden, da sie Teil des übergeordneten Schlüssels für den Bezug {0} ist. + + Die der aktuellen Verbindung zugeordnete Transaktion wurde abgeschlossen, jedoch nicht verworfen. Die Transaktion muss verworfen werden, bevor die Verbindung zum Ausführen von SQL-Anweisungen verwendet werden kann. - - Diese Spalte kann nicht entfernt werden, da sie Teil der Einschränkung {0} für die Tabelle {1} ist. + + {0} erfordert, dass der Befehl über eine Transaktion verfügt, wenn die dem Befehl zugewiesene Verbindung eine ausstehende lokale Verbindung ist. Die Transaction-Eigenschaft des Befehls wurde nicht initialisiert. - - Diese Spalte kann nicht entfernt werden, da sie Teil eines Ausdrucks ist: {0} = {1}. + + Diesem {0}selement ist bereits ein geöffneter DataReader zugeordnet, der zuerst geschlossen werden muss. - - Die AutoIncrement-Eigenschaft kann nicht für eine berechnete Spalte festgelegt werden. + + {0} DeriveParameters unterstützen nur CommandType.StoredProcedure, nicht CommandType.{1}. - - Die AutoIncrement-Eigenschaft kann nicht für eine Spalte festgelegt werden, deren DefaultValue festgelegt ist. - - - Ein DefaultValue kann für eine AutoIncrement-Spalte nicht festgelegt werden. - - - AutoIncrementStep muss ein Wert sein, der nicht Null ist. - - - Der ColumnName ist erforderlich wenn er Teil einer DataTable ist. - - - Der DataType einer Spalte kann nicht geändert werden, wenn diese Daten enthält. - - - Spalte erfordert gültigen DataType. - - - Der DefaultValue für Spalte {0} ist vom Typ {1} und kann nicht in {2} konvertiert werden. - - - Der DefaultValue der Spalte ist vom Typ {0} und kann nicht in {1} konvertiert werden. - - - Der DefaultValue für Spalte {0} ist vom Typ {1}, aber die Spalte ist vom Typ {2}. - - - ReadOnly-Eigenschaft für die Ausdruckspalte kann nicht geändert werden. - - - Die Unique-Eigenschaft der Ausdrucksspalte kann nicht geändert werden. - - - Ein Ausdruck kann nicht für eine Spalte erstellt werden, die AutoIncrement oder Unique hat. - - - Der Ausdruck kann nicht festgelegt werden, da die Spalte nicht über ReadOnly mit einem Schreibschutz versehen werden kann. - - - Die Expression-Eigenschaft kann für Spalte {0} nicht festgelegt werden, da sie Teil einer Einschränkung ist. - - - Eine Einschränkung basierend auf Expression-Spalte {0} kann nicht erstellt werden. - - - Die Expression-Eigenschaft kann nicht festgelegt werden, da der Ausdruck einen zirkulären Verweis enthält. - - - Spalte '{0}' hat null Werte. - - - Spalte '{0}' läßt nicht 'nulls' zu. - - - Spalte '{0}' ist schreibgeschützt. - - - Spalte '{0}' enthält nicht eindeutige Werte. - - - Spalte '{0}' gehört nicht zu Tabelle {1}. - - - Spalte gehört in eine Tabelle. - - - <{0}> konnte nicht in der {1}-Spalte gespeichert werden. Erwarteter Typ: {2}. - - - Column '{0}' kann nicht auf 'null' gesetzt werden. Verwenden Sie statt dessen DBNull. - - - Die Spalte kann nicht auf '{0}' festgelegt werden. Der Wert überschreitet den MaxLength-Grenzwert dieser Spalte. - - - MaxLength trifft nur auf den Datentyp der Zeichenfolge zu. Die MaxLength-Eigenschaft von Column '{0}' kann nicht auf eine positive Zahl festgelegt werden. - - - Die MaxLength-Eigenschaft von Column '{0}' kann nicht auf '{1}' festgelegt werden. Mindestens eine Zeichenfolge in der Tabelle überschreitet die neue Grenze. - - - Die MaxLength-Eigenschaft für die Spalte '{0}' kann nicht festgelegt werden. Column ist SimpleContent. - - - Die DataType-Eigenschaft für die Spalte '{0}' kann nicht auf {1} festgelegt werden. Column ist SimpleContent. - - - Die MappingType-Eigenschaft für die Spalte '{0}' kann nicht auf SimpleContent festgelegt werden. Der DataType von Column ist {1}. - - - Column '{0}' überschreitet das MaxLength-Limit. - - - Die Spalte '{0}' lässt DBNull.Value nicht zu. - - - Die Namespace-Eigenschaft für die Spalte '{0}' kann nicht geändert werden. Column ist SimpleContent. - - - AutoIncrement einer DataColumn mit dem Typ '{0}' kann nicht geändert werden, sobald diese Daten enthält. - - - Die Spalte '{0}' gehört nicht zur zugrunde liegenden Tabelle '{1}'. - - - 'DataColumn' mit Typ '{0}' ist ein 'complexType'. Der Wert eines 'complexType' kann nicht als 'Attribute' serialisiert werden. - - - Der DateTimeMode kann nur für DataColumns vom Typ DateTime festgelegt werden. - - - '{0}' ist ein ungültiger DataSetDateTime-Wert. - - - Der 'DateTimeMode' kann nicht von '{0}' in '{1}' geändert werden, sobald die Tabelle Daten enthält. - - - Type '{0}' enthält nicht die static Null-Eigenschaft bzw. das Null-Feld. - - - Der Typ '{0}' implementiert nicht IRevertibleChangeTracking. Deswegen ist eine Fortsetzung mit RejectChanges() nicht möglich. - - - SetAdded und SetModified können nur für DataRows mit Unchanged DataRowState aufgerufen werden. - - - Die Ordnungszahl „{0}“ überschreitet den maximalen Wert. - - - 'DataSet' unterstützt nicht System.Nullable<>. - - - Der Name einer Einschränkung kann nicht in eine leere Zeichenfolge geändert werden, wenn sie sich in der ConstraintCollection befindet. - - - Für die Einschränkung {0} können keine Einschränkungen erzwungen werden. - - - Die Spalte '{0}' hat die Einschränkung, dass sie eindeutig sein muss. Der Wert '{1}' ist bereits vorhanden. - - - Die Einschränkung '{0}' gehört nicht zu dieser DataTable. - - - Die Einschränkung {0} wurde nicht gefunden. - - - Die Einschränkung stimmt mit der Einschränkung {0} überein, die sich bereits in der Sammlung befindet. - - - Eine Constraint mit dem Namen '{0}' gehört bereits zu dieser DataTable. - - - Diese Spalten haben momentan keine eindeutigen Werte. - - - Diese Spalten zeigen nicht auf diese Tabelle. - - - Diese Einschränkung kann nicht aktiviert werden, da nicht alle Werte entsprechende übergeordnete Werte besitzen. - - - Diese Einschränkung kann nicht hinzugefügt werden, da ForeignKey nicht zu Tabelle {0} gehört. - - - Eine Einschränkung, die nicht zu dieser Tabelle gehört, kann nicht entfernt werden. - - - Die eindeutige Einschränkung '{0}' kann nicht entfernt werden. Entfernen Sie zuerst die Fremdschlüsseleinschränkung '{1}'. - - - Diese Zeile kann nicht gelöscht werden, da für die Beziehung {0} Einschränkungen erzwungen werden, und das Löschen dieser Zeile untergeordnete Zeilen freilegen würde. - - - Diese Änderung kann nicht durchgeführt werden, da für die Beziehung {0} Einschränkungen erzwungen werden und durch Ändern dieses Wertes untergeordnete Zeilen freigelegt werden. - - - Die Tabelle {0} kann nicht gelöscht werden, da ForeignKeyConstraint {1} Einschränkungen erzwingt und sich untergeordnete Zeilen in {2} befinden. - - - Für ForeignKeyConstraint {0} müssen die untergeordneten Schlüsselwerte ({1}) in der übergeordneten Tabelle vorhanden sein. - - - Auf die Eigenschaft kann nicht zugegriffen werden, da: '{0}'. - - - Diese Zeile kann nicht entfernt werden, da sie untergeordnete Zeilen enthält, und Einschränkungen für die Beziehung {0} erzwungen werden. - - - Die Primärschlüsseleinschränkung kann nicht hinzugefügt werden, da der Primärschlüssel für die Tabelle bereits festgelegt wurde. - - - Die Einschränkung kann nicht zu DataTable '{0}' hinzugefügt werden, bei der es sich um eine untergeordnete Tabelle in zwei geschachtelten Beziehungen handelt. - - - Für Spalten, die zu unterschiedlichen Tabellen gehören, kann kein Schlüssel erstellt werden. - - - Die Anzahl der Spalten kann nicht 0 sein. - - - Mehr als {0} Spalten sind unzulässig. - - - Ein Schlüssel kann nicht erstellt werden, wenn dieselbe Spalte mehrmals aufgeführt wird: '{0}' - - - Die eindeutige Einschränkung kann nicht entfernt werden, da sie der Primärschlüssel einer Tabelle ist. - - - Die eindeutige Einschränkung kann nicht entfernt werden, da sie der Primärschlüssel der Tabelle {0} ist. - - - Übergeordnete Spalten und untergeordnete Spalten haben keine Spalten mit Typübereinstimmung. - - - ParentKey und ChildKey sind identisch. - - - ParentColumns und ChildColumns sollten dieselbe Länge haben. - - - ParentColumns und ChildColumns dürfen nicht die Länge null haben. - - - Die Zeile gehört nicht zum selben DataSet wie diese Beziehung. - - - RelationName ist erforderlich, wenn er Teil eines DataSet ist. - - - GetChildRows erfordert eine Zeile, deren Table {0} ist. Die Table der angegebenen Zeile ist jedoch {1}. - - - Diese Beziehung sollte zwei Tabellen in diesem DataSet verbinden, die dem DataSet hinzugefügt werden sollen. - - - GetParentRow erfordert eine Zeile, deren Tabelle {0} ist. Die Tabelle der angegebenen Zeile ist jedoch {1}. - - - SetParentRow erfordert eine untergeordnete Zeile, deren Tabelle {0} ist. Die Tabelle der angegebenen Zeile ist jedoch {1}. - - - Eine Beziehung zwischen Tabellen in unterschiedlichen DataSets ist nicht möglich. - - - Es kann keine Beziehung zwischen Tabellen in unterschiedlichen DataSets hergestellt werden. - - - Bezug für diese untergeordneten Spalten ist bereits vorhanden. - - - Diese Beziehung gehört nicht zu dieser Beziehungssammlung. - - - Dieser Bezug gehört bereits zu einen anderen DataSet. - - - Dieser Bezug gehört bereits zu diesen DataSet. - - - Eine Relation mit dem Namen '{0}' ist bereits in diesem DataSet vorhanden. - - - Die Relation {0} gehört nicht in diesen DataSet. - - - Bezug {0} wurde nicht gefunden. - - - Eine Sammlung kann nicht für eine null-Tabelle erstellt werden. - - - Die Tabelle, für die diese Sammlung Beziehungen anzeigt, wurde aus dem DataSet entfernt. - - - Es kann keine Beziehung zur ParentRelation-Sammlung dieser Tabelle hinzugefügt werden, bei der dieser Tabelle nicht die untergeordnete Tabelle ist. - - - Es kann keine Beziehung zur ChildRelation-Sammlung dieser Tabelle hinzugefügt werden, in der diese Tabelle nicht die übergeordnete Tabelle ist. - - - Die 'Nested'-Eigenschaft kann für diese Beziehung nicht auf 'false' gesetzt werden. - - - Dieselbe Tabelle '{0}' kann in zwei geschachtelten Beziehungen nicht die untergeordnete Tabelle sein. - - - Die Tabelle ({0}) kann in geschachtelten Beziehungen nicht sich selbst untergeordnet sein. - - - DataRelation oder Constraint mit unterschiedlichen Locale- oder CaseSensitive-Einstellungen in den übergeordneten und untergeordneten Tabellen kann nicht hinzugefügt werden. - - - DataRelation kann nicht erstellt werden, wenn über- und untergeordneten Spalten nicht in einem DataSet enthalten sind. - - - Die geschachtelte Tabelle '{0}', die ihren Namespace erbt, darf nicht über mehrere übergeordnete Tabellen in verschiedenen Namespaces verfügen. - - - Die geschachtelte Tabelle '{0}' mit leerem Namespace darf nicht über mehrere übergeordnete Tabellen in verschiedenen Namespaces verfügen. - - - Die Zeile gehört nicht zum selben DataSet wie diese Beziehung. - - - Diese Operation kann nicht an einer Zeile durchgeführt werden, die sich nicht in der Tabelle befindet. - - - Diese Beziehung und diese untergeordnete Zeile gehören nicht zu demselben DataSet. - - - Ein vorgeschlagener Wert kann nicht im RowChanging-Ereignis geändert werden. - - - EndEdit() kann nicht innerhalb eines OnRowChanging-Ereignisses aufgerufen werden. - - - BeginEdit() kann nicht innerhalb des RowChanging-Ereignisses aufgerufen werden. - - - CancelEdit() kann nicht innerhalb eines OnRowChanging-Ereignisses aufgerufen werden. Verwenden Sie eine exception, um das Update abzubrechen. - - - Delete kann nicht innerhalb eines OnRowDeleting-Ereignisses aufgerufen werden. Verwenden Sie eine exception, um diesen Löschvorgang abzubrechen. - - - Der Eingabearray ist länger als die Anzahl der Spalten in dieser Tabelle. - - - Es sind keine aktuellen Daten vorhanden, auf die zugegriffen werden kann. - - - Es sind keine Originaldaten vorhanden, auf die zugegriffen werden kann. - - - Es sind keine vorgeschlagenen Daten vorhanden, auf die zugegriffen werden kann. - - - Diese Zeile wurde aus einer Tabelle entfernt und enthält keine Daten. BeginEdit() ermöglicht das Erstellen von neuen Daten in dieser Zeile. - - - Auf gelöschte Zeileninformationen kann nicht über die Zeile zugegriffen werden. - - - Die Version muss Original, Aktuell oder Vorgeschlagen sein. - - - An der Position {0} befindet sich keine Zeile. - - - Der Zeileneinfügepunkt {0} ist ungültig. - - - rowOrder value={0} wurde zweimal für die Tabelle '{1}' gefunden. - - - In der rowOrder-Sequenz für die Tabelle '{0}' fehlen Werte. - - - Die angegebene DataRow befindet sich nicht in der aktuellen DataRowCollection. - - - Diese Zeile gehört bereits zu einer anderen Tabelle. - - - Diese Zeile gehört bereits zu dieser Tabelle. - - - Diese Zeile kann nicht gelöscht werden, da sie bereits gelöscht ist. - - - Diese Zeile ist leer. - - - Eine Zeile, die bereits entfernt ist, kann nicht entfernt werden. - - - Eine untergeordnete Zeile hat mehrere übergeordnete Zeilen. - - - Nicht erkanntes Bitmuster für Zeilenstatus. - - - Der Name des DataSet kann nicht in eine leere Zeichenfolge geändert werden. - - - Der Name '{0}' ist ungültig. Ein DataSet kann nicht den gleichen Namen wie die DataTable haben. - - - Der Schemanamespace ist ungültig. Verwenden Sie statt dessen {0}. - - - Die CaseSensitive- oder Locale-Eigenschaft kann nicht geändert werden. Die Änderung würde dazu führen, dass mindestens eine DataRelation oder Constraint unterschiedliche Locale- oder CaseSensitive-Einstellungen in den verknüpften Tabellen hat. - - - Die 'SchemaSerializationMode'-Eigenschaft kann nur festgelegt werden, wenn sie vom abgeleiteten DataSet überschrieben wurde. - - - PrimaryKey-Spalten gehören nicht zu dieser Tabelle. - - - Eine geschachtelte Beziehung oder eine Elementespalte kann nicht zu einer Tabelle mit einer SimpleContent-Spalte hinzugefügt werden. - - - Der TableName ist erforderlich wenn er Teil eines DataSets ist. - - - DataTable enthält bereits eine einfache Inhaltsspalte. - - - Die Tabelle hat keine Primärschlüssel. - - - {0} ist kein gültiger Sortierzeichenfolgen-Eintrag. - - - Die DataTable kann nicht serialisiert werden. Eine DataTable, die in einem oder mehreren DataColumn-Ausdrücken verwendet wird, ist kein nachfolgender Wert der aktuellen DataTable. - - - Diese DataTable kann nur als Teil eines DataSet als remote festgelegt werden. Eine oder mehrere Expression Columns weisen einen Verweis auf andere DataTable(s) auf. - - - Für DataSet und DataTable ist kein unterschiedlicher Remote-Formateigenschaftenwert möglich. - - - Die DataTable kann nicht serialisiert werden. Der DataTable-Name ist nicht festgelegt. - - - Eine DataTable mit dem Namen '{0}' gehört bereits zu diesem DataSet. - - - Eine DataTable mit dem Namen '{0}' mit demselben Namespace '{1}' gehört bereits zu diesem DataSet. - - - Die Tabelle ({0}) kann in einer geschachtelten Beziehung keine untergeordnete Tabelle von sich selbst sein. Der DataSet-Name steht mit dem Tabellennamen in Konflikt. - - - Der Name '{0}' ist ungültig. Eine DataTable darf nicht den gleichen Namen wie der DataSet haben. - - - Die DataTable gehört bereits zu einem anderen DataSet. - - - Die DataTable gehört bereits zu diesem DataSet. - - - Die Tabelle {0} gehört nicht zu diesem DataSet. - - - Die Tabelle {0} kann nicht gefunden werden. - - - Eine Tabelle, für die Beziehungen vorhanden sind, kann nicht entfernt werden. Entfernen Sie zuerst die Beziehungen. - - - Die Tabelle {0} kann nicht entfernt werden, da auf sie in der ForeignKeyConstraint {1} verwiesen wird. Entfernen Sie zuerst diese Fremdschlüsseleinschränkung. - - - Die DataTable '{0}' stimmt mit keiner DataTable in der Quelle überein. - - - Ziel-DataSet hat keine Definition für {0}. - - - Ziel-DataSet hat kein {0} {1}. - - - <target>.{0} und <source>.{0} haben Eigenschaften, die miteinander im Konflikt stehen: keine DataType-Eigenschaftenübereinstimmung. - - - <target>.PrimaryKey und <source>.PrimaryKey haben unterschiedliche Length-Werte. - - - Spaltenkonflikt im PrimaryKey: <target>.{0} im Vergleich zu <source>.{1}. - - - Die Beziehung {0} kann nicht zusammengeführt werden, da die Schlüssel nicht übereinstimmende Spalten aufweisen. - - - Zieltabelle {0} hat keine Definition für Spalte {1}. - - - PrimaryKey-Spalte {0} ist in der Quelltabelle nicht vorhanden. - - - Der RowStates-Parameter muss auf eine gültige Wertkkombination aus der DataViewRowState-Enumeration gesetzt sein. - - - Sucht eine Zeile, basierend auf Sortierreihenfolge. Es wurde jedoch keine Sortierreihenfolge bestimmt. - - - Es werden {0}-Wert(e) für den Schlüssel, der indiziert wird, erwartet. Es wurden jedoch {1}-Wert(e) empfangen. - - - Ungültige Verwendung von Aggregatfunktion {0}() und Typ: {1}. - - - Ungültiger Speichertyp: {0}. - - - Die DataSet Xml-Persistenz unterstützt den Wert '{0}' nicht als Char-Wert. Verwenden Sie stattdessen den Byte-Speicher. - - - Der Typ des Werts stimmt nicht mit dem Spaltentyp überein - - - Der Typ '{0}' implementiert nicht die IComparable-Schnittstelle. Es erfolgt kein Vergleich. - - - {0} kann nicht festgelegt werden. - - - Ein DataSet kann in DataViewManager nicht geändert werden, wenn diese bereits die Standardsicht eines DataSets ist. - - - RowStateFilter kann nicht gleichzeitig ModifiedOriginals und ModifiedCurrents anzeigen. - - - Die Table-Eigenschaft kann in einer DefaultView oder einer DataView, die aus einem DataViewManager stammt, nicht geändert werden. - - - Die Eigenschaft 'DataSet' kann nicht geändert werden, nachdem sie festgelegt wurde. - - - DataSet muss vor der Verwendung von DataViewManager festgelegt werden. - - - Die Eigenschaft 'Table' kann nicht geändert werden, nachdem sie festgelegt wurde. - - - DataTable muss vor der Verwendung von DataView festgelegt werden. - - - Bindung an unbenannte DataTable nicht möglich. - - - Ein Objekt kann dieser List nicht hinzugefügt werden. - - - AddNew kann nicht für eine DataView aufgerufen werden, für die AllowNew auf False gesetzt ist. - - - DataView ist nicht geöffnet. - - - Die Beziehung ist der Tabelle, auf diese DataView zeigt, nicht übergeordnet. - - - Löschvorgang für DataSource ist nicht möglich, wenn AllowDelete auf 'false' gesetzt ist. - - - Eine DataSource kann nicht bearbeitet werden, wenn AllowEdit auf 'false' gesetzt ist. - - - Der Index {0} ist entweder negativ oder über der Gesamtanzahl der Zeilen. - - - Externe Objekte können nicht zur Liste hinzugefügt werden. - - - Diese Liste kann nicht gelöscht werden. - - - Externe Objekte können nicht in diese List eingefügt werden. - - - Objekt, die sich nicht in der Liste befinden, können nicht entfernt werden. - - - {0} ist keine DataColumn oder DataRelation für die Tabelle {1}. - - - In einem Range-Objekt muss Min ({0}) kleiner oder gleich max ({1}) sein. - - - Dies ist ein null-Bereich. - - - MinimumCapacity darf nicht negativ sein. - - - Der Bezeichner für den Namen "{0}" kann nicht generiert werden. - - - Es sind mehrere Tabellen mit demselben Namen '{0}' (wenngleich in verschiedenen Namespaces) vorhanden. - - - Spalte {0}: Typ {1} darf nicht NULL sein. - - - Spalte '{0}': Typ '{1}' hat keinen parameterlosen Konstruktor. - - - Spalte '{0}': Typ '{1}' hat keinen Konstruktor mit einem Zeichenfolgenargument. - - - Ein Objekt vom Typ "{0}" kann nicht in ein Objekt vom Typ "{1}" konvertiert werden. - - - Data Exception. - - - Constraint Exception. - - - Zugriff auf gelöschte Zeilen nicht möglich. - - - Doppelte Namen nicht zulässig. - - - Operation wird innerhalb des RowChanging-Ereignisses nicht unterstützt. - - - Ungültige Einschränkung. - - - Fehlender Primärschlüssel. - - - Null nicht zulässig. - - - Spalte ist als schreibgeschützt markiert. - - - Zeile in Tabelle nicht gefunden. - - - Version nicht gefunden. - - - ReadOnly-Daten wurden geändert. - - - DataTableReader ist für die aktuelle DataTable '{0}' ungültig. - - - Das Schema der aktuellen DataTable '{0}' in DataTableReader hat sich geändert. DataTableReader ist ungültig. - - - DataTableReader kann nicht erstellt werden. Es gibt keine DataTable im DataSet. - - - DataTableReader kann nicht erstellt werden. Argument ist leer. - - - DataTableReader kann nicht erstellt werden. Argumente enthalten null-Wert. - - - Die aktuelle DataRow hat entweder den Status Deleted' oder 'Detached'. - - - Aktuelle DataTable '{0}' ist leer. Es gibt keine DataRow in der DataTable. - - - Der interne DataTable-Index ist beschädigt: '{0}'. - - - Die Sammlung wurde geändert; möglicherweise wurde die Enumeration nicht ausgeführt. - - - Der einfache Typ „{0}“ wurde bereits mit einem anderen „{1}“ deklariert. - - - Ungültige Paginierung. - - - Der Tabellenname kann nicht geändert werden, wenn der verknüpfte DataSet einem geladenen XML-Dokument zugeordnet ist. - - - Der Tabellennamespace kann nicht geändert werden, wenn der verknüpfte DataSet einem geladenen XML-Dokument zugeordnet ist. - - - Der Spaltenname kann nicht geändert werden, wenn der verknüpfte DataSet einem geladenen XML-Dokument zugeordnet ist. - - - Der Spaltennamespace kann nicht geändert werden, wenn der verknüpfte DataSet einem geladenen XML-Dokument zugeordnet ist. - - - Die ColumnMapping-Eigenschaft kann nicht mehr geändert werden, nachdem das verknüpfte DataSet dem geladenen XML-Dokument zugeordnet wurde. - - - Spalten der Tabelle können nicht mehr hinzugefügt oder entfernt werden, sobald das DataSet einem geladenen XML-Dokument zugeordnet ist. - - - Tabellen des DataSets können nicht mehr hinzugefügt oder entfernt werden, sobald das DataSet einem geladenen XML-Dokument zugeordnet ist. - - - Geschachtelte Beziehungen des DataSets können nicht mehr hinzugefügt, entfernt, oder geändert werden, sobald das DataSet einem geladenen XML-Dokument zugeordnet ist. - - - Der DataSet-Parameter ist ungültig. Er darf nicht 'null' sein. - - - Der DataSet-Name kann nicht geändert werden, wenn der DataSet einem geladenen XML-Dokument zugeordnet ist. - - - Dieser Knotentyp kann nicht geklont werden: {0}. - - - XmlDataDocument kann nicht geladen werden, wenn es bereits Daten enthält. Verwenden Sie ein neues XmlDataDocument. - - - DataSet kann höchstens mit einem XmlDataDocument verknüpft werden. DataSet kann nicht mit dem aktuellen XmlDataDocument verknüpft werden, da der DataSet bereits mit einem anderen XmlDataDocument verknüpft ist. - - - Legen Sie DataSet.EnforceConstraints == false fest, bevor Sie das XmlDataDocument mit XML-Vorgängen bearbeiten. - - - GetElementById() wird auf DataDocument nicht unterstützt. - - - Es können keine Entitätsverweise auf ein DataDocument erstellt werden. - - - Clear-Funktion für DateSet und DataTable wird für XmlDataDocument nicht unterstützt. - - - Die Spalte kann nicht entfernt werden, da sie in das 'dataSet' eingebaut ist. - - - Die Beziehung kann nicht entfernt werden, da sie in dieses DataSet integriert ist. - - - Tritt auf, wenn eine Eigenschaft für das Steuerelement geändert wird. - - - Tritt auf, wenn sich die Mitgliedschaft dieser Sammlung ändert. - - - Der Wert kann nicht abgerufen werden, da er DBNull ist. - - - Für die '{0}'-Eigenschaft ist Microsoft WindowsNT oder ein WindowsNT-basiertes Betriebssystem erforderlich. - - - Der angeforderte .NET Framework-Datenanbieter wurde nicht gefunden. Möglicherweise ist er nicht installiert. - - - Die angeforderte Implementierung des .NET Framework-Datenanbieters umfasst kein Instanzenfeld eines abgeleiteten System.Data.Common.DbProviderFactory-Typs. - - - Der registrierte .NET Framework-Datenanbieter wurde nicht gefunden oder kann nicht geladen werden. - - - Der fehlende Name mit Assemblyqualifikation des .NET Framework-Datenanbieters ist erforderlich. - - - Nur Elemente sind zulässig. - - - Untergeordnete Knoten sind nicht zulässig. - - - Nicht erkanntes Attribut '{0}'. - - - Unbekanntes Element. - - - Der Abschnitt {0} darf pro Konfigurationsdatei nur einmal angegeben werden. - - - Das erforderliche Attribut "{0}" wurde nicht gefunden. - - - Das erforderliche Attribut '{0}' darf nicht leer sein. - - - Für den Parameter '{0}' wird kein leeres Array erwartet. - - - Der einzige zulässige Wert für die Eigenschaft '{0}' ist '{1}'. - - - Die zulässigen Werte für die Eigenschaft '{0}' sind '{1}' oder '{2}'. - - - Die für QuotePrefix und QuoteSuffix angegebenen Werte stimmen nicht überein. - - - Die Länge des Arguments '{0}' überschreitet dessen Grenzwert von '{1}'. - - - Erwartet wird ein Argument vom Typ {1}, empfangen wurde jedoch Typ {0}. - - - Ungültiger Wert für den {0}-Schlüssel. - - - Für die Verwendung des Schlüssels '{0}' muss der Schlüssel '{1}' vorhanden sein. - - - Die Länge des Werts für Taste '{0}' überschreitet dessen Grenzwert von '{1}'. - - - Schlüsselwort wird nicht unterstützt: '{0}'. - - - UDL-Datei konnte nicht geladen werden. - - - Ungültige UDL-Datei. - - - Interner .NET Framework-Datenanbieterfehler {0}. - - - Die Eigenschaften 'QuotePrefix' und 'QuoteSuffix' können nicht geändert werden, wenn Insert, Update oder Delete generiert wurde. - - - Die Eigenschaft 'DataAdapter.SelectCommand' muss initialisiert werden. - - - Die Eigenschaft DataAdapter.SelectCommand.Connection muss initialisiert werden. - - - {0} "{1}". - - - {0} "{1}", falsche Verwendung von Anführungszeichen. - - - {0} "{1}", der aktuelle Grenzwert von "{2}" ist unzureichend. - - - Fehler bei 'SqlBulkCopy.WriteToServer'. 'SqlBulkCopy.DestinationTableName' ist ein ungültiger mehrteiliger Name. - - - Fehler bei der Verarbeitung von SQL Server-Ergebnissen aufgrund eines ungültigen mehrteiligen Namens. - - - 'SqlParameter.UdtTypeName' ist ein ungültiger mehrteiliger Name. - - - SqlParameter.TypeName ist ein ungültiger mehrteiliger Name. - - - Fehler bei 'SqlCommand.DeriveParameters'. Der Wert der 'SqlCommand.CommandText'-Eigenschaft ist ein ungültiger mehrteiliger Name. - - - Fehler bei 'OdbcCommandBuilder.DeriveParameters'. Der Wert der 'OdbcCommand.CommandText'-Eigenschaft ist ein ungültiger mehrteiliger Name. - - - Fehler bei 'OleDbCommandBuilder.DeriveParameters'. Der Wert der 'OleDbCommandBuilder.CommandText'-Eigenschaft ist ein ungültiger mehrteiliger Name. - - - Die Verbindung mit einer gespiegelten SQL Server-Instanz unter Verwendung der MultiSubnetFailover-Verbindungsoption wird nicht unterstützt. - - - Die Spaltenzuordnung von SourceColumn '{0}' ist fehlgeschlagen, da die DataColumn '{1}' ist berechnete Spalte ist. - - - Nicht konvertierbarer Typenkonflikt zwischen SourceColumn '{0}' von {1} und DataColumn '{2}' von {3}. - - - DataColumn '{0}' in der DataTable '{1}' für SourceColumn '{2}' ist nicht vorhanden. - - - SourceColumn muss eine nicht leere Zeichenfolge sein. - - - Fehlende SourceColumn-Zuordnung für '{0}'. - - - Der Enumerationswert von {0}, {1}, wird von der Methode {2} nicht unterstützt. - - - Der Enumerationswert für {0} ({1}) wird vom .NET Framework-ODBC-Datenanbieter nicht unterstützt. - - - Der Enumerationswert für {0} ({1}) wird vom .NET Framework-OLEDB-Datenanbieter nicht unterstützt. - - - Der Enumerationswert für {0} ({1}) wird vom .NET Framework-SqlClient-Datenanbieter nicht unterstützt. - - - 'ComputerNameDnsFullyQualified', {0}, kann nicht abgerufen werden. - - - Fehlende '{0}' DataTable für die '{1}' SourceTable. - - - SourceTable muss eine nicht leere Zeichenfolge sein. - - - Fehlende SourceTable-Zuordnung: '{0}' - - - {0}: CommandText-Eigenschaft wurde nicht initialisiert. - - - {0}: Connection-Eigenschaft wurde nicht initialisiert. - - - {0} erfordert eine geöffnete und verfügbare Connection. {1} - - - Fill: SelectCommand.Connection-Eigenschaft wurde nicht initialisiert. - - - FillPage: SelectCommand.Connection-Eigenschaft wurde nicht initialisiert. - - - FillSchema: SelectCommand.Connection-Eigenschaft wurde nicht initialisiert. - - - Für ein Update benötigt der InsertCommand ein Verbindungsobjekt. Die Connection-Eigenschaft des InsertCommand wurde nicht initialisiert. - - - Für ein Update benötigt der UpdateCommand ein Verbindungsobjekt. Die Connection-Eigenschaft von UpdateCommand wurde nicht initialisiert. - - - Für ein Update benötigt der DeleteCommand ein Verbindungsobjekt. Die Connection-Eigenschaft von DeleteCommand wurde nicht initialisiert. - - - Für ein Update ist ein Verbindungsobjekt erforderlich. Die Connection-Eigenschaft wurde nicht initialisiert. - - - Für ein Update benötigt der Befehlsklon ein Verbindungsobjekt. Die Connection-Eigenschaft des Befehlsklons wurde nicht initialisiert. - - - Für ein Update ist eine Verbindung erforderlich. - - - Für ein Update muss der {0}Command über ein geöffnetes Verbindungsobjekt verfügen. {1} - - - Für ein Update muss der {0}Command über ein geöffnetes Verbindungsobjekt verfügen. {1} - - - Für ein Update muss der {0}Command über ein geöffnetes Verbindungsobjekt verfügen. {1} - - - Für ein Update muss der Updatebefehl über ein geöffnetes Verbindungsobjekt verfügen. {1} - - - Die gespeicherte Prozedur '{0}' ist nicht vorhanden. - - - Die dem Befehl zugewiesene Transaktion muss die geschachtelste lokale Transaktion sein, die ansteht. - - - Die Transaktion ist entweder nicht der aktuellen Verbindung zugeordnet oder wurde abgeschlossen. - - - Die der aktuellen Verbindung zugeordnete Transaktion wurde abgeschlossen, jedoch nicht verworfen. Die Transaktion muss verworfen werden, bevor die Verbindung zum Ausführen von SQL-Anweisungen verwendet werden kann. - - - {0} erfordert, dass der Befehl über eine Transaktion verfügt, wenn die dem Befehl zugewiesene Verbindung eine ausstehende lokale Verbindung ist. Die Transaction-Eigenschaft des Befehls wurde nicht initialisiert. - - - Diesem Befehl ist bereits ein geöffnetes SqlResultSet zugeordnet, das zuerst geschlossen werden muss. - - - Diesem {0}selement ist bereits ein geöffneter DataReader zugeordnet, der zuerst geschlossen werden muss. - - - {0} DeriveParameters unterstützen nur CommandType.StoredProcedure, nicht CommandType.{1}. - - - Die Methode '{0}' kann für dieselbe Ausführung nicht mehr als einmal aufgerufen werden. - - - Falsches asynchrones Ergebnis. - - - Die SelectCommand-Eigenschaft wurde nicht initialisiert vor dem Aufruf von: '{0}'. - - - Der StatementType {0} wird hier nicht erwartet. - - - FillSchema: Es wird eine nicht leere Zeichenfolge als SourceTable-Name erwartet. - - - Der MaxRecords Wert von {0} ist ungültig; der Wert muss >= 0 sein. - - - Der StartRecord-Wert von {0} ist ungültig; der Wert muss >= 0 sein. - - - Fill: Es wird eine nicht leere Zeichenfolge als SourceTable-Name erwartet. - - - Hierarchische Kapitelspalten müssen zu einer AutoIncrement DataColumn zugeordnet werden. - - - DataReader.GetFieldType({0}) gab 'null' zurück. - - - Geben Sie bei der Verwendung von Werten ungleich Null für 'startRecords' oder 'maxRecords' nur ein Element im 'dataTables'-Array an. - - - Update: TableMapping['{0}'] oder DataTable '{0}' kann nicht gefunden werden. - - - Update: Nicht leerer SourceTable-Name erwartet. - - - Fehlende TableMapping wenn TableMapping.DataSetTable='{0}'. - - - Für ein Update ist ein gültiger Befehlsklon erforderlich. - - - Die automatische SQL-Generierung erfordert während des Updates einen gültigen SelectCommand. - - - Für ein Update ist ein gültiger InsertCommand erforderlich, wenn eine DataRow-Sammlung mit neuen Zeilen weitergegeben wird. - - - Für ein Update ist ein gültiger UpdateCommand erforderlich, wenn eine DataRow-Sammlung mit modifizierten Zeilen weitergegeben wird. - - - Für ein Update ist ein gültiger DeleteCommand erforderlich, wenn eine DataRow-Sammlung mit gelöschten Zeilen weitergegeben wird. - - - DataRow[{0}] stammt aus einer anderen DataTable als DataRow[0]. - - - RowUpdatedEvent: Fehler sind aufgetreten, es sind keine zusätzlichen Informationen vorhanden. - - - RowUpdatingEvent: Fehler sind aufgetreten, es sind keine zusätzlichen Informationen vorhanden. - - - Bei der Batchverarbeitung ist der Wert der UpdatedRowSource-Eigenschaft von UpdateRowSource.FirstReturnedRecord oder UpdateRowSource.Both ungültig. - - - Parallelitätsverletzung: Der UpdateCommand hat sich auf {0} der erwarteten {1} Datensätze ausgewirkt. - - - Parallelitätsverletzung: Der DeleteCommand hat sich auf {0} der erwarteten {1} Datensätze ausgewirkt. - - - Parallelitätsverletzung: der Batchbefehl hat sich auf {0} der erwarteten {1} Datensätze ausgewirkt. + + Die Methode '{0}' kann für dieselbe Ausführung nicht mehr als einmal aufgerufen werden. Ungültiger CommandTimeout-Wert {0}; der Wert muss >= 0 sein. - - {1}[{0}]: die Size-Eigenschaft hat den ungültigen Wert 0. - - - Die {0}.Prepare-Methode erfordert, dass für alle Parameter explizit ein Typ festgelegt wird. - - - Die {0}.Prepare-Methode erfordert, dass für alle variablen Längenparameter explizit eine nicht-Null-Size festgelegt wird. - - - Die {0}.Prepare-Methode erfordert, dass für Parameter des Typs '{1}' Precision und Scale explizit festgelegt ist. - - - Nicht übereinstimmender Aufruf zum Beenden der Methode für asyncResult. Erwartet wurde der Aufruf von {0}, stattdessen wurde {1} aufgerufen. - - - Ungültige Operation. Die Verbindung ist geschlossen. - - - Die Verbindung wurde deaktiviert. - - - Eintrag in der Transaktion fehlgeschlagen, da für die Verbindung gerade eine lokale Transaktion durchgeführt wird. Beenden Sie die lokale Transaktion, und wiederholen Sie den Vorgang. - - - Für die Verbindung ist gegenwärtig eine Transaktion eingetragen. Beenden Sie die aktuelle Transaktion, und versuchen Sie es erneut. - - - Datenbank kann nicht Null, eine leere Zeichenfolge oder eine Zeichenfolge aus Leerzeichen sein. - - - Das Argument ist zu lang. - - - Ungültiger 'Connect Timeout'-Wert. Der Wert muss eine ganze Zahl >= 0 sein. - - - Ungültiger Quellpufferoffset (Größe von {0}): {1} - - - Ungültiger Zielpufferoffset (Größe von {0}): {1} - - - Der numerische Wert ist zu groß, um in eine Dezimalzahl mit 96 Bits zu passen. - - - Ungültiger Versuch, zu {0}, da der Stream bereits geschlossen wurde. - - - Der angegebene SeekOrigin-Wert ist ungültig. - - - Dynamische SQL-Generierung wird gegen mehrere Basistabellen nicht unterstützt. - - - Dynamische SQL-Generierung wird für einen SelectCommand, der keine Basistabelleninformationen zurückgibt, nicht unterstützt. - - - Dynamische SQL-Generierung für den DeleteCommand wird nicht für einen SelectCommand unterstützt, der keine Schlüsselspalteninformationen zurückgibt. - - - Dynamische SQL-Generierung für den UpdateCommand wird nicht für einen SelectCommand unterstützt, der keine Schlüsselspalteninformationen zurückgibt. - - - Die dynamische SQL-Generierung für den 'DeleteCommand' wird für einen 'SelectCommand', der keine Zeilenversionsspalte enthält, nicht unterstützt. - - - Die dynamische SQL-Generierung für den 'UpdateCommand' wird für einen 'SelectCommand', der keine Zeilenversionsspalte enthält, nicht unterstützt. - - - Die dynamische SQL-Generierung wird für Tabellennamen '{0}', die das QuotePrefix- oder QuoteSuffix-Zeichen '{1}' enthalten, nicht unterstützt. - - - Ungültiger Versuch, aus der Spaltenordinalzahl '{0}' zu lesen. Mit CommandBehavior.SequentialAccess kann nur aus der Spaltenordinalzahl '{1}' oder größer gelesen werden. - - - Der Datentyp '{0}' kann nicht als Literal formatiert werden, da er ungültige Stellen für Datum/Uhrzeit aufweist. - - - Der Wert kann nicht als Literal des angeforderten Typs formatiert werden. - - - Der Datentyp '{0}' kann nicht als Literal formatiert werden, da er eine ungültige maximale Dezimalstellenanzahl aufweist. - - - Der angegebene Literalwert ist kein gültiges Literal für den Datentyp '{0}'. - - - '{0}':Die Länge des Literalwerts muss gerade sein. - - - '{0}':Der Literalwert muss eine Zeichenfolge mit hexadezimalen Stellen sein - - - {0} erfordert eine geöffnete Verbindung, wenn das Anführungspräfix nicht festgelegt wurde. - - - Die korrekten booleschen Literalwerte können nicht bestimmt werden. Boolean-Literale können nicht erstellt werden. - - - Literale des nativen Datentyps, die mit dem Datentyp '{0}' verknüpft sind, werden nicht unterstützt. - - - Der Parameterdatentyp von {0} ist ungültig. - - - Es ist keine Zuordnung zwischen dem Objekttyp {0} und einem bekannten verwalteten nativen Providertyp vorhanden. - - - Der unbekannte TypeCode {0}, der vom Type {1} zurückgegeben wurde, kann nicht behandelt werden. - - - Es ist keine Zuordnung von DbType {0} zu einem bekannten {1} vorhanden. - - - Die verwendete SQL Server-Version unterstützt nicht den Datentyp '{0}'. - - - Der Parameterwert '{0}' befindet sich außerhalb des gültigen Bereichs. - - - Der angegebene Parametername '{0}' ist ungültig. - - - Mehrere Rückgabewertparameter werden nicht unterstützt. - - - Ungültiger Wert für die Parametergröße '{0}'. Der Wert muss größer oder gleich 0 sein. - - - Ungültiger Wert für das Argument '{0}'. Der Wert muss größer oder gleich 0 sein. - - - Ungültiger Wert für diese Metadaten. - - - Metadaten müssen vom Typ SqlDbType.Row sein - - - Parameterwert konnte nicht von {0} in {1} umgewandelt werden. - - - {0} unterstützt keine parallelen Transaktionen. - - - {0} wurde beendet und kann nicht mehr verwendet werden. - - - '{0}' kann nicht aufgerufen werden, wenn der Datensatz schreibgeschützt ist. - - - '{0}' kann nicht aufgerufen werden, wenn DbDataRecord schreibgeschützt ist. - - - Die implizite Umwandlung des Objekttyps '{0}' in den Datentyp '{1}' wird nicht unterstützt. - - - Pufferoffset '{1}' plus verfügbare Bytes '{0}' ist größer als die Länge des übergebenen Puffers. - - - Datenlänge '{0}' ist kleiner als 0. - - - Die angegebene Länge '{0}' ist außerhalb des Gültigkeitsbereichs. - - - Ungültiger Versuch dataIndex '{0}' zu {2}. Mit CommandBehavior.SequentialAccess kann nur dataIndex '{1}' oder größer gelesen werden. - - - Offset muss sich auf eine Stelle im Wert beziehen. - - - "Der von ODBC verwaltete Provider erfordert es, dass die TABLE_NAME-Einschränkung angegeben wird und für die 'GetSchema'-Indexsammlung ungleich Null ist. - - - Ungültiger Argumentwert für Methode '{0}'. - - - Der ODBC-Provider hat keine Ergebnisse für SQLGETTYPEINFO zurückgegeben. - - - Unerwartetes null DataTable-Argument - - - Unerwartetes null DataSet-Argument. - - - Die Verbindungszeichenfolge überschreitet die zulässige Länge von {0}. - - - {0} - Typzuordnung nicht möglich. - - - Unbekannter SQL-Typ - {0}. - - - Unbekannter URT-Typ - {0}. - - - Ungültiges negatives Argument. - - - Eigenschaft kann für eine offene Verbindung nicht festgelegt werden. - - - Keine gültige Zuordnung einer SQL_TRANSACTION '{0}' zu einem System.Data.IsolationLevel-Enumerationswert. - - - {0} - Verbindungspooling kann nicht aktiviert werden... - - - {0} - Umgebungshandle konnte nicht zugewiesen werden. - - - {0} - das Deskriptorhandle kann nicht abgerufen werden. - - - Nicht in einer Transaktion - - - Ungültiger OdbcType-Enumerationswert, value={0}. - - - IsDBNull verwenden, wenn DBNull.Value-Daten erwartet werden. - - - {0} [{1}] {2} - - - {0} - Keine Fehlerinformationen verfügbar - - - Die Verbindung wurde geschlossen. - - - Eine interne Verbindung hat keinen Besitzer. - - - Der .NET Framework-ODBC-Datenanbieter benötigt Microsoft Data Access Components (MDAC) 2.6 oder höher. Aktuell ist Version {0} installiert. - - - Der .NET Framework-OLEDB-Datenanbieter benötigt Microsoft Data Access Components (MDAC) 2.6 oder höher. Aktuell ist Version {0} installiert. - - - Die Schnittstelle '{0}' wird nicht vom '{1}'-Provider unterstützt. GetOleDbSchemaTable ist mit dem aktuellen Provider nicht verfügbar. - - - '{0}' ist ohne Fehlermeldung fehlgeschlagen, Ergebniscode: {1}. - - - Keine Fehlermeldung verfügbar, Fehlercode: {0}. - - - Der .NET Framework-Datenanbieter benötigt Microsoft Data Access Components (MDAC). Installieren Sie MDAC 2.6 oder höher. - - - Der .NET Framework-Datenanbieter für OLEDB (Microsoft.Data.OleDb) bietet keine Unterstützung für den Microsoft OLE DB-Anbieter für ODBC-Treiber (MSDASQL). Verwenden Sie den .NET Framework-Datenanbieter für ODBC (System.Data.Odbc). - - - Der .NET Framework-Datenanbieter für OLEDB lässt eine Benutzeraufforderung durch den OLE DB-Anbieter in einer nicht interaktiven Umgebung nicht zu. - - - Der '{0}'-Provider ist nicht auf dem lokalen Computer registriert. - - - Die ICommandText-Schnittstellen wird vom '{0}'-Provider nicht unterstützt. Verwenden Sie stattdessen CommandType.TableDirect. - - - Die ITransactionLocal-Schnittstelle wird nicht vom '{0}'-Provider unterstützt. Lokale Transaktionen sind mit dem aktuellen Provider nicht verfügbar. - - - Das Format der Initialisierungszeichenfolge entspricht nicht der OLE DB-Spezifikation. Beginn bei char[{0}] in der Verbindungszeichenfolge. - - - Die asynchrone Verarbeitung ist kein unterstütztes Feature des .NET Framework-OLE DB-Datenanbieters (Microsoft.Data.OleDb). - - - In ConnectionString wurde kein OLE DB Provider angegeben. Anwendungsbeispiel: 'Provider=SQLOLEDB;'. - - - Der OLE DB-Provider, der in ConnectionString angegeben wurde, ist zu lang. - - - Für DbInfoKeywords OleDbSchemaGuid werden keine Einschränkungen erwartet. - - - Für DbInfoLiterals OleDbSchemaGuid werden keine Einschränkungen erwartet. - - - Für die Schema-guid OleDbSchemaGuid werden keine Einschränkungen erwartet. - - - Das Schema {0} OleDbSchemaGuid wird vom '{1}'-Provider nicht unterstützt. - - - Die Konfigurationseinstellung '{0}' weist eine falsche Anzahl von Werten auf. - - - Die in der Konfigurationseinstellung '{0}' angegebene XML-Datei konnte nicht geladen werden. - - - Befehlsparameter [{0}] '{1}' ist ungültig. - - - Der Datenwert des Befehlsparameters [{0}] '{1}' konnte aus anderen Gründen als einem Signaturübereinstimmungsfehler oder Datenüberlauf nicht konvertiert werden. - - - Die Konvertierung des Befehlsparameters [{0}] '{1}' ist fehlgeschlagen, da der Datenwert signiert war, aber der vom Anbieter verwendete Typ nicht. - - - Die Konvertierung des Befehlsparameters [{0}] '{1}' ist fehlgeschlagen, da der Datenwert den vom Provider verwendeten Typ überlaufen hat. - - - Der Anbieter ist beim Senden des Werts des Befehlsparameters [{0}] '{1}' auf einen Fehler gestoßen und hat die Verarbeitung angehalten. - - - Der Parameter [{0}] '{1}' hat keinen Standardwert. - - - Fehler beim Befehlsparameter [{0}]: {1}. - - - Interner Microsoft.Data.OleDb.OleDbDataAdapter-Fehler: ungültiger Parameteraccessor: {0} {1}. - - - Parameter[{0}]: die Eigenschaft OleDbType ist nicht initialisiert: OleDbType.{1}. - - - Die ICommandWithParameters-Schnittstelle wird vom '{0}'-Provider nicht unterstützt. Befehlsparameter werden vom aktuellen Provider nicht unterstützt. - - - Das Abfragen der Prozedurparameterinformationen wird vom '{0}'-Provider nicht unterstützt. - - - Das Dezimaltrennzeichen des Servers kann nicht bestimmt werden. Nicht ganzzahlige numerische Literale können nicht erstellt werden. - - - Das Objekt ist kein ADODB.RecordSet oder ADODB.Record. - - - Die Schnittstelle '{0}' kann vom ADODB.RecordSet-Objekt nicht abgerufen werden. + + {1}[{0}]: die Size-Eigenschaft hat den ungültigen Wert 0. - - Die IRow-Schnittstelle kann nicht vom ADODB.Record-Objekt abgefragt werden. + + Die {0}.Prepare-Methode erfordert, dass für alle Parameter explizit ein Typ festgelegt wird. - - Der Typ unterstützt nicht die OLE DB-Schnittstelle ISourcesRowset + + Die {0}.Prepare-Methode erfordert, dass für alle variablen Längenparameter explizit eine nicht-Null-Size festgelegt wird. - - Die ReservedWords-Schema-Sammlung kann nicht erstellt werden, da der Provider IDBInfo nicht unterstützt. + + Die {0}.Prepare-Methode erfordert, dass für Parameter des Typs '{1}' Precision und Scale explizit festgelegt ist. - - Der Eigenschaftenwert wurde aus folgenden Gründen nicht festgelegt: der Anbieter unterstützt die Eigenschaft '{0}' nicht, oder der Consumer hat versucht, Werte von Eigenschaften, die nicht in der Eigenschaftengruppe 'Initialization' vorhanden sind, abzurufen oder festzulegen, ohne dass das Datenquellenobjekt initialisiert war. + + Nicht übereinstimmender Aufruf zum Beenden der Methode für asyncResult. Erwartet wurde der Aufruf von {0}, stattdessen wurde {1} aufgerufen. - - Die Eigenschaft '{0}' konnte aus einem der folgenden Gründe nicht initialisiert werden: - Der Wertdatentyp entsprach nicht dem Datentyp der Eigenschaft oder war ungleich Null. Beispiel: Die Eigenschaft war DBPROP_MEMORYUSAGE mit dem Datentyp Int32, der Datentyp dagegen war Int64. - Der Wert war nicht gültig. Die Eigenschaft war DBPROP_MEMORYUSAGE, der Wert negativ. - Der Wert war gültig für die Eigenschaft, und der Provider unterstützt die Eigenschaft als festlegbare Eigenschaft, ohne jedoch den angegebenen Wert zu unterstützen. Dies gilt auch dann, wenn der Wert der Eigenschaft in OLE DB hinzugefügt wurde, nachdem der Provider geschrieben wurde. + + Ungültige Operation. Die Verbindung ist geschlossen. - - Der Wert 'Options' war ungültig. + + Eintrag in der Transaktion fehlgeschlagen, da für die Verbindung gerade eine lokale Transaktion durchgeführt wird. Beenden Sie die lokale Transaktion, und wiederholen Sie den Vorgang. - - Das ColumnID-Element war ungültig. + + Für die Verbindung ist gegenwärtig eine Transaktion eingetragen. Beenden Sie die aktuelle Transaktion, und versuchen Sie es erneut. - - Die angegebene Eigenschaft '{0}' sollte auf alle Spalten angewendet werden, konnte jedoch auf eine oder mehrere Spalten nicht angewendet werden. + + Datenbank kann nicht Null, eine leere Zeichenfolge oder eine Zeichenfolge aus Leerzeichen sein. - - Die Eigenschaft '{0}' war schreibgeschützt, oder der Consumer hat versucht, Werte von Eigenschaften in der Eigenschaftengruppe 'Initialization' festzulegen, nachdem das Datenquellenobjekt initialisiert wurde. Consumer können den Wert einer schreibgeschützten Eigenschaft auf deren aktuellen Wert festlegen. Dieser Status wird auch zurückgegeben, wenn eine festlegbare Spalteneigenschaft für die bestimmte Spalte nicht festgelegt werden konnte. + + Ungültiger Quellpufferoffset (Größe von {0}): {1} - - Der Wert der optionalen Eigenschaft '{0}' wurde nicht auf den angegebenen Wert festgelegt. Das Festlegen der Eigenschaft auf den angegebenen Wert war nicht möglich. + + Ungültiger Zielpufferoffset (Größe von {0}): {1} - - Der Wert der Eigenschaft '{0}' wurde nicht festgelegt, da dies zu einem Konflikt mit einer vorhandenen Eigenschaft geführt hätte. + + Ungültiger Versuch, zu {0}, da der Stream bereits geschlossen wurde. - - (Reserviert) + + Der angegebene SeekOrigin-Wert ist ungültig. - - Der Provider hat den unbekannten DBPROPSTATUS_-Wert '{0}' zurückgegeben. + + Ungültiger Versuch, aus der Spaltenordinalzahl '{0}' zu lesen. Mit CommandBehavior.SequentialAccess kann nur aus der Spaltenordinalzahl '{1}' oder größer gelesen werden. - - Accessor-Validierung wurde verzögert und ausgeführt, während die Methode Daten zurückgegeben hat. Ungültige Bindung für diese Spalte oder diesen Parameter. + + Der Parameterdatentyp von {0} ist ungültig. - - Interner OleDbDataAdapter-Fehler: ungültiger RowSet-Accessor: Ordinal={0} Status={1}. + + Es ist keine Zuordnung zwischen dem Objekttyp {0} und einem bekannten verwalteten nativen Providertyp vorhanden. - - Der Datenwert konnte aus anderen Gründen als Signaturübereinstimmungsfehler oder Datenüberlauf nicht konvertiert werden. Möglicherweise wurden die Daten im Datenspeicher beschädigt, aber die Zeile war weiterhin abrufbar. + + Der unbekannte TypeCode {0}, der vom Type {1} zurückgegeben wurde, kann nicht behandelt werden. - - Anbieter konnte keinen Arbeitsspeicher für die Rückgabe von {0}-Daten belegen. + + Es ist keine Zuordnung von DbType {0} zu einem bekannten {1} vorhanden. - - Die Konvertierung ist fehlgeschlagen, da der Datenwert {0} den für den Wertteil {0} im Puffer des Benutzers festgelegten Typ überlaufen hat. + + Die verwendete SQL Server-Version unterstützt nicht den Datentyp '{0}'. - - Interner OleDbDataAdapter-Fehler: [get] unbekannter OLE DB-Datentyp: 0x{0} ({1}). + + Der Parameterwert '{0}' befindet sich außerhalb des gültigen Bereichs. - - Konvertierung ist fehlgeschlagen, da der {0}-Datenwert signiert wurde und der für den {0}-Wert angegebene Typ nicht signiert wurde. + + Der angegebene Parametername '{0}' ist ungültig. - - Interner OleDbDataAdapter-Fehler: [set] unbekannter OLE DB-Datentyp: 0x{0} ({1}). + + Ungültiger Wert für die Parametergröße '{0}'. Der Wert muss größer oder gleich 0 sein. - - Anbieter konnte den {0}-Wert nicht ermitteln. Zum Beispiel: die Zeile wurde gerade erstellt, der Standard für die {0}-Spalte nicht verfügbar und der Consumer hat noch keinen neuen {0}-Wert gesetzt. + + Ungültiger Wert für das Argument '{0}'. Der Wert muss größer oder gleich 0 sein. - - OLE DB-Anbieter hat unerwarteten Statuswert von {0} zurückgegeben. + + Ungültiger Wert für diese Metadaten. - - OleDbDataReader.Read muss im gleichen Thread verwendet werden, in dem sie erstellt wurde, wenn der ApartmentState des Threads nicht ApartmentState.MTA war. + + Parameterwert konnte nicht von {0} in {1} umgewandelt werden. - - Nicht angegebener Fehler: {0} + + {0} unterstützt keine parallelen Transaktionen. - - Fehler {0} in IErrorInfo.GetDescription. + + {0} wurde beendet und kann nicht mehr verwendet werden. - - Fehler {0} in IErrorInfo.GetSource. + + Die angegebene Länge '{0}' ist außerhalb des Gültigkeitsbereichs. - - DBTYPE_VECTOR-Daten werden vom .NET Framework-OLE DB-Datenanbieter (Microsoft.Data.OleDb) nicht unterstützt. + + Ungültiger Versuch dataIndex '{0}' zu {2}. Mit CommandBehavior.SequentialAccess kann nur dataIndex '{1}' oder größer gelesen werden. Ungültiger Wert für die minimale oder maximale Poolgröße. Die minimale Poolgröße kann nicht größer als die maximale Poolgröße sein. - - Das Schlüsselwort '{0}' ist veraltet. Verwenden Sie stattdessen '{1}'. - Die Adresse des verteilten Transaktionskoordinators für den Server konnte nicht vom Server abgerufen werden. Wurde DTC auf dem Server aktiviert? - - Die Länge des Werts für den Verbindungsparameter <{0}> überschreitet die maximale Länge von 65535 Zeichen. - Ungültiger Packet Size-Wert. Der Wert muss eine ganze Zahl >= 512 und <= 32768 sein. Ungültige Transaktion oder ungültiger Name eines Speicherungspunkts der Transaktion. - - Der Enumerationswert von {0}, {1}, wird nicht von SQL Server 7.0 oder SQL Server 2000 unterstützt. - Die Optionen 'User Instance' und 'Failover' sind nicht kompatibel. Wählen Sie nur eine der beiden Optionen in der Verbindungszeichenfolge. @@ -2499,33 +540,21 @@ Die Eigenschaft "Credential " kann nicht festgelegt werden, wenn "Authentication=Active Directory Integrated" in der Verbindungszeichenfolge angegeben wurde. - - Bei der Zertifikatauthentifizierung wurden falsche Authentifizierungsparameter angegeben. - Die Instanz von SQL Server, mit der Sie sich verbinden möchten, erfordert eine Verschlüsselung, die von diesem Computer jedoch nicht unterstützt wird. Die Instanz von SQL Server, mit der Sie sich verbinden möchten, unterstützt keine Verschlüsselung. - - Die Instanz von SQL Server, mit der Sie eine Verbindung herstellen möchten, unterstützt CTAIP nicht. - Nicht unterstützte SQL Server-Version. Der .NET Framework-SqlClient-Datenanbieter kann nur mit SQL Server-Version 7.0 und höher verwendet werden. {0} kann nicht geändert werden, während eine asynchrone Operation ausgeführt wird. - - 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. - Instanzausfall. @@ -2556,27 +585,18 @@ Fehlercodecode 0x{0} - - ChangePassword erfordert SQL Server 9.0 oder höher. - Die Umleitung der SSE-Instanz wird bei nicht lokalen Benutzerinstanzen nicht unterstützt. Die Authentifizierung "{0}" wird nicht unterstützt. - - Die SQL-Authentifizierungsmethode "{0}" wird nicht unterstützt. - Fehler beim Instanziieren eines Authentifizierungsanbieters des Typs "{1}" für "{0}". Fehler beim Instanziieren eines SqlAuthenticationInitializer des Typs "{0}". - - Der Anbieter "{0}" hat bei der Initialisierung eine Ausnahme ausgelöst. - Die Authentifizierung "{1}" wird vom Anbieter "{0}" nicht unterstützt. @@ -2586,24 +606,15 @@ Für „{0}“ wurde kein Authentifizierungsanbieter gefunden. Installieren Sie das NuGet-Paket „Microsoft.Data.SqlClient.Extensions.Azure“ (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), um Active Directory (Entra ID)-Authentifizierungsmethoden zu verwenden. - - Fehler beim Lesen des Konfigurationsabschnitts für Authentifizierungsanbieter. - Der Parameter "{0}" darf nicht NULL oder leer sein. - - Die asynchrone Operation wurde bereits abgeschlossen. - Die Befehlsausführung kann aufgrund einer ausstehenden bereits laufenden asynchronen Operation nicht fortgesetzt werden. Der an ExecuteXmlReader gesendete Befehl ist ungültig. Der Befehl muss ein Xml-Ergebnis zurückgeben. - - Für Benachrichtigungen ist SQL Server 9.0 oder höher erforderlich. - Ungültiges dreiteiliges Namensformat für UdtTypeName. @@ -2697,9 +708,6 @@ Das SSPI-Paket kann nicht initialisiert werden. - - Timeout abgelaufen. Das Zeitlimit wurde vor dem Beenden des Vorgangs überschritten oder der Server reagiert nicht. - Verbindungstimeout abgelaufen. Das Timeout ist zu Beginn der Phase vor der Anmeldung aufgetreten. Möglicherweise wurde ein zu kurzes Verbindungstimeout konfiguriert. @@ -2745,12 +753,6 @@ In der Verbindungszeichenfolge wurde eine Benutzerinstanz angefordert, doch der angegebene Server unterstützt diese Option nicht. - - Fehler bei der Anwendungsüberprüfung für das Serverzertifikat. - - - Die angegebene Datenlänge {0} überschreitet die zulässige Höchstlänge {1}. - Ungültiger Leseversuch, wenn keine Daten vorhanden sind. @@ -2775,9 +777,6 @@ Datenlänge '{0}' ist kleiner als 0. - - '{0}' kann nicht aufgerufen werden, wenn SqlDataRecord schreibgeschützt ist. - Die zugeordnete Sammlung wird verwendet, weshalb zur Zeit kein Zugriff auf diese möglich ist. @@ -2847,21 +846,6 @@ Das Herstellen einer Verbindung mit der Kontextverbindung über Microsoft.Data.SqlClient wird nicht unterstützt. - - Geschachtelte 'TransactionScopes' werden nicht unterstützt. - - - Unerwartetes Serverereignis: {0}. - - - 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}. - - - Zu viele Werte. - Vom Stream wird Schreiben nicht unterstützt. @@ -2886,192 +870,36 @@ NULL - - Nachricht - - - Arithmetischer Überlauf. - - - Fehler aufgrund einer Division durch 0. - - - Der Wert eines Null-SQL-Datensatzes kann nicht zurückgegeben werden. - - - Die numerische Arithmetik bewirkt ein Abschneiden. - SqlDateTime-Überlauf; muss zwischen 1/1/1753 12:00:00 AM und 12/31/9999 11:59:59 PM liegen. - - Zwei zu verkettende Zeichenfolgen haben unterschiedliche Sortierungen. - - - Zwei zu vergleichende Zeichenfolgen haben unterschiedliche Sortierungen. - - - Ungültiger Flagwert. - - - Überläufe bei Konvertierung von SqlDecimal in Decimal. - Konversionsüberläufe. - - Ungültige 'SqlDateTime'. - - - Es wurde eine Zeitzone angegeben. 'SqlDateTime' unterstützt keine Zeitzonen. - Ungültige Arraygröße. Ungültige numerische Genauigkeit/Skala. - - Die Eingabe hatte nicht das richtige Format. - - - SqlType-Fehler. - - - SqlBytes und SqlChars unterstützten in dieser Version nur eine maximale Länge von 2GB. - Fehler beim Lesen. - - Stream wurde geschlossen oder entfernt. - - - Kein Puffer vorhanden. Lese- bzw. Schreiboperation fehlgeschlagen. - - - Eine Länge ungleich Null kann nicht festgelegt werden, da der aktuelle Wert Null ist. - - - Der Puffer ist unzureichend. Lese- bzw. Schreiboperation fehlgeschlagen. - - - Schreiben in einen Offset ungleich Null nicht möglich, da der aktuelle Wert Null ist. - - - Schreiben aus einem Offset, der größer als die aktuelle Länge ist, nicht möglich. Es würden nicht initialisierte Daten im Puffer verbleiben. - Die Größe der zurückgegebenen Daten beträgt mehr als 2Gb. Verwenden Sie das SequentialAccess-command behavior, um alle Daten abzurufen. - - Argument für GetDayOfWeek muss eine Ganzzahl von 1 bis 7 sein. - - - SQL Type wurde nicht mit Daten geladen. - - - SQL Type wurde bereits mit Daten geladen. - - - Ungültiger Versuch des Zugriffs auf einen geschlossenen XmlReader. - - - Ungültiger Versuch des Aufrufs von {0}, wenn der Datenstrom geschlossen ist. - - - Ungültiger Versuch des Aufrufs von {0}, wenn nicht in den Datenstrom geschrieben werden kann. - - - Ungültiger Versuch des Aufrufs von {0}, wenn der Datenstrom nicht lesbar ist. - - - Ungültiger Versuch des Aufrufs von {0}, wenn der Datenstrom nicht durchsuchbar ist. - - - Die Unterklasse hat keine erforderliche Methode überschrieben. - Die Normalisierungsfunktion für '{0}' kann nicht erstellt werden. Interner Fehler - - Der Befehlstext von Befehlsparametern darf weder Null noch leer sein. - - - Die Länge des MetaData-Parameterarrays muss dem ParameterDirection-Arrayargument entsprechen. - - - Datenadapter-Zuordnungsfehler. - - - Datenadapterfehler. - - - DB-Parallelitätsverletzung. - Operation abgebrochen. Operation aufgrund einer Ausnahme abgebrochen (Details finden Sie in der InnerException). - - Gibt an, ob Fill DataRow.AcceptChanges aufruft. - - - Gibt an, ob DataRow.AcceptChanges von Update aufgerufen wird. - - - Gibt an, ob mit der nächsten DataRow fortgesetzt wird, wenn der Status der Updateereignisse RowUpdating and RowUpdated 'UpdateStatus.ErrorsOccurred' ist. - - - Bestimmt, wie der Adapter die DataTable aus dem DataReader auffüllt. - - - Die Aktion, die unternommen wird, wenn eine Tabelle oder Spalte in TableMappings fehlt. - - - Die Aktion, die unternommen wird, wenn eine Tabelle oder Spalte im DataSet fehlt. - - - Zeigt, wie eine Quelltabelle einer DataSet-Tabelle zugeordnet wird. - - - Ereignis, das ausgelöst wird, wenn während Fill ein wiederherstellbarer Fehler auftritt. - - - Gibt an, ob Fill providerspezifische Werte oder allgemeine CLSCompliant-Werte zurückgeben soll. - - - DataColumn.ColumnName - - - Quellspaltenname - Groß-/Kleinschreibung wird beachtet. - - - Anzahl der Elemente in der Sammlung - - - Angegebenes DataColumnMapping-Objekt. - - - Individuelle Spaltenzuordnungen, wenn diese Tabellenzuordnung übereinstimmt. - - - DataTable.TableName - - - Der DataTableMapping-Quelltabellenname. Für diesen Namen muss die Groß-/Kleinschreibung beachtet werden. - - - Anzahl der Elemente in der Sammlung - - - Das angegebene DataTableMapping-Objekt. - Wird verwendet bei Update für gelöschte Zeilen im DataSet. @@ -3090,63 +918,9 @@ Das Ereignis, das während des Updates nach jeder DataRow ausgelöst wird. - - Die Anzahl der Zeilen, die in einem Batch zusammengefasst werden, vor der Anwendung des Befehls auf die Datenquelle. - - - Verwendete Verbindung, wenn die 'Select/Insert/Update/DeleteCommands' nicht bereits über eine Verbindung verfügen. - - - Wird während Update für gelöschte Zeilen in der DataTable verwendet. - - - Wird während Update für neue Zeilen in der DataTable verwendet. - - - Wird während Fill verwendet. - - - Wird während Update für geänderte Zeilen in der DataTable verwendet. - - - Gibt an, ob Fill providerspezifische Werte oder allgemeine CLSCompliant-Werte zurückgeben soll. - - - Zuordnen der Quelltabelle zur DataTable. - - - Legt fest, wie die Befehle Insert/Update/DeleteCommands generiert werden, wenn diese nicht vom Benutzer festgelegt werden. - - - Die Anzahl der Zeilen, die in einem Batch zusammengefasst werden, vor der Anwendung des Befehls auf die Datenquelle. - - - Die Verbindungszeichenfolge zum Herstellen einer Verbindung mit der Datenquelle. - - - Der Name des zum Herstellen einer Verbindung mit der Data Source zu verwendenden ODBC-Treibers. - - - Der zum Herstellen einer Verbindung mit der Data Source zu verwendende DSN-Name. - - - Gibt, falls wahr, an, dass das verwaltete Verbindungspooling verwendet werden soll. - - - Die bei Herstellung einer Verbindung mit der Data Source zu verwendende UDL-Datei. - - - Gibt an, welche OLE DB Services mit dem OleDb Provider aktiviert oder deaktiviert werden sollen. - - - Der Name des zum Herstellen einer Verbindung mit der Data Source zu verwendenden OLE DB-Providers. - Der Name der Anwendung. - - Bei Festlegung auf TRUE wird die Verwendung der asynchronen Funktionalität im .NET Framework-Datenanbieter aktiviert. - Der Name der primären Datei, einschließlich dem vollständigen Pfadnamen, einer anfügbaren Datenbank. @@ -3156,9 +930,6 @@ Gibt, falls wahr, an, dass der Status der Verbindung bei Entfernen aus dem Pool zurückgesetzt wird. - - Falls wahr, muss die Verbindung aus dem Sql Server-Kontext hergestellt werden. Ist nur bei Ausführung im Sql Server-Prozess verfügbar. - Der Datensatzname der SQL Server-Sprache. @@ -3252,54 +1023,9 @@ Legt die Methode der Authentifzierung mit SQL Server fest. - - Das angegebene Clientzertifikat für die Authentifizierung bei SQL Server. - - - Informationen, die zum verbinden mit einer Datenquelle verwendet werden. - - - Der aktuelle Zeitüberschreitungswert der Verbindung kann nicht in ConnectionString festgelegt werden. - - - Aktueller Datenquellenkatalog-Wert, 'Database=X' in der Verbindungszeichenfolge. - - - Aktuelle Datenquelle 'Server=X' in der Verbindungszeichenfolge. - - - Aktueller ODBC-Treiber. - - - Produktversion, auf die vom ODBC-Treiber zugegriffen wird. - - - Informationen, die zum verbinden mit einer Datenquelle verwendet werden. - - - Aktueller Verbindungstimeout-Wert, 'Connect Timeout=X' in ConnectionString. - - - Aktueller Datenquellenkatalog-Wert, 'Initial Catalog=X' in der Verbindungszeichenfolge. - - - Aktuelle Datenquelle, 'Data Source=X' in der Verbindungszeichenfolge. - - - Aktuelle OLE DB-Provider-ProgID, 'Provider=X' in der Verbindungszeichenfolge. - - - Produktversion, auf die vom OLE DB-Provider zugegriffen wird. - Zugriffs-Token zur Verwendung bei der Authentifizierung. - - Status der Verbindung: synchron oder asynchron. 'Asynchronous Processing=x' in der Verbindungszeichenfolge. - - - Informationen, die verwendet werden, um eine Verbindung für die Replikation herzustellen. - Informationen zum Verbinden mit einer DataSource; z.B. 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'. @@ -3324,9 +1050,6 @@ Erfassen von Statistiken für diese Verbindung. - - Verschlüsselungs-Schlüsselspeicheranbieter für benutzerdefinierte Spalte. - Eine GUID, die die physische Verbindung darstellt. @@ -3363,99 +1086,9 @@ Automatische Eintragung in von Microsoft SQL Server verwendete Benachrichtigungen. - - Automatisches Generieren der WHERE-Klausel für die Befehle 'Update' und 'Delete', wenn diese vom Benutzer nicht angegeben wurde. - - - Gibt die Position des Katalognamens in einem qualifizierten Tabellennamen in einem Textbefehl an. - - - Das Zeichen, das den Katalognamen vom Rest des Bezeichners in einem Textbefehl trennt. - - - Das Zeichen, das den Schemanamen vom Rest des Bezeichners in einem Textbefehl trennt. - - - Die Präfixzeichenfolge um sql-Objekte. - - - Die Suffixzeichenfolge um sql-Objekte. - - - Der DataAdapter, für den Befehle automatisch generiert werden. - - - Verwenden Sie das Schema der DataTable oder den SelectCommand. - - - Automatisches Generieren der SET-Klausel für den Update-Befehl, falls diese nicht vom Benutzer angegeben wurde. - - - Der DataAdapter für den OdbcCommands automatisch generiert werden - - - Das Zeichen, das in einem Textbefehl als öffnendes Anführungszeichen für Bezeichner verwendet wird, die spezielle Zeichen verwenden. - - - Das Zeichen, das in einem Textbefehl als schließendes Anführungszeichen für Bezeichner verwendet wird, die spezielle Zeichen enthalten. - - - DataAdapter für den automatisch OleDbCommands generiert werden. - - - Das in numerischen Literalen verwendete Dezimaltrennzeichen. - - - Die Präfixzeichenfolge um sql-Objekte - - - Die Suffixzeichenfolge um sql-Objekte - Der DataAdapter, für den automatisch SqlCommands generiert werden - - Das in numerischen Literalen verwendete Dezimaltrennzeichen. - - - Das Zeichen, das in einem Textbefehl als öffnendes Anführungszeichen für Bezeichner verwendet wird, die spezielle Zeichen verwenden. - - - Das Zeichen, das in einem Textbefehl als schließendes Anführungszeichen für Bezeichner verwendet wird, die spezielle Zeichen enthalten. - - - Nur erforderlich zum Festlegen für dezimale und numerische Parameter im Rahmen einer Verwendung bei den Szenarien 'Prepare', 'FillSchema' und 'CommandBuilder'. - - - Nur erforderlich zum Festlegen für dezimale und numerische Parameter im Rahmen einer Verwendung bei den Szenarien 'Prepare', 'FillSchema' und 'CommandBuilder'. - - - Der native Parametertyp. - - - Der native Parametertyp. - - - Name des Parameters, z.B. '@p1' - - - Der native Parametertyp. - - - Der Servername für den Typ. - - - Offset in Datentypen variabler Länge. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - Die angegebene ParameterDirection '{0}' für Parameter '{1}' wird nicht unterstützt. Table-valued parameters unterstützen nur ParameterDirection.Input. @@ -3468,9 +1101,6 @@ Der angegebene TypeName für Parameter '{0}'. TypeName darf nur für Structured-Parameter festgelegt werden. - - Der Typ SqlDbType.Structured wird nur für Typen mit mehreren Werten unterstützt. - Die 'DateType'-Spalte für das Feld '{0}' in der Schematabelle ist NULL. 'DataType' darf nicht 'null' sein. @@ -3483,18 +1113,9 @@ Die Anzahl der Felder im Datensatz '{0}' stimmt nicht mit der Anzahl in dem ursprünglichen Datensatz überein. - - Bereich: 0-8000 - Im SqlClient-Datenprovider ist ein unerwarteter Fehler aufgetreten. {0} - - Type Info für {0},{1} kann nicht abgerufen werden - - - Der Provider hat folgende Assembly nicht geladen: {0} - Die UdtTypeName-Eigenschaft muss für UDT-Parameter festgelegt werden. @@ -3504,72 +1125,9 @@ Der angegebene Typ ist nicht auf dem Zielserver {0} registriert. - - UDT-Parameter sind in der WHERE-Klausel nur dann zulässig, wenn sie Teil des Primärschlüssels sind. - - - '{0}' ist ein ungültiger benutzerdefinierter Typ. Grund: {1}. - - - unterstützt sowohl speicherresidente als auch benutzerdefinierte Formate - - - Das native Format kann nicht unterstützt werden. - - - implementiert nicht IBinarySerialize - - - nicht serialisierbar - - - keine öffentlichen Constructors - - - implementiert nicht INullable - - - hat keinen öffentlichen Constructor - kein UDT-Attribut - - Serialisierung ohne Zuordnung wird noch nicht unterstützt. - - - 'public static x Parse(System.Data.SqlTypes.SqlString)'-Methode fehlt - - - 'public override string ToString()'-Methode fehlt - - - 'public static x Null { get; }'-Methode fehlt - - - Das Format 'Native' unterstützt keine Felder (direkt oder über ein anderes Feld) vom Typ '{0}' - - - Typ ist nicht öffentlich - - - Native UDT nicht sequenzielles Layout aufgrund von Typ '{0}' - - - Native UDT gibt eine maximale byte-Größe an - - - Feld '{0}' ist als nicht serialisiert markiert - - - Der Typ des Felds „{0}“ ist als explizites Layout markiert, was im nativen Format nicht zulässig ist. - - - Mehrere mit Werten versehene Assembly-Verweise müssen über eine Assembly-ID ungleich Null verfügen. - - - Structured-Typen mit mehreren Werten können nur für Parameter verwendet und nicht in einem anderen Typ verschachtelt werden. - Der Pfadname ist ungültig. @@ -3585,9 +1143,6 @@ Fehler beim Versuch, die Transaktion heraufzustufen. - - Abhängigkeitsobjekt, das zum Empfangen von Abfragebenachrichtigungen verwendet wird. - Eigenschaft, die angibt, ob diese Abhängigkeit ungültig ist. @@ -3600,9 +1155,6 @@ Zum Hinzufügen eines Befehls zu einem vorhandenen Abhängigkeitsobjekt. - - Command ist bereits mit einem anderen Abhängigkeitsobjekt verknüpft. Überschreiben nicht möglich. - Dieses SqlCommand-Objekt ist bereits mit einem anderen SqlDependency-Objekt verknüpft. @@ -3630,12 +1182,6 @@ Der angegebene Timeoutwert ist ungültig. Der Timeoutwert darf nicht < 0 sein. - - Benachrichtigungsfehler. Typ={0}, Info={1}, Quelle={2}. - - - GetMetaData ist für diesen SqlDbType nicht gültig. - Der dbType {0} ist für diesen Konstruktor ungültig. @@ -4104,9 +1650,6 @@ Interner Fehler. Wert für Klartext darf nicht NULL sein. - - Verschlüsselung nicht möglich. Die Verschlüsselung ergab {0} Bytes an Chiffretext, womit der maximal mögliche Wert von {1} Bytes überschritten ist. Der angegebene Klartextwert ist wahrscheinlich zu groß (Klartextgröße ist: {2} Bytes). - Interner Fehler. Wert des Chiffretexts darf nicht NULL sein. @@ -4224,9 +1767,6 @@ Von SQL Server wurden keine Nachweisinformationen zurückgegeben. Der Enclave-Typ ist "{0}", die Enclave-Nachweis-URL lautet {1}. - - Es kann kein sicherer Kanal eingerichtet werden. Fehlermeldung: {0} - Interner Fehler. NULL-Argument "{0}" beim Erstellen eines Objekts vom Typ "{1}" angegeben. "{0}" darf nicht NULL sein. @@ -4242,15 +1782,6 @@ Hiermit wird ein Endpunkt für einen Enclave-Nachweisdienst angegeben. Mit diesem wird die Gültigkeit und Sicherheit des in der SQL Server-Instanz konfigurierten Enclave-Anbieters überprüft, der für Berechnungen in Always Encrypted-verschlüsselten Datenbankspalten verwendet wird. - - Fehler beim Lesen des Konfigurationsabschnitts für Enclave-Anbieter. Stellen Sie sicher, dass der Abschnitt in Ihrer Anwendungskonfigurationsdatei ordnungsgemäß formatiert ist. Fehlermeldung: {0} - - - Fehler beim Instanziieren eines Enclave-Anbieters mit dem Typ "{1}" für den Namen "{0}". Fehlermeldung: {2} - - - Interner Fehler. SqlColumnEncryptionEnclaveProviderName darf nicht NULL oder leer sein. - Fehler beim Lesen des Resultsets "{0}". In der Verbindungszeichenfolge wurde keine Nachweis-URL angegeben, aber für die Abfrage sind Enclave-Berechnungen erforderlich. Der Enclave-Typ lautet "{1}". @@ -4260,9 +1791,6 @@ Interner Fehler. Beim Ausführen einer Abfrage, die Enclave-Berechnungen erfordert, war der von SQL Server empfangene Enclave-Typ leer oder NULL. - - Das Ausführen einer Abfrage erfordert Enclave-Berechnungen, aber die Anwendungskonfiguration enthält keinen Abschnitt zum Enclave-Anbieter. - Für den Enclave-Typ "{0}" und das Nachweisprotokoll "{1}" wurde kein Enclave-Anbieter gefunden. Geben Sie das richtige Nachweisprotokoll in der Verbindungszeichenfolge an. @@ -4320,9 +1848,6 @@ Standardverschlüsselungseinstellung für alle Befehle in der Verbindung. - - Erzwingt, dass Parameter verschlüsselt werden, bevor vertrauliche Daten an den Server gesendet werden. - Wörterbuchobjekt, das SQL Server-Namen und deren vertrauenswürdige Spaltenhauptschlüsselpfade enthalten. @@ -4332,9 +1857,6 @@ Die Verbindungsoption "ApplicationIntent ReadOnly" wird nicht unterstützt, wenn eine Verbindung mit einer gespiegelten SQL Server-Instanz hergestellt wird. - - Unerwartete Routinginformationen empfangen. - Ungültige Routinginformationen empfangen. @@ -4485,9 +2007,6 @@ In diesem Kontext wurde eine nicht unterstützte Authentifizierung angegeben: {0} - - Timeout bei der interaktiven Active Directory-Authentifizierung. Der Benutzer hat zu lange nicht auf die Authentifizierungsanforderung reagiert. - "Authentication=Active Directory Interactive" kann nicht verwendet werden, wenn die Eigenschaft "Credential" festgelegt wurde. @@ -4497,9 +2016,6 @@ Die Serverprozess-ID (SPID) der aktiven Verbindung. - - Timeout bei der Authentifizierung per Active Directory-Gerätecodeflow. Der Benutzer hat zu lange nicht auf die Authentifizierungsanforderung reagiert. - Die Eigenschaft "Credential" kann nicht festgelegt werden, wenn in der Verbindungszeichenfolge "Authentication=Active Directory Device Code Flow" angegeben wurde. @@ -4533,9 +2049,6 @@ Nur eindimensionale Arrays werden für die angeforderte Aktion unterstützt. - - Das angegebene Element kann nicht entfernt werden, da es nicht in der Sammlung gefunden wurde. - Nicht negative Zahl erforderlich. @@ -4563,87 +2076,36 @@ Die Eigenschaft AccessTokenCallback kann nicht festgelegt werden, wenn in der Verbindungszeichenfolge "Authentication=Active Directory Default" angegeben wurde. - - {0} hat {1} zurückgegeben. - - - Protokollfehler: Eine empfangene Nachricht enthält eine gültige Signatur, wurde aber nicht so verschlüsselt, wie es nach dem gültigen Schutzgrad erforderlich ist. - - - Fehler bei "{0}": {1} - Die Methode wird von der Klasse nicht implementiert. - - Das angeforderte Sicherheitspaket wird nicht unterstützt. - Falscher physicalConnection-Typ. Ausnahme beim Festlegen des AppContext-Schalters "{0}"={1}. - - Der Typ "{0}" wird auf dieser Plattform nicht unterstützt. - Der invariante Globalisierungsmodus wird nicht unterstützt. - - Die Authentifizierung mit Kerberos ist nicht möglich. Stellen Sie sicher, dass Kerberos auf dem Client mit "kinit" initialisiert wurde und ein Dienstprinzipalname für die SQL Server registriert wurde, um die Kerberos-Authentifizierung zuzulassen. - Das Schlüsselwort "Netzwerkbibliothek" wird auf dieser Plattform nicht unterstützt. Stellen Sie der Datenquelle stattdessen das gewünschte Protokoll voran ("tcp:" für eine TCP-Verbindung oder "np:" für eine Named Pipe-Verbindung). Socket hat den erwarteten "{0}" nicht ausgelöst. Fehlercode: "{1}". - - Der Server versucht, ein Feature zu verwenden, das auf dieser Plattform nicht unterstützt wird. - Das Schlüsselwort "{0}" wird auf dieser Plattform nicht unterstützt. Die aktuell geladene System.Transactions.dll unterstützt keine globalen Transaktionen. - - Beim Lesen von Daten vom Server wurde ein nicht unterstütztes Token "{0}" empfangen. - - - "{0}" ist kein unterstützter Handletyp. - SqlFileStream wird auf dieser Plattform nicht unterstützt. LocalDB wird auf dieser Plattform nicht unterstützt. - - Nicht genügend Pufferspeicherplatz. Erforderlich: {0} Tatsächlich: {1}. - - - Fehler beim GSSAPI-Vorgang. Status: {0} (Nebenstatus: {1}). - - - Fehler beim GSSAPI-Vorgang (Fehler: {0} ({1})). - - - Keine Unterstützung für Kanalbindung auf anderen Betriebssystemen als Windows. - - - Der Zielname sollte nicht leer sein, wenn Standardanmeldeinformationen übergeben werden. - - - Die angeforderte Schutzebene wird bei der derzeit installierten GSSAPI-Implementierung nicht unterstützt. - - - Die Serverimplementierung wird nicht unterstützt. - - - Die NTLM-Authentifizierung ist mit Standardanmeldeinformationen auf dieser Plattform nicht möglich. - Bei Verwendung durch 'DataAdapter.Update' wird der Parameterwert von DBNull.Value in (Int32)1 oder (Int32)0, falls nicht Null, geändert. @@ -4668,9 +2130,6 @@ Maximale Puffergröße überschritten: {0} - - DataColumn '{0}' für SourceColumn '{2}' ist nicht vorhanden. - Die SqlBatchCommand-Liste wurde nicht initialisiert. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx index 19ac0fe966..e62177e353 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Ascendente - Índice {0} no válido para {1} con Count={2}. @@ -132,18 +129,12 @@ Otro {1} ya contiene {0}. - - {0} con ya contenido por {1}. - {0} sólo acepta objetos de tipo no null {1}. Intentó quitar {0} que no está contenido por {1}. - - {0}.{1} debe ser único, '{2}' ya existe en la colección. - La conexión no se cerró. {0} @@ -171,12 +162,6 @@ Intento no válido de llamar a {0} cuando el lector está cerrado. - - No se puede dar de alta en la transacción porque la conexión es la conexión principal de una transacción delegada o promovida. - - - Descendente - Se esperaba una cadena no vacía para el parámetro '{0}'. @@ -204,9 +189,6 @@ No se ha inicializado la propiedad ConnectionString. - - Se detectó una Exception no conforme a CLS. - El elemento de seguridad proporcionado no es un elemento de permiso. @@ -252,9 +234,6 @@ Datos - - StateChange - Actualizar @@ -264,48 +243,12 @@ ConnectionState que indica si la conexión está abierta o cerrada. - - Evento que se desencadena cuando la conexión cambia el estado. - - - Tipo de parámetro genérico. - - - Entrada, salida o parámetro bidireccional. - - - propiedad de tiempo de diseño que se usa para generar código fuertemente tipado. - - - Desplazamiento en tipos de datos de longitud variable. - - - Nombre del parámetro. - - - Tamaño de tipos de datos de longitud variable (cadena y matrices). - - - Cuando lo utiliza DataAdapter.Update, el nombre de la columna de origen utilizado para encontrar el nombre de DataSetColumn en ColumnMappings. Copia un valor entre el parámetro y una fila de datos. - - - Cuando lo usa DataAdapter.Update (sólo UpdateCommand), versión del valor DataRow que se usa para actualizar el origen de los datos. - - - Cuando lo utiliza DataAdapter.Update, el valor del parámetro cambia de DBNull.Value a (Int32)1 o (Int32)0 si no es nulo. - - - Valor del parámetro. - No se puede compilar la colección '{0}' debido a un error en la ejecución de la consulta SQL. Vea la excepción interna para obtener detalles. Se han proporcionado más restricciones de las admitidas por el esquema solicitado ('{0}'). - - '{2}' no es un valor válido para la restricción '{1}' de la colección de esquemas '{0}'. - La colección solicitada ({0}) no está definida. @@ -315,15 +258,6 @@ La colección solicitada ({0}) no es compatible con esta versión del proveedor. - - Falta una de las columnas necesarias de las tablas DataSourceInformation. - - - La tabla DataSourceInformation debe contener exactamente una fila. - - - Faltan una o más columnas necesarias de colección de restricciones. - Existe una restricción para la que no hay fila coincidente en la colección de restricciones. @@ -342,42 +276,21 @@ Falta la colección '{0}' del XML de metadatos. - - XML de metadatos no válido. - - - XML de metadatos no válido. La colección {0} debe contener una columna {1} y debe ser una columna de cadena. - XML de metadatos no válido. La columna {1} de la colección {0} debe contener una cadena que no esté vacía. - - Acción - - - Comportamiento - Relleno Mensaje informativo - - Asignación - StatementCompleted - - UDT - Notificación - - Esquema - XML @@ -393,9 +306,6 @@ Agrupación - - ConnectionString con nombre - Seguridad @@ -408,2073 +318,204 @@ Resistencia de la conexión - - Colección con la información de usuario personalizada. - - - Indica si se distinguen mayúsculas y minúsculas en DataSet al comparar cadenas. - - - Nombre de DataSet. - - - Indica una "vista" personalizada de los datos que contiene el DataSet. Esta vista permite filtrar, buscar y explorar la vista personalizada de datos. - - - Indica si se siguen las reglas de restricción. - - - Indica que el DataSet tiene errores. - - - Indica una configuración regional con la que se pueden comparar las cadenas de DataSet. - - - Indica el espacio de nombres del uri de XML para el elemento raíz que señala a este DataSet. - - - Indica el prefijo del espacio de nombres utilizado en esta DataSet. - - - La colección que contiene las relaciones de DataSet. - - - La colección que contiene las tablas de DataSet. - - - Tiene lugar cuando no es posible combinar esquemas para dos tablas con el mismo nombre. - - - Ocurre al finalizar la Initialization. - - - Representa una caché de datos en memoria. - - - Indica si se distinguen mayúsculas y minúsculas en la tabla al comparar cadenas. - - - Devuelve las relaciones secundarias de esta tabla. - - - La colección que contiene las columnas para esta tabla. - - - La colección que contiene las restricciones para esta tabla. - - - Indica la DataSet a la que pertenece esta tabla. - - - DataView predeterminado de la tabla. - - - La expresión utilizada para calcular el valor enlazado a datos de esta fila. - - - Indica si la tabla tiene errores. - - - Indica una configuración regional con la que se pueden comparar las cadenas de la tabla. - - - Indica el tamaño inicial para esta tabla. - - - Indica el espacio de nombres del uri de XML para los elementos contenidos en esta tabla. - - - Indica el prefijo del espacio de nombres utilizado para esta tabla en representación XML. - - - Devuelve las relaciones primarias para esta tabla. - - - Indica las columnas que representan la clave principal para esta tabla. - - - Indica la colección que contiene las filas de datos de esta tabla. - - - Indica el nombre utilizado para buscar esta tabla en la colección Tables de un DataSet. - - - Tiene lugar después de que se edite correctamente una fila en la tabla. - - - Tiene lugar cuando se cambia la fila, el controlador de eventos puede modificar o cancelar el cambio. El usuario puede modificar valores y puede iniciar una excepción para cancelar la edición. - - - Tiene lugar después de que se elimine correctamente una fila de la tabla. - - - Tiene lugar cuando una fila de la tabla está marcada para eliminarla. Inicie una excepción para cancelar la eliminación. - - - Tiene lugar cuando se envía un valor para esta columna. El usuario puede modificar el valor propuesto y puede iniciar una excepción para cancelar la edición. - - - Tiene lugar cuando ha cambiado el valor para esta columna. - - - Tiene lugar antes de borrar todas las filas de la tabla. - - - Tiene lugar después de borrar correctamente todas las filas de la tabla. - - - Tiene lugar después de crear una nueva instancia de DataRow. - - - El nombre utilizado para buscar esta relación en la colección Relations del DataSet. - - - Indica las columnas secundarias de esta relación. - - - Indica las columnas primarias de esta relación. - - - Indica si las relaciones están anidadas. - - - Para eliminaciones, indica el tipo de cascada que tiene lugar en la relación. - - - Para actualizaciones, indica el tipo de cascada que tiene lugar en la relación. - - - Para aceptar o rechazar los cambios, indica que tipo de cascada tiene lugar en esta relación. - - - Indica las columnas secundarias de esta restricción. - - - Indica las columnas primarias de esta restricción. - - - Indica la columnas secundaria de esta restricción. - - - Indica las columnas de esta restricción. - - - Indica si esta restricción es de tipo PRIMARY KEY. - - - Indica el nombre de esta restricción. - - - Indica la tabla de esta restricción. - - - Indica si esta columna puede tener valores null. - - - Indica si la columna se incrementa automáticamente con nuevas filas agregadas a la tabla. La columna debe ser de tipo Int16, Int32, o Int64. - - - Indica el valor inicial de la columna AutoIncrement. - - - Indica el incremento utilizado por la columna AutoIncrement. - - - Indica la leyenda predeterminada de la interfaz de usuario de esta columna. - - - Indica el nombre utilizado para buscar esta columna en la colección Columns de DataTable. - - - Devuelve la tabla DataTable a la que pertenece esta columna. - - - Indica el tipo de datos almacenados en esta columna. - - - Indica el valor predeterminado de la columna utilizado al agregar nuevas filas a la tabla. - - - Indica el valor que esta columna calcula para cada fila basándose en otras columnas en vez de tener en cuenta la acción del usuario. - - - Indica cómo persiste esta columna en XML: como atributo, elemento, nodo de contenido simple o nada. - - - Indica el identificador uri de XML para elementos o atributos almacenados en esta columna. - - - Indica el Prefix utilizado para esta DataColumn en la representación xml. - - - Indica el índice de esta columna en la colección de Columns. - - - Indica si esta columna permite realizar cambios una vez agregada la fila a la tabla. - - - Indica si esta columna debe restringir los valores en las filas de la tabla para ser únicos. - - - Indica la longitud máxima del valor que permite esta columna. - - - Indica el DateTimeMode de esta DataColumn. - - - Indica si DataView y la interfaz de usuario asociada permiten realizar eliminaciones. - - - Indica si DataView y la interfaz de usuario asociada permiten editar. - - - Indica si DataView y la interfaz de usuario asociada permiten agregar nuevas filas. - - - Devuelve el número de elementos actuales en esta vista. - - - Devuelve un puntero para volver a DataViewManager al que pertenece este DataSet (si hay alguno). - - - Indica si una vista está abierta. - - - Indica una expresión utilizada para filtrar los datos devueltos por DataView. - - - Indica las versiones de datos devueltos por DataView. - - - Indica los nombres de la columna y el orden en el que DataView devuelve los datos. - - - Indica si se debe utilizar el orden predeterminado si no está establecida la propiedad Sort. - - - Indica la tabla que DataView utiliza para obtener datos. - - - Indica que los datos devueltos por DataView han cambiado de alguna manera. - - - Indica el origen de datos para este DataViewManager. - - - Indica la configuración de orden, filtro y estado para cualquier tabla en el DataSet correspondiente. - - - DataSet no admite valores 'union' o 'list' como simpleType. - - - Sintaxis {0} no válida: falta el atributo '{1}' necesario. - - - El valor '{1}' no es válido para el atributo '{0}'. - - - El valor del atributo '{0}' debería ser '{1}' o '{2}'. - - - No se puede encontrar el ElementType name='{0}'. - - - Falta el nombre de la tabla primaria en la relación '{0}'. - - - Falta el nombre de la tabla secundaria en la relación '{0}'. - - - Falta la clave de la tabla primaria en la relación '{0}'. - - - Falta la clave de la tabla secundaria en la relación '{0}'. - - - Tipo de datos sin definir: '{0}'. - - - Tipo de datos sin definir. - - - Selección XPath no válida dentro del nodo de campo. No se puede encontrar: {0}. - - - La selección de XPath en un nodo de selector {0} no es válida. - - - Nodo 'Key' no válido dentro de la restricción denominada: {0}. - - - Ya está en uso el nombre de la restricción {0} en el esquema. - - - No se puede convertir '{0}' en el tipo '{1}'. - - - Falta la parte '{0}' en la restricción '{1}' denominada '{2}'. - - - Definición de Relation no válida: claves con longitudes diferentes. - - - DataSet no permite una referencia circular en el elemento ComplexType denominado '{0}'. - - - DataSet no puede crear una instancia de un ComplexType abstracto para el nodo {0}. - - - Error del convertidor de varios destinos al escribir un Schema Xml. Vea la excepción interna para obtener detalles. - - - Error del convertidor de varios destinos al escribir un Schema Xml. Se devolvió null o una cadena vacía. - - - Declaración duplicada '{0}'. - - - No se puede cargar diffGram. Falta la tabla '{0}' en el DataSet de destino. - - - No se puede cargar diffGram. Falta el nodo 'sql'. - - - El nombre de la columna '{0}' está definido para diferentes tipos de asignación. - - - El prefijo '{0}' no es válido porque contiene caracteres especiales. - - - Referencia circular en la tabla '{0}' anidada en sí misma. - - - DataSet no puede expandir entidades. Utilice XmlValidatingReader y establezca la propiedad EntityHandling en consecuencia. - - - El tipo '{0}' no implementa la interfaz IXmlSerializable, por lo que no puede continuar la serialización. - - - No se puede continuar con la deserialización. El dato no implementa IXMLSerializable, por lo que no se admite el polimorfismo. - - - DataTable no es compatible con la inferencia de esquemas de Xml. - - - No se puede continuar con la serialización de DataTable '{0}'. Contiene una DataRow que tiene varias filas primarias en la misma clave externa. - - - Falta el atributo IsDataSet del Schema de entrada. - - - No se puede determinar el elemento DataSet. El atributo IsDataSet existe más de una vez. - - - DataSet no serializará tipos que implementen IDynamicMetaObjectProvider pero que no implemente IXmlSerializable. - - - Característica sin implementar. {0}. - - - Error de sintaxis: falta el operando después del operador '{0}'. - - - El tipo no coincide en la expresión '{0}'. - - - Expresión demasiado compleja. - - - No se puede encontrar la columna [{0}]. - - - La expresión contiene una constante en la cadena no válida: {0}. - - - La expresión contiene una llamada a la función sin definir {0}(). - - - Error de sintaxis en la expresión. - - - Número no válido de argumentos: función {0}(). - - - Falta el paréntesis de cierre en la expresión. - - - No se puede interpretar el token '{0}' en la posición {1}. - - - Se esperaba {0}, pero el token en la posición {2} es {1}. - - - No se puede convertir de {0} a {1}. - - - No se puede convertir el valor '{0}' al tipo: {1}. - - - Nombre de columna no válido [{0}]. - - - La expresión contiene una constante de fecha '{0}' no válida. - - - En la lista de expresiones del operador IN sólo están permitidas las expresiones constantes. - - - Error en el operador Like: el patrón de cadena '{0}' no es válido. - - - Error de sintaxis: los elementos detrás de la palabra clave IN deben estar separados por comas y entre paréntesis. - - - El tipo en el argumento de función no coincide: {0}(), argumento {1}, se esperaba {2}. - - - El tipo no coincide en el argumento de la función: {0}(), argumento {1}, se esperaba un tipo entero. - - - No se puede realizar la operación '{0}' en {1} y {2}. - - - El operador '{0}' es ambiguo en operandos de tipo '{1}' y '{2}'. No se pueden mezclar tipos signed y unsigned. Utilice la función explícita Convert(). - - - Error de sintaxis: la palabra clave IN debe estar seguida de una lista con expresiones separadas por comas y entre paréntesis. - - - La expresión contiene un operador '{0}' no compatible. - - - La expresión contiene un nombre no válido: '{0}'. - - - Error de sintaxis: falta el operando antes del operador '{0}'. - - - La expresión tiene demasiados paréntesis de cierre. - - - La tabla [{0}] se encuentra en más de una relación. Debe mencionar el nombre de la relación en la expresión '{1}'. - - - Error de sintaxis en el argumento aggregate: Se esperaba una columna simple con un calificador 'Child'. - - - Referencia sin enlazar en la expresión aggregate '{0}'. - - - No se puede evaluar una expresión no constante en la fila actual. - - - Referencia sin enlazar en la expresión '{0}'. - - - No se puede evaluar. La expresión '{0}' no es un agregado. - - - La expresión de filtro '{0}' no se evalúa como un término booleano. - - - El nombre de tipo '{0}' no es válido. - - - Error de sintaxis en la expresión de búsqueda: se esperaba la palabra clave 'Parent' seguida de un argumento en una columna con un calificador de relación: Parent[(<relation_name>)].<column_name>. - - - Es necesaria una fila o una tabla para invocar DataFilter. - - - El argumento {0}() está fuera del intervalo. + + El desplazamiento y la longitud estaban fuera de los límites para esta matriz o el recuento es superior al número de elementos desde el índice al final de la colección de origen. - - Error de sintaxis: uso no válido del operador 'Is'. Sintaxis correcta: <expression> Is [Not] Null. + + No se puede convertir el objeto de tipo '{0}' al objeto de tipo '{1}'. - - Valor demasiado grande o demasiado pequeño para un tipo '{0}'. + + El único valor permitido para la propiedad '{0}' es '{1}'. - - Error al dividir por cero. + + Los valores permitidos para la propiedad '{0}' son '{1}' o '{2}'. - - No se encuentra la relación primaria '{0}'. + + Los valores QuotePrefix y QuoteSuffix especificados no coinciden. - - El argumento 'hours' está fuera del intervalo. El valor debe estar comprendido entre -14 y +14. + + La longitud del argumento '{0}' supera su límite de '{1}'. - - El argumento 'minutes' está fuera del intervalo. El valor debe estar comprendido entre -59 y +59. + + Valor no válido para la clave '{0}'. - - El primer intervalo de tiempo proporcionado supera un total de 14 horas. + + Para poder utilizar la clave '{0}', debe estar presente la clave '{1}'. - - La propiedad Kind del argumento DateTime proporcionado no coincide con los argumentos 'hours' y 'minutes'. + + La longitud del valor para la clave '{0}' supera el límite '{1}'. - - Una DataColumn de tipo '{0}' no admite la expresión. + + Palabra clave no admitida: '{0}'. - - No se pudieron habilitar las restricciones. Una o varias filas contienen valores que infringen las restricciones NON-NULL, UNIQUE o FOREIGN-KEY. + + Error interno de .NET Framework Data Provider {0}. - - No se puede modificar la misma colección. + + {0} "{1}". - - El nombre dado '{0}' coincide con al menos dos nombres del objeto de la colección con uso distinto de mayúsculas o minúsculas, pero no coincide con ninguno en el que el uso de mayúsculas y minúsculas sea igual. + + {0} "{1}", uso incorrecto de comillas. - - El nombre especificado '{0}' coincide con al menos dos nombres del objeto de la colección con espacios de nombres diferentes. + + {0} "{1}", el límite actual de "{2}" es insuficiente. - - El desplazamiento y la longitud estaban fuera de los límites para esta matriz o el recuento es superior al número de elementos desde el índice al final de la colección de origen. + + Fallo de SqlBulkCopy.WriteToServer porque SqlBulkCopy.DestinationTableName es un nombre de varias partes no válido. - - El argumento '{0}' está fuera del intervalo. + + Fallo al procesar los resultados de SQL Server por un nombre de varias partes no válido. - - El argumento '{0}' no puede ser nulo. + + SqlParameter.UdtTypeName no es un nombre de varias partes válido - - El argumento '{0}' contiene un valor null. + + SqlParameter.TypeName no es un nombre de varias partes válido - - No se puede encontrar la columna {0}. + + Fallo en SqlCommand.DeriveParameters, el valor de la propiedad SqlCommand.CommandText es un nombre de varias partes no válido. - - La columna '{0}' ya pertenece a esta DataTable. + + No se admite la conexión a una instancia de SQL Server reflejada mediante la opción de conexión MultiSubnetFailover. - - La columna '{0}' ya pertenece a otra DataTable. + + El valor de enumeración de {0}, {1}, no es compatible con el método {2}. - - No puede haber más de una columna SimpleContent en DataTable. + + El proveedor de datos SqlClient de .Net Framework no admite el valor de enumeración de {0}, {1}. - - No se puede agregar una columna SimpleContent a una tabla que contenga columnas de elementos o relaciones anidadas. + + {0}: no se ha inicializado la propiedad CommandText - - La columna '{0}' ya pertenece a DataTable. + + {0}: la propiedad Connection no se ha inicializado. - - No se puede agregar la columna llamada '{0}': esta tabla DataTable ya contiene una tabla anidada con el mismo nombre. + + {0} requiere una Connection abierta y disponible. {1} - - Esta tabla DataTable ya contiene una columna llamada '{0}' : no se puede utilizar el mismo nombre en una tabla anidada. + + No existe el procedimiento almacenado '{0}'. - - No se puede eliminar una columna que no pertenece a esta tabla. + + La transacción no está asociada a la conexión actual o se ha completado. - - No se puede eliminar esta columna, porque es parte de la clave principal. + + La transacción asociada con la conexión actual se ha completado, pero no se ha desechado. Debe desecharse antes de utilizar la conexión para ejecutar instrucciones SQL. - - No se puede eliminar la columna, porque es parte de una clave primaria de la relación {0}. + + {0} requiere que el comando tenga una transacción cuando la conexión asignada al mismo está en una transacción local pendiente. No se ha inicializado la propiedad Transaction del comando. - - No se puede eliminar esta columna, porque es parte de una restricción {0} en la tabla {1}. + + Ya hay un DataReader abierto asociado a {0}; debe cerrarlo primero. - - No se puede eliminar esta columna, porque es parte de una expresión: {0} = {1}. + + {0} DeriveParameters sólo admite CommandType.StoredProcedure, no CommandType.{1}. - - No se puede establecer la propiedad AutoIncrement en una columna calculada. - - - No se puede establecer la propiedad AutoIncrement en una columna con DefaultValue. - - - No se puede establecer DefaultValue en una columna AutoIncrement. - - - AutoIncrementStep debe ser un valor que no sea cero. - - - ColumnName es necesario cuando es parte de DataTable. - - - No se puede cambiar DataType de una columna que contiene datos. - - - La columna requiere que DataType sea válida. - - - DefaultValue de la columna {0} es de tipo {1} y no se puede convertir a {2}. - - - DefaultValue de la columna es de tipo {0} y no se puede convertir a {1}. - - - DefaultValue de la columna {0} es de tipo {1}, pero la columna es de tipo {2}. - - - No se puede cambiar la propiedad ReadOnly para la columna de expresión. - - - No se puede cambiar la propiedad Unique en la columna de expresión. - - - No se puede crear una expresión en una columna que tiene AutoIncrement o Unique. - - - No se puede establecer la expresión porque la columna no puede ser ReadOnly. - - - No se puede establecer la propiedad Expression en la columna {0}, porque es parte de una restricción. - - - No se puede crear una restricción basada en la columna Expression {0}. - - - No se puede establecer la propiedad Expression debido a una referencia circular en la expresión. - - - La columna '{0}' tiene valores null. - - - La columna '{0}' no permite tener valores nulls. - - - La columna '{0}' es de solo lectura. - - - La Column '{0}' contiene valores que no son únicos. - - - La columna '{0}' no pertenece a la tabla {1}. - - - La columna debe pertenecer a una tabla. - - - No se puede almacenar <{0}> en la columna {1}. El tipo esperado es {2}. - - - No puede establecer el valor de Column '{0}' en null. Utilice DBNull en su lugar. - - - No se puede establecer la columna '{0}'. El valor infringe el límite de MaxLength de esta columna. - - - MaxLength se aplica únicamente al tipo de datos String. La propiedad MaxLength de Column '{0}' no se puede establecer en un número negativo. - - - No se puede establecer la propiedad MaxLength de Column '{0}' en '{1}'. Hay al menos una cadena en la tabla más larga que el nuevo límite. - - - No se puede establecer la propiedad MaxLength de Column '{0}'. La columna es SimpleContent. - - - No se puede establecer la propiedad DataType de Column '{0}' en {1}. La columna es SimpleContent. - - - No se puede establecer la propiedad MappingType de Column '{0}' en SimpleContent. DataType de la columna es {1}. - - - La Column '{0}' superó el límite de MaxLength. - - - La columna '{0}' no admite DBNull.Value. - - - No se puede cambiar la propiedad Namespace de Column '{0}'. La columna es SimpleContent. - - - Una vez que tiene datos, no se puede cambiar AutoIncrement en DataColumn con tipo '{0}'. - - - La columna '{0}' no pertenece a la tabla subyacente '{1}'. - - - DataColumn con tipo '{0}' es un complexType. No se puede serializar el valor de un tipo complejo como Attribute - - - DateTimeMode solo se puede establecer en DataColumns de tipo DateTime. - - - '{0}' es un valor DataSetDateTime no válido. - - - No se puede cambiar DateTimeMode de '{0}' a '{1}' una vez que la tabla contiene datos. - - - El Type '{0}' no contiene el campo o la propiedad Null static. - - - El tipo '{0}' no implementa IRevertibleChangeTracking; por tanto, no se puede continuar con RejectChanges(). - - - Solo se puede llamar a SetAdded y SetModified en DataRows con DataRowState no modificado. - - - Ordinal '{0}' supera el número máximo. - - - DataSet no admite System.Nullable<>. - - - No se puede cambiar el nombre de una restricción por una cadena vacía cuando está en ConstraintCollection. - - - No se pueden forzar las restricciones en la restricción {0}. - - - La columna '{0}' está restringida para ser única. Ya está presente el valor '{1}'. - - - La restricción '{0}' no pertenece a esta DataTable. - - - No se puede encontrar la restricción {0}. - - - Esta restricción coincide con la restricción denominada {0}, que ya está en la colección. - - - La Constraint denominada '{0}' ya pertenece a esta DataTable. - - - Estas columnas no tienen valores únicos actualmente. - - - Estas columnas no señalan a esta tabla. - - - No se puede habilitar esta restricción ya que todos los valores no tienen los valores primarios correspondientes. - - - No se puede agregar esta restricción porque ForeignKey no pertenece a la tabla {0}. - - - No se puede eliminar una restricción que no pertenece a esta tabla. - - - No se puede eliminar la restricción UNIQUE '{0}'. Elimine primero la restricción FOREIGN KEY '{1}'. - - - No se puede eliminar esta fila porque las restricciones están forzando la relación {0}, y si se elimina esta fila, las filas secundarias se deshabilitarán. - - - No se puede realizar este cambio porque las restricciones están forzando la relación {0}, y si se cambia este valor, las filas secundarias se deshabilitarán. - - - No se puede borrar la tabla {0} porque ForeignKeyConstraint {1} fuerza las restricciones y hay filas secundarias en {2}. - - - ForeignKeyConstraint {0} requiere que los valores clave secundarios ({1}) existan en la tabla primaria. - - - No se puede obtener acceso a la propiedad por la siguiente razón '{0}'. - - - No se puede eliminar esta fila porque tiene filas secundarias, y las restricciones en la relación {0} están forzadas. - - - No se puede agregar una restricción PRIMARY KEY porque ya se estableció una clave principal para la tabla. - - - No se puede añadir restricción a DataTable '{0}' que es una tabla secundaria en dos relaciones anidadas. - - - No se puede crear una clave a partir de columnas que pertenecen a diferentes tablas. - - - No puede tener 0 columnas. - - - No puede haber más de {0} columnas. - - - No se puede crear una clave cuando la misma columna aparece más de una vez: '{0}' - - - No se puede eliminar una restricción UNIQUE porque es la clave principal de la tabla. - - - No se puede eliminar una restricción UNIQUE porque es la clave principal de la tabla {0}. - - - Las columnas primarias y las columnas secundarias no tienen columnas que coincidan. - - - ParentKey y ChildKey son idénticos. - - - ParentColumns y ChildColumns deben tener la misma longitud. - - - ParentColumns y ChildColumns no pueden tener una longitud que sea cero. - - - La fila no pertenece al mismo DataSet que la relación. - - - RelationName es necesario como parte de DataSet. - - - GetChildRows requiere una fila cuya Table es {0}, pero la Table de la fila especificada es {1}. - - - Esta relación debe conectar dos tablas en este DataSet para agregarse a este DataSet. - - - GetParentRow requiere una fila cuya tabla es {0}, pero la fila especificada de la tabla es {1}. - - - SetParentRow requiere una fila secundaria cuya tabla es {0}, pero la fila especificada de la tabla es {1}. - - - No puede haber una relación entre tablas de diferentes DataSet. - - - No se puede crear una relación entre tablas de diferentes DataSets. - - - Ya existe una relación para estas columnas secundarias. - - - Esta relación no pertenece a esta colección de relaciones. - - - Esta relación ya pertenece a otro DataSet. - - - Esta relación ya pertenece a este DataSet. - - - La Relation denominada '{0}' ya pertenece a este DataSet. - - - La Relation {0} no pertenece a este DataSet. - - - No se puede encontrar la relación {0}. - - - No se puede crear una colección en una tabla null. - - - Se ha eliminado del DataSet la tabla para la que esta colección muestra relaciones. - - - No se puede agregar una relación a la colección de tablas ParentRelation donde esta tabla no es la tabla secundaria. - - - No se puede agregar una relación a la colección de tablas ChildRelation donde esta tabla no es la tabla primaria. - - - No se puede establecer en false la propiedad 'Nested' para esta relación. - - - La misma tabla '{0}' no puede ser una tabla secundaria en dos relaciones anidadas. - - - La tabla ({0}) no puede ser la tabla secundaria de sí misma en relaciones anidadas. - - - No se puede agregar una clase DataRelation o Constraint con configuración Locale o CaseSensitive distinta entre las tablas primaria y secundarias. - - - No se puede crear DataRelation si las columnas primarias o secundarias no están en DataSet. - - - Una tabla anidada '{0}' que hereda su espacio de nombres no puede tener varias tablas primarias en espacios de nombres diferentes. - - - Una tabla anidada '{0}' con espacios de nombres vacíos no puede tener varias tablas primarias es espacios de nombres diferentes. - - - La fila no pertenece al mismo DataSet que la relación. - - - No se puede realizar esta operación en una fila que no está en la tabla. - - - La relación y la fila secundaria no pertenecen al mismo DataSet. - - - No se puede cambiar un valor propuesto en el evento RowChanging. - - - No se puede llamar a EndEdit() dentro de un evento OnRowChanging. - - - No se puede llamar a BeginEdit() dentro del evento RowChanging. - - - No se puede llamar a CancelEdit() dentro de un evento OnRowChanging. Inicie una exception para cancelar la actualización. - - - No se puede llamar a Delete dentro de un evento OnRowDeleting. Inicie una exception para cancelar la función de eliminar. - - - La matriz de entrada es más larga que el número de columnas en esta tabla. - - - No hay datos actuales a los que se pueda obtener acceso. - - - No hay datos originales a los que se pueda obtener acceso. - - - No hay datos propuestos a los que se pueda obtener acceso. - - - Esta fila ha sido eliminada de la tabla y no contiene ningún dato. BeginEdit() permite la creación de nuevos datos en esta fila. - - - No se puede obtener acceso a la información eliminada de una fila. - - - La versión debe ser original, actual o propuesta. - - - No hay ninguna fila en la posición {0}. - - - La posición de inserción de la fila {0} no es válida. - - - El valor rowOrder value={0} está duplicado en la tabla denominada '{1}'. - - - Faltan valores en la secuencia rowOrder para la tabla '{0}'. - - - El elemento DataRow especificado no está en la colección DataRowCollection actual. - - - Esta fila ya pertenece a otra tabla. - - - Esta fila ya pertenece a esta tabla. - - - No se puede eliminar esta fila porque ya está eliminada. - - - Esta fila está vacía. - - - No se puede eliminar una fila que ya está eliminada. - - - Una fila secundaria tiene varios elementos primarios. - - - patrón de bits de estado de fila no reconocido. - - - No se puede cambiar el nombre del DataSet a una cadena vacía. - - - El nombre '{0}' no es válido. DataSet no puede tener el mismo nombre que DataTable. - - - El espacio de nombres del esquema no es válido. Utilice éste en su lugar: {0}. - - - No se puede cambiar la propiedad CaseSensitive ni Locale. Este cambio provocaría que al menos una clase DataRelation o Constraint tengan una configuración Locale o CaseSensitive distinta entre sus tablas relacionadas. - - - La propiedad SchemaSerializationMode solo se puede establecer si la reemplaza un DataSet derivado. - - - Las columnas PrimaryKey no pertenecen a esta tabla. - - - No se puede agregar una relación anidada ni una columna de elementos a una tabla que contenga una columna SimpleContent. - - - TableName es necesario cuando forma parte de DataSet. - - - DataTable ya tiene una columna de contenido simple. - - - La tabla no tiene una clave principal. - - - {0} no es una entrada de cadena de Sort válida. - - - No se puede serializar DataTable. Una de las tablas DataTable utilizadas en una o varias expresiones DataColumn no es descendiente de la DataTable actual. - - - Esta DataTable sólo puede ser remota dentro de DataSet. Una o varias columnas Expression hacen referencia a otras tablas DataTable. - - - DataSet y DataTable no pueden tener valores de propiedad de formato remoto diferentes. - - - No se puede serializar DataTable. No se ha establecido el nombre DataTable. - - - DataTable denominada '{0}' ya pertenece a este DataSet. - - - Una tabla DataTable denominada '{0}' con el mismo Namespace '{1}' ya pertenece a este DataSet. - - - La tabla ({0}) no puede ser la tabla secundaria de sí misma en una relación anidada: el nombre de DataSet está en conflicto con el nombre de la tabla. - - - El nombre '{0}' no es válido. DataTable no puede tener el mismo nombre que DataSet. - - - DataTable ya pertenece a otro DataSet. - - - DataTable ya pertenece a este DataSet. - - - La tabla {0} no pertenece a esta DataSet. - - - No se puede encontrar la tabla {0}. - - - No se puede eliminar una tabla que contenga relaciones. Elimine primero las relaciones. - - - No se puede eliminar la tabla {0}, porque hace referencia a ForeignKeyConstraint {1}. Elimine primero la restricción. - - - DataTable '{0}' no coincide con ninguna tabla DataTable del origen. - - - Falta la definición del DataSet de destino {0}. - - - Falta el DataSet de destino {0} {1}. - - - Las propiedades de <target>.{0} y <source>.{0} están en conflicto: la propiedad DataType no coincide. - - - <target>.PrimaryKey y <source>.PrimaryKey tienen un valor de Length diferente. - - - Las columnas en PrimaryKey no coinciden: <target>.{0} frente a <source>.{1}. - - - No se puede combinar la relación {0} porque las claves tienen columnas que no coinciden. - - - En la tabla de destino {0} falta la definición para la columna {1}. - - - La columna PrimaryKey {0} no existe en la tabla de origen. - - - Debe establecer el parámetro RowStates con una combinación válida de valores de la enumeración DataViewRowState. - - - Encuentra una fila basándose en el criterio de ordenación, pero no ha especificado ningún criterio de ordenación. - - - Se esperaban los valores {0} para la clave indizada, pero se han recibido valores {1}. - - - Uso no válido de la función de agregado {0}() y el tipo: {1}. - - - Tipo de almacenamiento no válido: {0}. - - - La persistencia DataSet Xml no admite el valor '{0}' como valor Char; utilice el almacenamiento de Byte en su lugar. - - - El tipo de valor no coincide con el tipo de columna - - - El tipo '{0}' no implementa la interfaz IComparable. No se puede realizar la comparación. - - - No se puede establecer {0}. - - - No se puede cambiar DataSet en DataViewManager, ya que es la vista predeterminada de DataSet. - - - No se pueden mostrar RowStateFilter, ModifiedOriginals y ModifiedCurrents a la vez. - - - No se puede cambiar la propiedad Table en DefaultView o DataView que proviene de DataViewManager. - - - No se puede cambiar la propiedad DataSet una vez establecida. - - - Es necesario establecer DataSet antes de utilizar DataViewManager. - - - No se puede cambiar la propiedad Table una vez establecida. - - - Es necesario establecer DataTable antes de utilizar DataView. - - - No se puede enlazar a DataTable sin un nombre. - - - No se puede establecer un objeto en esta lista. - - - No se puede llamar a AddNew en DataView cuando AllowNew es igual a false. - - - DataView no está abierto. - - - Esta relación no es primaria de la tabla a la que señala DataView. - - - No se puede eliminar en DataSource cuando AllowDelete es igual a false. - - - No se puede editar en DataSource cuando AllowEdit es igual a false. - - - El índice {0} es negativo o superior al recuento de filas. - - - No se pueden agregar objetos externos a esta lista. - - - No se puede borrar esta lista. - - - No se pueden insertar objetos externos en esta lista. - - - No se pueden eliminar objetos que no estén en la lista. - - - {0} no es DataColumn ni DataRelation para la tabla {1}. - - - El valor de Min ({0}) debe ser inferior o igual al valor de max ({1}) en un objeto Range. - - - Hay un intervalo null. - - - El campo de MinimumCapacity no puede ser negativo. - - - No se puede generar el identificador para el nombre '{0}'. - - - Hay más de una tabla con el mismo nombre '{0}' (aunque el espacio de nombres sea diferente). - - - La columna '{0}': Type '{1}' no puede ser null. - - - La columna '{0}': tipo '{1}' no tiene un constructor sin parámetros. - - - La columna '{0}': tipo '{1}' no tiene un constructor con un argumento de cadena. - - - No se puede convertir el objeto de tipo '{0}' al objeto de tipo '{1}'. - - - Exception de Data. - - - Exception de Constraint. - - - No se tiene acceso a filas eliminadas. - - - No se permiten nombres duplicados. - - - Operación no compatible en el evento RowChanging. - - - Restricción no válida. - - - Falta la clave principal. - - - No se permite el valor Null. - - - La columna está marcada como de solo lectura. - - - No se encuentra la fila en la tabla. - - - No se puede encontrar la versión. - - - Los datos ReadOnly se han modificado. - - - DataTableReader no es válido para la tabla DataTable '{0}' actual. - - - Se ha cambiado el esquema de la DataTable actual '{0}' en DataTableReader; DataTableReader no es válido. - - - No se puede crear DataTableReader. No hay DataTable en DataSet. - - - No se puede crear DataTableReader. El argumento está vacío. - - - No se puede crear DataTableReader. Los argumentos contienen el valor null. - - - El estado de la DataRow actual es Deleted o Detached. - - - La tabla DataTable '{0}' actual está vacía. No hay DataRow en DataTable. - - - El índice interno de DataTable está dañado: '{0}'. - - - Se modificó la colección; puede que no se ejecute la operación de enumeración. - - - El tipo simple '{0}' ya se ha declarado con diferente '{1}'. - - - Intercalación no válida. - - - No se puede cambiar el nombre de la tabla una vez que DataSet asociado está asignado a un documento XML cargado. - - - No se puede cambiar el espacio de nombres de la tabla una vez que el DataSet asociado está asignado a un documento XML cargado. - - - No se puede cambiar el nombre de la columna una vez que el DataSet asociado está asignado a un documento XML cargado. - - - No se puede cambiar el espacio de nombres de la columna una vez que la DataSet asociada está asignada a un documento XML cargado. - - - No se puede cambiar la propiedad ColumnMapping una vez que el DataSet asociado se asigne a un documento XML cargado. - - - No se puede agregar o eliminar columnas de la tabla una vez que el DataSet está asignado a un documento XML cargado. - - - No se pueden agregar o eliminar tablas de DataSet una vez que está asignada a un documento XML cargado. - - - No se pueden agregar, eliminar o cambiar relaciones anidadas de DataSet una vez que está asignada a un documento XML cargado. - - - El parámetro DataSet no es válido. No puede ser null. - - - No se puede cambiar el nombre de DataSet una vez que está asignada a un documento XML cargado. - - - No se puede clonar este tipo de nodo: {0}. - - - No se puede cargar XmlDataDocument si ya contiene datos. Utilice un nuevo XmlDataDocument. - - - El DataSet se puede asociar a un XmlDataDocument como máximo. No se puede asociar el DataSet al XmlDataDocument actual porque ya está asociado a otro XmlDataDocument. - - - Establezca DataSet.EnforceConstraints == false antes de intentar editar XmlDataDocument utilizando operaciones de XML. - - - DataDocument no admite GetElementById(). - - - No se pueden crear referencias de entidad en DataDocument. - - - XmlDataDocument no admite la función Clear en DateSet y DataTable. - - - No se puede eliminar la columna porque está incorporada a este dataSet. - - - No se puede quitar la relación porque está integrada en este dataSet. - - - Tiene lugar cuando la propiedad de este control cambia. - - - Tiene lugar cuando cambia la pertenencia a esta colección. - - - No se puede obtener el valor porque es DBNull. - - - La propiedad '{0}' necesita un sistema operativo Microsoft WindowsNT o WindowsNT. - - - No se encuentra el proveedor de datos .NET Framework solicitado. Puede que no esté instalado. - - - La implementación del proveedor de datos .NET Framework solicitado no tiene un campo de instancia de un tipo derivado de System.Data.Common.DbProviderFactory. - - - No se encuentra o no se puede cargar el proveedor de datos .NET Framework registrado. - - - Se necesita el nombre calificado con el ensamblado del proveedor de datos .NET Framework que falta. - - - Sólo se permiten elementos. - - - No se permiten nodos secundarios. - - - Atributo no reconocido '{0}'. - - - Elemento no reconocido. - - - La sección '{0}' sólo puede aparecer una vez en cada archivo de configuración. - - - No se encuentra el atributo obligatorio '{0}'. - - - El atributo necesario '{0}' no puede estar vacío. - - - Se esperaba una matriz no vacía para el parámetro '{0}'. - - - El único valor permitido para la propiedad '{0}' es '{1}'. - - - Los valores permitidos para la propiedad '{0}' son '{1}' o '{2}'. - - - Los valores QuotePrefix y QuoteSuffix especificados no coinciden. - - - La longitud del argumento '{0}' supera su límite de '{1}'. - - - Se esperaba un argumento del tipo {1}, pero se ha recibido del tipo {0}. - - - Valor no válido para la clave '{0}'. - - - Para poder utilizar la clave '{0}', debe estar presente la clave '{1}'. - - - La longitud del valor para la clave '{0}' supera el límite '{1}'. - - - Palabra clave no admitida: '{0}'. - - - No se puede cargar el archivo UDL. - - - Archivo UDL no válido. - - - Error interno de .NET Framework Data Provider {0}. - - - Las propiedades QuotePrefix y QuoteSuffix no se pueden cambiar una vez que genera un comando Insert, Update o Delete. - - - Necesita inicializar la propiedad DataAdapter.SelectCommand. - - - Necesita inicializar la propiedad DataAdapter.SelectCommand.Connection; - - - {0} "{1}". - - - {0} "{1}", uso incorrecto de comillas. - - - {0} "{1}", el límite actual de "{2}" es insuficiente. - - - Fallo de SqlBulkCopy.WriteToServer porque SqlBulkCopy.DestinationTableName es un nombre de varias partes no válido. - - - Fallo al procesar los resultados de SQL Server por un nombre de varias partes no válido. - - - SqlParameter.UdtTypeName no es un nombre de varias partes válido - - - SqlParameter.TypeName no es un nombre de varias partes válido - - - Fallo en SqlCommand.DeriveParameters, el valor de la propiedad SqlCommand.CommandText es un nombre de varias partes no válido. - - - Fallo en OdbcCommandBuilder.DeriveParameters porque el valor de propiedad OdbcCommand.CommandText es un nombre de varias partes no válido. - - - Fallo en OleDbCommandBuilder.DeriveParameters porque el valor de propiedad OleDbCommandBuilder.CommandText es un nombre de varias partes no válido - - - No se admite la conexión a una instancia de SQL Server reflejada mediante la opción de conexión MultiSubnetFailover. - - - Error en la asignación de columna SourceColumn '{0}' porque DataColumn '{1}' es una columna calculada. - - - Falta de coincidencia entre los tipos que no se pueden convertir SourceColumn '{0}' de {1} y DataColumn '{2}' de {3}. - - - Falta DataColumn '{0}' en DataTable '{1}' para SourceColumn '{2}'. - - - SourceColumn no puede ser una cadena vacía. - - - Falta la asignación de SourceColumn '{0}'. - - - El valor de enumeración de {0}, {1}, no es compatible con el método {2}. - - - El proveedor de datos Odbc de .NET Framework no admite el valor de enumeración de {0}, {1}. - - - El proveedor de datos .NET Framework para OleDb no admite el valor de enumeración de {0}, {1}. - - - El proveedor de datos SqlClient de .Net Framework no admite el valor de enumeración de {0}, {1}. - - - No se puede recuperar ComputerNameDnsFullyQualified, {0}. - - - Falta '{0}' DataTable para '{1}' SourceTable. - - - Es necesario que SourceTable no sea una cadena vacía - - - Falta la asignación de SourceTable: '{0}' - - - {0}: no se ha inicializado la propiedad CommandText - - - {0}: la propiedad Connection no se ha inicializado. - - - {0} requiere una Connection abierta y disponible. {1} - - - Fill: la propiedad SelectCommand.Connection no se ha inicializado. - - - FillPage: la propiedad SelectCommand.Connection no se ha inicializado. - - - FillSchema: la propiedad SelectCommand.Connection no se ha inicializado. - - - La actualización requiere que InsertCommand tenga un objeto de conexión. La propiedad Connection de InsertCommand no se ha inicializado. - - - La actualización requiere que UpdateCommand tenga un objeto de conexión. La propiedad Connection de UpdateCommand no se ha inicializado. - - - La actualización requiere que DeleteCommand tenga un objeto de conexión. La propiedad Connection de DeleteCommand no se ha inicializado. - - - La actualización requiere un objeto de conexión. La propiedad Connection no se ha inicializado. - - - La actualización requiere que el clon del comando tenga un objeto de conexión. La propiedad Connection del clon del comando no se ha inicializado. - - - La actualización requiere una conexión. - - - Update requiere que {0}Command tenga un objeto de conexión abierta. {1} - - - Update requiere que {0}Command tenga un objeto de conexión abierta. {1} - - - Update requiere que {0}Command tenga un objeto de conexión abierta. {1} - - - Update requiere un comando de actualización para tener un objeto de conexión abierta. {1} - - - No existe el procedimiento almacenado '{0}'. - - - La transacción asignada a este comando debe ser la transacción local pendiente más anidada. - - - La transacción no está asociada a la conexión actual o se ha completado. - - - La transacción asociada con la conexión actual se ha completado, pero no se ha desechado. Debe desecharse antes de utilizar la conexión para ejecutar instrucciones SQL. - - - {0} requiere que el comando tenga una transacción cuando la conexión asignada al mismo está en una transacción local pendiente. No se ha inicializado la propiedad Transaction del comando. - - - Hay abierto un SqlResultSet asociado a este comando que debe cerrarse antes. - - - Ya hay un DataReader abierto asociado a {0}; debe cerrarlo primero. - - - {0} DeriveParameters sólo admite CommandType.StoredProcedure, no CommandType.{1}. - - - No se puede llamar al método '{0}' más de una vez para la misma ejecución. - - - Resultado asíncrono incorrecto. - - - No se ha inicializado la propiedad SelectCommand antes de llamar a '{0}'. - - - No se esperaba aquí el StatementType {0}. - - - FillSchema: se esperaba una cadena que contenga datos para el nombre de SourceTable. - - - El valor de MaxRecords {0} no es válido. El valor debe ser >= 0. - - - El valor de StartRecord {0} no es válido. El valor debe ser >= 0. - - - Fill: se esperaba una cadena con datos para el nombre de SourceTable. - - - Las columnas de capítulos jerárquicos se deben asignar a un AutoIncrement DataColumn. - - - DataReader.GetFieldType({0}) devolvió null. - - - Especifique sólo un elemento en la matriz dataTables al utilizar valores distintos de cero para startRecords o maxRecords. - - - Update no puede encontrar TableMapping['{0}'] o DataTable '{0}'. - - - Update: se esperaba un nombre de SourceTable que contenga datos. - - - Falta TableMapping cuando TableMapping.DataSetTable='{0}'. - - - Update requiere que el clon del comando sea válido. - - - La generación SQL automática durante la operación Update requiere que SelectCommand sea válido. - - - Update requiere que InsertCommand sea válido cuando se pasa la colección DataRow con nuevas filas. - - - Update requiere que UpdateCommand sea válido cuando se pasa la colección DataRow con filas modificadas. - - - Update requiere que DeleteCommand sea válido cuando se pasa la colección DataRow con filas eliminadas. - - - DataRow[{0}] pertenece a un DataTable diferente a DataRow[0]. - - - RowUpdatedEvent: errores; no hay información adicional disponible. - - - RowUpdatingEvent: errores; no hay información adicional disponible. - - - Durante el procesamiento por lotes, se ha detectado que el valor de propiedad de UpdatedRowSource de UpdateRowSource.FirstReturnedRecord o UpdateRowSource.Both del comando no es válido. - - - Infracción de simultaneidad: UpdateCommand afectó a {0} de los {1} registros esperados. - - - Infracción de simultaneidad: DeleteCommand afectó a {0} de los {1} registros esperados. - - - Infracción de simultaneidad: el comando por lotes afectó a {0} de los {1} registros esperados. + + No se puede llamar al método '{0}' más de una vez para la misma ejecución. Valor de CommandTimeout {0} no válido. El valor debe ser >= 0. - - {1}[{0}]: la propiedad Size tiene un tamaño no válido de 0. - - - El método {0}.Prepare requiere que todos los parámetros tengan un tipo establecido de forma explícita. - - - El método {0}.Prepare requiere que todos los parámetros de longitud variable tengan un valor distinto de cero explícitamente establecido para Size. - - - El método {0}.Prepare requiere que los parámetros de tipo '{1}' tengan valores de Precision y Scale explícitamente establecidos. - - - Llamada de método final no coincide para asyncResult. Se esperaba una llamada a {0} pero se llamó a {1} en su lugar. - - - Operación no válida; se ha terminado la conexión. - - - Se ha deshabilitado la conexión. - - - No se puede dar de alta en la transacción porque hay una transacción local en curso durante la conexión. Finalice la transacción local e inténtelo de nuevo. - - - La conexión tiene actualmente dada de alta una transacción. Termine dicha transacción y vuelva a intentarlo. - - - La base de datos no puede ser nula, la cadena no puede estar vacía o no puede constar solamente de espacios en blanco. - - - El argumento es demasiado largo. - - - Valor de 'Connect Timeout' no válido, debe ser un número entero >= 0. - - - Desplazamiento del búfer de origen (tamaño de {0}) no válido: {1} - - - Desplazamiento del búfer de destino (tamaño de {0}) no válido: {1} - - - El valor numérico es demasiado grande para ajustarlo a un valor decimal de 96 bits. - - - Intento de {0} no válido cuando está cerrada la secuencia. - - - El valor SeekOrigin especificado no es válido. - - - La generación SQL dinámica no es compatible con varias tablas de base. - - - La generación SQL dinámica no es compatible con SelectCommand, que no devuelve ninguna información de tabla base. - - - La generación SQL dinámica para DeleteCommand no es compatible con SelectCommand, que no devuelve ninguna información sobre columnas clave. - - - La generación SQL dinámica para UpdateCommand no es compatible con SelectCommand, que no devuelve ninguna información sobre columnas clave. - - - La generación SQL dinámica para DeleteCommand no es compatible con SelectCommand que no contiene columna de versión de fila. - - - La generación Dynamic SQL para UpdateCommand no es compatible con SelectCommand que no contiene columna de versión de fila. - - - La generación SQL dinámica no es compatible con nombres de tablas '{0}' que contengan el carácter '{1}' de QuotePrefix o QuoteSuffix. - - - Intento no válido de leer el índice de columna '{0}'. Con CommandBehavior.SequentialAccess, solo se puede leer un índice de columna igual a '{1}' o mayor. - - - No se puede dar formato al tipo de datos '{0}' como literal porque tiene dígitos de fecha y hora no válidos. - - - No se puede dar formato al valor como literal del tipo solicitado. - - - No se puede dar formato al tipo de datos '{0}' como literal porque tiene una escala máxima no válida. - - - El valor literal proporcionado no es un literal válido para el tipo de datos '{0}'. - - - '{0}':La longitud del valor literal debe ser par. - - - '{0}':El valor literal debe ser una cadena con dígitos hexadecimales - - - {0} requiere una conexión abierta cuando no se ha establecido el prefijo de cita. - - - No se pueden determinar los valores literales boolean correctos. No se pueden crear los literales Boolean. - - - Los literales del tipo de datos nativo asociado al tipo de datos '{0}' no son compatibles. - - - El tipo de datos del parámetro {0} no es válido. - - - No hay ninguna asignación de tipo de objeto {0} a un tipo nativo de un proveedor administrado conocido. - - - No se puede tratar un TypeCode {0} desconocido devuelto por Type {1}. - - - No hay ninguna asignación de DbType {0} a ningún {1} conocido. - - - La versión de SQL Server utilizada no es compatible con el tipo de datos '{0}'. - - - El valor de parámetro '{0}' está fuera del intervalo. - - - El nombre de parámetro especificado '{0}' no es válido. - - - No se admiten varios parámetros de valores devueltos. - - - Valor del parámetro Size '{0}' no válido. El valor debe ser mayor o igual a 0. - - - Valor no válido para el argumento '{0}'. El valor debe ser igual o mayor que 0. - - - Valor no válido para este metadato. - - - Los metadatos deben ser SqlDbType.Row - - - Error al convertir el valor del parámetro de {0} a {1}. - - - {0} no admite transacciones paralelas. - - - {0} se completó; ya no se puede utilizar. - - - No se puede llamar a '{0}' cuando el registro es de solo lectura. - - - No se puede llamar a '{0}' cuando DbDataRecord es de solo lectura. - - - La conversión implícita del tipo de objeto '{0}' a tipo de dato '{1}' no es compatible. - - - El desplazamiento del búfer '{1}' más los bytes disponibles '{0}' es mayor que la longitud del búfer introducido. - - - La longitud de los datos '{0}' es menor que 0. - - - La longitud '{0}' especificada está fuera del intervalo. - - - Intento {2} no válido de dataIndex '{0}'. Con CommandBehavior.SequentialAccess, sólo puede leer de '{1}' dataIndex o mayores. - - - El desplazamiento debe hacer referencia a una ubicación dentro del valor. - - - "El proveedor de ODBC administrado requiere que se especifique la restricción TABLE_NAME y que no sea null para la colección de índices GetSchema. - - - Valor de argumento no válido para el método '{0}'. - - - El proveedor ODBC no devolvió resultados de SQLGETTYPEINFO. - - - No se esperaba un argumento de DataTable null - - - No se esperaba un argumento de DataSet null. - - - La cadena de conexión supera la longitud máxima permitida de {0}. - - - {0} - no se puede asignar el tipo. - - - Tipo SQL desconocido - {0}. - - - Tipo URT desconocido - {0}. - - - Argumento negativo no válido - - - No se puede establecer la propiedad en una conexión abierta. - - - Asignación no válida de SQL_TRANSACTION '{0}' a un valor de enumeración System.Data.IsolationLevel. - - - {0}, no se puede habilitar la agrupación de conexiones... - - - {0}, no se puede asignar un identificador de entorno. - - - {0} - no se puede obtener un identificador de descriptor. - - - No en una transacción - - - Valor de enumeración OdbcType no válido={0}. - - - Utilice IsDBNull cuando se espera DBNull.Value. - - - {0} [{1}] {2} - - - {0}, información de error no disponible - - - La conexión está cerrada. - - - Una conexión interna no tiene un propietario. - - - El proveedor de datos Odbc de .NET Framework requiere tener instalada la versión 2.6 o posterior de Microsoft Data Access Components(MDAC). Actualmente tiene instalada la versión {0}. - - - El proveedor de datos OleDb de .NET Framework requiere tener instalada la versión 2.6 o posterior de Microsoft Data Access Components(MDAC). Actualmente tiene instalada la versión {0}. - - - El proveedor '{1}' no admite la interfaz '{0}'. GetOleDbSchemaTable no está disponible con el proveedor actual. - - - Fallo de '{0}' con mensaje de error no disponible, código de resultado: {1}. - - - Mensaje de error no disponible, código de resultado: {0}. - - - Los proveedores de datos .NET Framework requieren la instalación de Microsoft Data Access Components (MDAC). Instale la version 2.6 o posterior de Microsoft Data Access Components (MDAC). - - - El proveedor de datos .NET Framework para OLEDB (Microsoft.Data.OleDb) no admite el proveedor OLE DB de Microsoft para controladores ODBC (MSDASQL). Use el proveedor de datos .NET Framework para ODBC (System.Data.Odbc). - - - El proveedor de datos .NET Framework para OLEDB no permitirá que el proveedor de OLE DB pregunte al usuario en un entorno no interactivo. - - - El proveedor '{0}' no está registrado en el equipo local. - - - El proveedor '{0}' no admite la interfaz ICommandText. Utilice CommandType.TableDirect en su lugar. - - - El proveedor '{0}' no admite la interfaz ITransactionLocal. Las transacciones locales no están disponibles con el proveedor actual. - - - El formato de la cadena de inicialización no cumple con las especificaciones OLE DB. Empieza alrededor del char [{0}] en la cadena de conexión. - - - El procesamiento asíncrono no es una característica admitida del proveedor de datos .NET Framework para OLE DB (Microsoft.Data.OleDb). - - - No se ha especificado un proveedor OLE DB en ConnectionString. Un ejemplo sería, 'Provider=SQLOLEDB;'. - - - El proveedor OLE DB especificado en ConnectionString es demasiado largo. - - - No se esperaban restricciones para DbInfoKeywords OleDbSchemaGuid. - - - No se esperaban restricciones para DbInfoLiterals OleDbSchemaGuid. - - - No se esperaban restricciones para el esquema guid OleDbSchemaGuid. - - - El proveedor '{1}' no admite el esquema OleDbSchemaGuid {0}. - - - La configuración '{0}' posee un número incorrecto de valores. - - - No se puede cargar el archivo XML especificado en la configuración '{0}'. - - - Parámetro de comando [{0}] '{1}' no válido. - - - No se puede convertir el valor de los datos del parámetro de comando [{0}] '{1}' por otras razones diferentes a la falta de correspondencia de signo o el desbordamiento de los datos. - - - Error de conversión del parámetro de comando [{0}] '{1}' porque el valor de los datos es de tipo signed y el tipo utilizado por el proveedor es de tipo unsigned. - - - Error de conversión del parámetro de comando [{0}] '{1}' porque el valor de datos ha desbordado el tipo utilizado por el proveedor. - - - El proveedor encontró un error al enviar el valor del parámetro de comando [{0}] '{1}' y ha detenido el procesamiento. - - - El parámetro [{0}] '{1}' no tiene un valor predeterminado. - - - Error del parámetro[{0}]: {1}. - - - Error interno de Microsoft.Data.OleDb.OleDbDataAdapter: descriptor de acceso de parámetro incorrecto: {0} {1}. - - - Parameter[{0}]: la propiedad OleDbType no está inicializada: OleDbType.{1}. - - - El proveedor '{0}' no admite con la interfaz ICommandWithParameters. Los parámetros de comando no son compatibles con el proveedor actual. - - - El proveedor '{0}' no admite la recuperación de información de parámetros de procedimientos. - - - No se puede determinar el separador decimal del servidor. No se pueden crear literales numéricos no enteros. - - - El objeto no es ADODB.RecordSet ni ADODB.Record. - - - No se puede recuperar la interfaz '{0}' del objeto ADODB.RecordSet. + + {1}[{0}]: la propiedad Size tiene un tamaño no válido de 0. - - No se puede recuperar la interfaz IRow del objeto ADODB.Record. + + El método {0}.Prepare requiere que todos los parámetros tengan un tipo establecido de forma explícita. - - Este tipo no es compatible con la interfaz OLE DB ISourcesRowset + + El método {0}.Prepare requiere que todos los parámetros de longitud variable tengan un valor distinto de cero explícitamente establecido para Size. - - No se puede crear la colección de esquemas ReservedWords porque el proveedor no es compatible con IDBInfo. + + El método {0}.Prepare requiere que los parámetros de tipo '{1}' tengan valores de Precision y Scale explícitamente establecidos. - - No se estableció el valor de la propiedad porque el proveedor no admite la propiedad '{0}' o el consumidor ha intentado obtener o establecer valores de propiedades que no pertenecen al grupo de propiedades de inicialización y el objeto de origen de datos no se ha inicializado. + + Llamada de método final no coincide para asyncResult. Se esperaba una llamada a {0} pero se llamó a {1} en su lugar. - - No se pudo inicializar la propiedad '{0}' debido a uno de los siguientes motivos: - El tipo de dato del valor no coincidía con el tipo de dato de la propiedad o no era null. Por ejemplo, la propiedad era DBPROP_MEMORYUSAGE, que tiene un tipo de dato Int32, y el tipo de dato era Int64. - El valor no era válido. Por ejemplo, la propiedad era DBPROP_MEMORYUSAGE y el valor era negativo. - El valor era válido para la propiedad y el proveedor es compatible con la misma como propiedad que se puede establecer, pero el proveedor no admite el valor especificado. Esto incluye el caso en el que se ha agregado el valor a la propiedad en OLE DB una vez escrito el proveedor. + + Operación no válida; se ha terminado la conexión. - - El valor de Options no es válido. + + No se puede dar de alta en la transacción porque hay una transacción local en curso durante la conexión. Finalice la transacción local e inténtelo de nuevo. - - El elemento ColumnID no es válido. + + La conexión tiene actualmente dada de alta una transacción. Termine dicha transacción y vuelva a intentarlo. - - Se ha especificado la aplicación de una propiedad '{0}' en todas las columnas, pero no se ha podido aplicar a una o varias de ellas. + + La base de datos no puede ser nula, la cadena no puede estar vacía o no puede constar solamente de espacios en blanco. - - La propiedad '{0}' es de solo lectura o el consumidor ha intentado establecer valores de propiedades en el grupo de propiedades de inicialización una vez inicializado el objeto de origen de datos. Los consumidores pueden establecer el valor de una propiedad de solo lectura en su valor actual. Este estado también se devuelve si no se puede establecer una propiedad de columna configurable para la columna en cuestión. + + Desplazamiento del búfer de origen (tamaño de {0}) no válido: {1} - - No se ha podido establecer el valor especificado para la propiedad '{0}' opcional. + + Desplazamiento del búfer de destino (tamaño de {0}) no válido: {1} - - No se ha establecido el valor de propiedad '{0}' porque entraría en conflicto con una propiedad existente. + + Intento de {0} no válido cuando está cerrada la secuencia. - - Reservado + + El valor SeekOrigin especificado no es válido. - - El proveedor ha devuelto un valor DBPROPSTATUS_ desconocido '{0}'. + + Intento no válido de leer el índice de columna '{0}'. Con CommandBehavior.SequentialAccess, solo se puede leer un índice de columna igual a '{1}' o mayor. - - Se ha aplazado la validación del descriptor de acceso mientras el método devuelve datos. No se puede enlazar esta columna o parámetro. + + El tipo de datos del parámetro {0} no es válido. - - Error interno de OleDbDataAdapter: descriptor de acceso del conjunto de filas no válido: Ordinal={0} Status={1}. + + No hay ninguna asignación de tipo de objeto {0} a un tipo nativo de un proveedor administrado conocido. - - No se puede convertir el valor de los datos por otras razones que no sean la falta de correspondencia de signo ni el desbordamiento de los datos. Por ejemplo, los datos están dañados en el almacén de datos aunque la fila se puede recuperar. + + No se puede tratar un TypeCode {0} desconocido devuelto por Type {1}. - - El proveedor no puede asignar memoria para devolver los datos {0}. + + No hay ninguna asignación de DbType {0} a ningún {1} conocido. - - Error de conversión, debido a que el valor de los datos {0} desbordó el tipo especificado para la parte de valor {0} en el búfer del consumidor. + + La versión de SQL Server utilizada no es compatible con el tipo de datos '{0}'. - - Error interno de OleDbDataAdapter: tipo de datos de OLE DB [get] desconocido: 0x{0} ({1}). + + El valor de parámetro '{0}' está fuera del intervalo. - - Error de conversión, debido a que el valor de los datos {0} se firmaron y el tipo especificado para la parte de valor {0} en el búfer del consumidor estaba sin firmar. + + El nombre de parámetro especificado '{0}' no es válido. - - Error interno de OleDbDataAdapter: [set] Tipo de datos OLE DB desconocido: 0x{0} ({1}). + + Valor del parámetro Size '{0}' no válido. El valor debe ser mayor o igual a 0. - - El proveedor no puede determinar el valor {0}. Por ejemplo, la fila se acaba de crear, el valor predeterminado para la columna {0} no está disponible y el consumidor todavía no ha establecido un nuevo valor {0}. + + Valor no válido para el argumento '{0}'. El valor debe ser igual o mayor que 0. - - El proveedor OLE DB devolvió un valor de estado inesperado de {0}. + + Valor no válido para este metadato. - - OleDbDataReader.Read se debe utilizar desde el mismo subproceso en el que se creó si ApartmentState del subproceso no es ApartmentState.MTA. + + Error al convertir el valor del parámetro de {0} a {1}. - - Error no especificado: {0} + + {0} no admite transacciones paralelas. - - Error de IErrorInfo.GetDescription con {0}. + + {0} se completó; ya no se puede utilizar. - - Error de IErrorInfo.GetSource con {0}. + + La longitud '{0}' especificada está fuera del intervalo. - - El proveedor de datos .NET Framework para OLE DB (Microsoft.Data.OleDb). no admite los datos de DBTYPE_VECTOR. + + Intento {2} no válido de dataIndex '{0}'. Con CommandBehavior.SequentialAccess, sólo puede leer de '{1}' dataIndex o mayores. Valor mínimo o máximo del tamaño de grupo no válido, el tamaño mínimo del grupo no puede ser mayor que el tamaño máximo del grupo. - - La palabra clave '{0}' está desusada. Use '{1}'. - No se puede obtener desde el servidor la dirección del coordinador de transacciones distribuidas. ¿DTC está habilitado en el servidor? - - La longitud del valor para el parámetro de conexión <{0}> supera el máximo permitido de 65535 caracteres. - 'Packet Size' no válido. El valor debe ser un entero >= 512 y <= 32768. Transacción o nombre no válido para un punto en el que se va a guardar la transacción. - - El valor de enumeración de {0}, {1}, no es compatible con SQL Server 7.0 o SQL Server 2000. - No se admite utilizar User Instance y Failover. Elija uno de los dos en la cadena de conexión. @@ -2499,33 +540,21 @@ No se puede establecer la propiedad Credential si se ha especificado "Authentication=Active Directory Integrated" en la cadena de conexión. - - Se especificaron parámetros de autenticación incorrectos con la autenticación de certificado. - La instancia de SQL Server con la que intenta conectar requiere cifrado, pero este equipo no lo admite. La instancia de SQL Server con la que intenta conectar no admite cifrado. - - La instancia de SQL Server con la que intenta conectar no admite CTAIP. - Versión no compatible de SQL Server. El proveedor de datos SqlClient de .NET Framework solo se puede utilizar con SQL Server versión 7.0 o posterior. No se puede cambiar {0} mientras la operación asíncrona esté en proceso. - - 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. - Error de instancia. @@ -2556,27 +585,18 @@ Código de error 0x{0} - - ChangePassword requiere SQL Server 9.0 o posterior. - Las instancias de usuario no locales no admiten la redirección SSE Instance. No se admite la autenticación "{0}". - - No se admite el método de autenticación SQL "{0}". - No se pudo crear una instancia de un proveedor de autenticación con el tipo "{1}" para "{0}". No se pudo crear una instancia de SqlAuthenticationInitializer con el tipo "{0}". - - El proveedor "{0}" generó una excepción al inicializar. - El proveedor "{0}" no admite la autenticación "{1}". @@ -2586,24 +606,15 @@ No se encuentra un proveedor de autenticación para ''{0}''. Instale el paquete NuGet "Microsoft.Data.SqlClient.Extensions.Azure" (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) para usar métodos de autenticación Active Directory (Id. de Entra). - - No se pudo leer la sección de configuración para los proveedores de autenticación. - El parámetro "{0}" no puede ser nulo ni estar vacío. - - La operación asincrónica ya se ha completado. - La ejecución del comando no puede continuar debido a que ya está en curso una operación asincrónica pendiente. Comando no válido enviado a ExecuteXmlReader. El comando debe devolver un resultado Xml. - - Las notificaciones requieren tener instalado SQL Server 9.0 o posterior. - Formato de nombre de parte 3 no válido para UdtTypeName. @@ -2697,9 +708,6 @@ No se puede inicializar el paquete SSPI. - - Valor de Timeout caducado. El período de tiempo de espera caducó antes de completar la operación o el servidor no responde. - Valor de tiempo de espera caducado. El periodo de tiempo de espera se agotó al comienzo de la fase previa al inicio de sesión. El motivo podría ser que el tiempo proporcionado para el tiempo de espera de la conexión no fuera suficiente. @@ -2745,12 +753,6 @@ Se solicitó una instancia de usuario en la cadena de conexión pero el servidor no admite esta opción. - - Error de validación de aplicación en el certificado de servidor. - - - La longitud de datos {0} especificada supera la longitud máxima permitida de {1}. - Intento de lectura no válido al no haber datos presentes. @@ -2775,9 +777,6 @@ La longitud de los datos '{0}' es menor que 0. - - No se puede llamar a '{0}' cuando SqlDataRecord es de solo lectura. - La colección asignada está en uso o no se puede tener acceso a la misma en este momento; @@ -2847,21 +846,6 @@ No se admite la conexión a la conexión de contexto mediante Microsoft.Data.SqlClient. - - No se admiten las TransactionScopes anidadas. - - - Evento de servidor inesperado: {0}. - - - La longitud de '{0}' debe coincidir con la longitud de '{1}'. - - - SqlDbType '{0}' no es válido para {1}. Sólo admite {2}. - - - Demasiados valores. - La Stream no admite escritura. @@ -2886,192 +870,36 @@ NULL - - Mensaje - - - Desbordamiento aritmético. - - - Error al dividir por cero. - - - Los datos tienen valor Null. No se puede llamar a este método o propiedad con valores Null. - - - La aritmética numérica provoca truncamientos. - Desbordamiento de SqlDateTime. Debe estar entre 1/1/1753 12:00:00 AM y 12/31/9999 11:59:59 PM. - - Las dos cadenas que se concatenan tienen una intercalación diferente. - - - Las dos cadenas que se comparan tienen una intercalación diferente. - - - Valor de marca no válido. - - - Desbordamiento de la conversión de SqlDecimal a Decimal. - Desbordamiento de conversión. - - Estructura de SqlDateTime no válida. - - - Se ha especificado una zona horaria. SqlDateTime no admite zonas horarias. - Tamaño de matriz no válido. Precisión o escala numérica no válida. - - La entrada no está en un formato correcto. - - - Error de SqlType. - - - SqlBytes y SqlChars no admiten longitudes superiores a los 2GB en esta versión. - Error al leer. - - Se ha cerrado o desechado Stream. - - - No hay búfer. Error al realizar la operación de lectura o escritura. - - - No se puede establecer una longitud distinta de cero porque el valor actual es Null. - - - Tamaño de búfer insuficiente. Error al realizar la operación de lectura o escritura. - - - No se puede escribir en un desplazamiento distinto de cero porque el valor actual es Null. - - - No se puede escribir desde un desplazamiento mayor que la longitud actual, ya que dejaría datos no inicializados en el búfer. - El tamaño de los datos devueltos supera los 2Gb. Utilice SequentialAccess command behavior para obtener todos los datos. - - El argumento de GetDayOfWeek debe ser un número entero entre 1 y 7. - - - No se ha cargado el Type SQL con datos. - - - El Type SQL ya se ha cargado con datos. - - - Intento no válido de obtener acceso a un XmlReader cerrado. - - - Intento no válido de llamar a {0} cuando la secuencia está cerrada. - - - Intento no válido de llamar a {0} cuando no se puede escribir en la secuencia. - - - Intento no válido de llamar a {0} cuando no se puede leer la secuencia. - - - Intento no válido de llamar a {0} cuando no se puede buscar en la secuencia. - - - La subclase no invalidó un método requerido. - No se puede crear un normalizador para '{0}'. Error interno - - El parámetro de comando debe tener un texto de comando que no sea null o no esté vacío. - - - La matriz del parámetro MetaData debe tener una longitud equivalente al argumento de la matriz ParameterDirection. - - - Error de asignación de adaptador de datos. - - - Error del adaptador de datos. - - - Infracción de simultaneidad de la base de datos. - Operación cancelada. Operación cancelada debido a una excepción (consulte InnerException para obtener detalles). - - Indica si se realiza Fill con una llamada a DataRow.AcceptChanges. - - - Indica si se realiza Update con una llamada a DataRow.AcceptChanges. - - - Si puede continuar o no al siguiente DataRow cuando los eventos Update, RowUpdating y RowUpdated y Status es UpdateStatus.ErrorsOccurred. - - - Cómo rellena el adaptador la tabla DataTable de DataReader. - - - Acción realizada cuando falta una tabla o una columna en TableMappings. - - - Acción realizada cuando falta una tabla o una columna en DataSet. - - - Cómo asignar la tabla de origen a la tabla de DataSet. - - - Evento que se desencadena cuando se produce un error recuperable durante Fill. - - - Debe Fill devolver valores específicos del proveedor o valores CLSCompliant comunes. - - - DataColumn.ColumnName - - - Nombre de la columna de origen- distinguir mayúsculas y minúsculas. - - - Número de elementos en la colección - - - Objeto DataColumnMapping especificado. - - - Asignaciones de columnas individuales cuando coincide esta asignación de tabla. - - - DataTable.TableName - - - El nombre de la tabla origen DataTableMapping. Este nombre distingue mayúsculas y minúsculas. - - - Número de elementos en la colección - - - Objeto especificado DataTableMapping - Se emplea durante Update para filas eliminadas en DataSet. @@ -3090,63 +918,9 @@ Evento que se desencadena después de cada DataRow durante Update. - - Número de filas que se deben procesar juntas por lotes antes de ejecutarlas en el origen de datos. - - - Conexión utilizada si Select/Insert/Update/DeleteCommands no tienen todavía una conexión. - - - Se emplea durante Update para las filas eliminadas en DataTable. - - - Se emplea durante Update para las nuevas filas de DataTable. - - - Utilizado durante Fill. - - - Se emplea durante Update para las filas modificadas en DataTable. - - - Debe Fill devolver valores específicos del proveedor o valores CLSCompliant comunes. - - - Cómo asignar la tabla de origen a DataTable. - - - Cómo se generan Insert/Update/DeleteCommands cuando no han sido establecidos por el usuario. - - - Número de filas que se deben procesar juntas por lotes antes de ejecutarlas en el origen de datos. - - - Cadena de conexión utilizada para conectar con el origen de datos. - - - Nombre del controlador ODBC que se debe utilizar al conectar con el Data Source. - - - DSN que se debe utilizar al conectar con el Data Source. - - - Cuando el valor es true, indica la agrupación de conexiones administradas que se debe utilizar. - - - Archivo UDL que se debe utilizar al conectar con el Data Source. - - - Especifica los OLE DB Services que se van a habilitar o deshabilitar con el OleDb Provider. - - - Nombre del proveedor OLE DB que se debe utilizar para conectar con el Data Source. - El nombre de la aplicación. - - Cuando el valor es true, se puede usar la funcionalidad asincrónica en el proveedor de datos .NET Framework. - Nombre del archivo principal, incluido el nombre completo de ruta, de una base de datos que se puede adjuntar. @@ -3156,9 +930,6 @@ Cuando el valor es true, indica que el estado de conexión se restablece cuando se elimina del grupo. - - Cuando es true, indica que la conexión debe ser desde el contexto de Sql Server. Disponible sólo cuando funciona en el proceso de Sql Server. - Nombre de registro de lenguaje de SQL Server. @@ -3252,54 +1023,9 @@ Especifica el método de autenticación con SQL Server. - - Certificado de cliente especificado para la autenticación con SQL Server. - - - Información utilizada para conectarse al origen de datos. - - - Valor actual de tiempo de espera de conexión, no se puede establecer en ConnectionString. - - - Valor del catálogo del origen de datos actual, 'Database=X' en la cadena de conexión. - - - Origen de datos actuales, 'Server=X' en la cadena de conexión. - - - Controlador ODBC actual - - - El controlador ODBC ha obtenido acceso a la versión del producto. - - - Información utilizada para conectarse al origen de datos. - - - Valor actual de tiempo de espera de conexión, 'Connect Timeout=X' en la cadena de conexión (ConnectionString). - - - Valor del catálogo del origen de datos actual, 'Initial Catalog=X' en la cadena de conexión. - - - Origen de datos actual, 'Data Source=X' en la cadena de conexión. - - - ProgID del proveedor OLE DB actual, 'Provider=X' en la cadena de conexión. - - - El proveedor OLE DB ha obtenido acceso a la versión del producto. - Token de acceso para usar en la autenticación. - - Estado de la conexión, sincrónica o asincrónica. 'Asynchronous Processing=x' en la cadena de conexión. - - - Información utilizada para conexión de replicación. - Información utilizada para conectar con DataSource, como 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'. @@ -3324,9 +1050,6 @@ Recopilar estadísticas de esta conexión. - - Proveedores de almacenamiento de claves de cifrado de columnas personalizadas. - Un GUID para representar la conexión física. @@ -3363,99 +1086,9 @@ Alta automática en notificaciones utilizada por Microsoft SQL Server. - - Cómo los comandos Update y Delete generan automáticamente la cláusula where cuando el usuario no lo especifica. - - - Indica la posición del nombre de catálogo en un nombre de tabla completo de un comando de texto. - - - Carácter que separa el nombre de catálogo del resto del identificador en un comando de texto. - - - Carácter que separa el nombre de esquema del resto del identificador en un comando de texto. - - - Cadena de prefijo que se ajusta alrededor de los objetos sql. - - - Cadena de sufijo que se ajusta alrededor de los objetos sql. - - - DataAdapter para el que generar automáticamente comandos. - - - Utilice el esquema de DataTable o SelectCommand. - - - Cómo se genera automáticamente la cláusula set para el comando Update cuando no ha sido especificada por el usuario. - - - DataAdapter para generar automáticamente OdbcCommands - - - El carácter utilizado en un comando de texto, como la comilla de apertura para los identificadores de comillas que contienen caracteres especiales. - - - El carácter utilizado en un comando de texto, como la comilla de cierre para los identificadores de comillas que contienen caracteres especiales. - - - DataAdapter para generar automáticamente OleDbCommands - - - Separador decimal utilizado en literales numéricos. - - - Cadena de prefijo que se ajusta alrededor de los objetos sql - - - Cadena de sufijo que se ajusta alrededor de los objetos sql - DataAdapter para generar automáticamente SqlCommands - - Separador decimal utilizado en literales numéricos. - - - El carácter utilizado en un comando de texto, como la comilla de apertura para los identificadores de comillas que contienen caracteres especiales. - - - El carácter utilizado en un comando de texto, como la comilla de cierre para los identificadores de comillas que contienen caracteres especiales. - - - Sólo es necesario establecerlo para parámetros numéricos y decimales al utilizar escenarios con Prepare, FillSchema y CommandBuilder. - - - Sólo es necesario establecerlo para parámetros numéricos y decimales al utilizar escenarios con Prepare, FillSchema y CommandBuilder. - - - Parámetro de tipo nativo. - - - Parámetro de tipo nativo. - - - Nombre del parámetro, formato '@p1' - - - Parámetro de tipo nativo. - - - Nombre del servidor para el tipo. - - - Desplazamiento en tipos de datos de longitud variable. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - No se admite la ParameterDirection '{0}' especificada para el parámetro '{1}'. Table-valued parameters sólo admiten ParameterDirection.Input. @@ -3468,9 +1101,6 @@ Se especificó TypeName para el parámetro '{0}'. Solo se debe especificar TypeName para los parámetros Structured. - - El tipo SqlDbType.Structured sólo es compatible con varios tipos con valores. - La columna DateType para el campo '{0}' en la tabla de esquema es null. DataType debe ser no null. @@ -3483,18 +1113,9 @@ El número de campos del registro '{0}' no coincide con el número del registro original. - - intervalo: 0-8000 - error inesperado en el proveedor de datos SqlClient. {0} - - No se puede obtener Type Info para {0},{1} - - - El proveedor no puede cargar el siguiente ensamblado: {0} - Debe establecerse la propiedad UdtTypeName para los parámetros UDT. @@ -3504,72 +1125,9 @@ El tipo especificado no está registrado en el servidor de destino.{0}. - - No se permiten parámetros UDT en la cláusula where, salvo que formen parte de la clave principal. - - - '{0}' es un tipo no válido definido por el usuario, motivo: {1}. - - - compatible con formatos en memoria y definidos por el usuario - - - No se puede admitir el formato nativo. - - - no implementa IBinarySerialize - - - no serializable - - - sin constructores públicos - - - no implementa INullable - - - no tiene un constructor público - sin atributo UDT - - La serialización sin asignación no se admite aún. - - - falta el método 'public static x Parse(System.Data.SqlTypes.SqlString)' - - - falta el método 'public override string ToString()' - - - falta el método 'public static x Null { get; }' - - - El formato nativo no es compatible con los campos del tipo '{0}' (directamente o a través de otro campo) - - - El tipo no es público - - - Native UDT sin diseño secuencial debido al tipo '{0}' - - - Native UDT especifica un tamaño máximo de byte - - - el campo '{0}' está marcado como no serializado - - - El tipo de campo '{0}' está marcado como diseño explícito que no se permite en formato nativo - - - Las referencias de ensamblado con varios valores deben tener un identificador de ensamblado distinto de cero. - - - Los tipos estructurados con varios valores sólo se pueden utilizar para parámetros y no se pueden anidar dentro de otro tipo. - El nombre de la ruta de acceso no es válido. @@ -3585,9 +1143,6 @@ Error al intentar promover la transacción. - - Objeto dependiente utilizado para recibir notificaciones de consulta. - Propiedad para indicar si esta dependencia no es válida. @@ -3600,9 +1155,6 @@ Para agregar un comando a un objeto dependiente existente. - - El Command ya está asociado a otro objeto dependiente. No se puede sobrescribir. - Este objeto SqlCommand ya está asociado a otro objeto SqlDependency. @@ -3630,12 +1182,6 @@ Timeout especificado no válido. El Timeout no puede ser < 0. - - Error de notificación. Tipo={0}, Info={1}, Origen={2}. - - - GetMetaData no es válido para este SqlDbType. - El dbType {0} no es válido para este constructor. @@ -4104,9 +1650,6 @@ Error interno. El valor de texto no cifrado no puede ser nulo. - - No se puede cifrar. El cifrado dio como resultado {0} bytes de texto cifrado, lo que supera el límite máximo permitido de {1} bytes. Probablemente, el valor de texto sin cifrar especificado es demasiado grande (tamaño de texto sin cifrar: {2} bytes). - Error interno. El valor del texto cifrado no puede ser nulo. @@ -4224,9 +1767,6 @@ SQL Server no devolvió información de atestación. El tipo de enclave es "{0}" y la dirección URL de la atestación de enclave es "{1}". - - No se puede establecer un canal seguro. Mensaje de error: {0} - Error interno. Se especificó un argumento nulo "{0}" al construir un objeto de tipo "{1}". "{0}" no puede ser nulo. @@ -4242,15 +1782,6 @@ Especifica un punto de conexión de un servicio de atestación de enclave, que se utilizará para comprobar si el enclave, configurado en la instancia de SQL Server para cálculos en columnas de base de datos que se cifran mediante Always Encrypted, es válido y seguro. - - Error al leer la sección de configuración para los proveedores de enclave. Asegúrese de que la sección tiene el formato correcto en el archivo de configuración de la aplicación. Mensaje de error: {0} - - - No se pudo crear una instancia de un proveedor de enclave con el tipo "{1}" para el nombre "{0}". Mensaje de error: {2} - - - Error interno. SqlColumnEncryptionEnclaveProviderName no puede ser nulo ni estar vacío. - Se ha producido un error al leer el conjunto de resultados "{0}". No se ha especificado la dirección URL de atestación en la cadena de conexión, pero la consulta requiere cálculos de enclave. El tipo de enclave es "{1}". @@ -4260,9 +1791,6 @@ Error interno. El tipo de enclave recibido de SQL Server es nulo o está vacío al ejecutar una consulta que requiere cálculos de enclave. - - La ejecución de una consulta requiere cálculos de enclave, pero falta la sección de proveedor de enclave en la configuración de la aplicación. - No se ha encontrado ningún proveedor de enclave para el tipo de enclave "{0}" y el protocolo de atestación "{1}". Especifique el protocolo de atestación correcto en la cadena de conexión. @@ -4320,9 +1848,6 @@ Valor de cifrado de columnas predeterminado para todos los comandos de la conexión. - - Fuerza el cifrado del parámetro antes de enviar información confidencial al servidor. - El objeto de diccionario con nombres de SQL Server y sus rutas de clave maestras de columna de confianza. @@ -4332,9 +1857,6 @@ No se admite la conexión a una instancia de SQL Server reflejada mediante la opción de conexión ApplicationIntent ReadOnly. - - Se ha recibido información de enrutamiento inesperada. - Se recibió información de enrutamiento no válida. @@ -4485,9 +2007,6 @@ Se ha especificado una autenticación no admitida en este contexto: {0} - - Se ha agotado el tiempo de espera de la autenticación interactiva de Active Directory. El usuario tardó demasiado en responder a la solicitud de autenticación. - No se puede usar "Authentication=Active Directory Interactive" si se ha establecido la propiedad Credential. @@ -4497,9 +2016,6 @@ Id. de proceso de servidor (SPID) de la conexión activa. - - Se ha agotado el tiempo de espera de la autenticación del flujo de código de dispositivo de Active Directory. El usuario tardó demasiado en responder a la solicitud de autenticación. - No se puede establecer la propiedad Credential si se ha especificado "Authentication=Active Directory Device Code Flow" en la cadena de conexión. @@ -4533,9 +2049,6 @@ Solo se admiten matrices de una sola dimensión para la acción solicitada. - - No se puede quitar el elemento especificado porque no se puede encontrar en la colección especificada. - Número no negativo requerido. @@ -4563,87 +2076,36 @@ No se puede establecer la propiedad AccessTokenCallback si se ha especificado 'Authentication=Active Directory Default' en la cadena de conexión. - - {0} devuelto {1}. - - - Error de protocolo: un mensaje recibido contiene una signatura válida pero no se cifró como requería el nivel de protección efectivo. - - - Error de {0}: {1} - Esta clase no implementa este método. - - No se admite el paquete de seguridad solicitado. - Tipo physicalConnection incorrecto. Excepción al intentar establecer el modificador de AppContext '{0}'={1}. - - No se admite el tipo {0} en esta plataforma. - No se admite el modo invariable de globalización. - - No se puede autenticar mediante Kerberos. Asegúrese de que Kerberos se ha inicializado en el cliente con "kinit" y de que se ha registrado un nombre de entidad de seguridad de servicio para el SQL Server para permitir la autenticación Kerberos. - La palabra clave 'Network Library' no se admite en esta plataforma, anteponer el protocolo deseado al 'Origen de datos' ('tcp:' para una conexión TCP o 'np:' para una conexión de canalización con nombre). El socket no produjo el esperado '{0}' con el código de error '{1}'. - - El servidor está intentando usar una característica que no se admite en esta plataforma. - La palabra clave '{0}' no se admite en esta plataforma. El archivo System.Transactions.dll cargado actualmente no admite transacciones globales. - - Se recibió un token no admitido '{0}' al leer los datos del servidor. - - - '{0}' no es un tipo de controlador compatible. - SqlFileStream no se admite en esta plataforma. LocalDB no se admite en esta plataforma. - - Espacio de búfer insuficiente. Requerido: {0} Real: {1}. - - - Error en la operación de GSSAPI con el estado: {0} (Estado secundario: {1}). - - - Error en la operación GSSAPI: {0} ({1}). - - - No se admite el enlace de canal en sistemas operativos distintos de Windows. - - - El nombre de destino no debe estar vacío si se pasan las credenciales predeterminadas. - - - El nivel de protección solicitado no es compatible con la implementación de GSSAPI instalada actualmente. - - - No se admite la implementación del servidor. - - - La autenticación NTLM no es posible con las credenciales predeterminadas en esta plataforma. - Cuando lo utiliza DataAdapter.Update, el valor del parámetro cambia de DBNull.Value a (Int32)1 o (Int32)0 si no es nulo. @@ -4668,9 +2130,6 @@ 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. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx index c943aeb1fb..f175dbf816 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Croissant - Index {0} non valide pour ce {1} avec Count={2}. @@ -132,18 +129,12 @@ L'élément {0} est déjà contenu dans un autre {1}. - - L'élément {0} est déjà contenu dans ce {1}. - {0} accepte uniquement des objets de type {1} non null. Tentative de suppression d'un {0} qui n'est pas contenu dans ce {1}. - - {0}.{1} doit être unique, '{2}' existe déjà dans la collection. - La connexion n'est pas fermée. {0} @@ -171,12 +162,6 @@ Tentative d'appel de {0} non valide lorsque le lecteur est fermé. - - Inscription dans la transaction impossible car la connexion est la connexion principale d'une transaction déléguée ou promue. - - - Décroissant - Chaîne non vide attendue pour le paramètre '{0}'. @@ -204,9 +189,6 @@ La propriété ConnectionString n'a pas été initialisée. - - Exception non-CLS interceptée. - L'élément de sécurité donné n'est pas un élément d'autorisation. @@ -252,9 +234,6 @@ Données - - StateChange - Mise à jour @@ -264,48 +243,12 @@ Le ConnectionState indiquant si la connexion est ouverte ou fermée. - - Événement déclenché lorsque l'état de la connexion change. - - - Le type generic du paramètre. - - - Paramètre d'entrée, de sortie ou bidirectionnel. - - - une propriété au moment du design utilisée pour la génération de code fortement typé. - - - Offset dans les types de données de longueur variable. - - - Nom du paramètre. - - - Taille des types de données de longueur variable (chaîne & tableaux). - - - Lorsqu'il est utilisé par un DataAdapter.Update, correspond au nom de la colonne source utilisé pour rechercher le nom DataSetColumn dans ColumnMappings. Sert à copier une valeur entre le paramètre et une ligne de données. - - - Lorsqu'elle est utilisée par un DataAdapter.Update (uniquement UpdateCommand), correspond à la version de la valeur DataRow qui est utilisée pour mettre à jour la source de données. - - - Lorsqu'elle est utilisée par DataAdapter.Update, la valeur de paramètre DBNull.Value est remplacée par (Int32)1 ou par (Int32)0 si elle n'est pas null. - - - Valeur du paramètre. - Impossible de créer la collection '{0}' car l'exécution de la requête SQL a échoué. Pour plus de détails, consultez l'exception interne. Un nombre de restrictions supérieur au nombre de restrictions pris en charge par le schéma demandé ('{0}') a été fourni. - - '{2}' n'est pas une valeur valide pour la restriction '{1}' de la collection de schémas '{0}'. - La collection demandée ({0}) n'est pas définie. @@ -315,15 +258,6 @@ La collection demandée ({0}) n'est pas prise en charge par cette version du fournisseur. - - L'une des tables DataSourceInformation requises est manquante. - - - La table DataSourceInformation ne doit contenir qu'une seule ligne. - - - Une ou plusieurs des colonnes requises de la collection de restrictions sont manquantes. - Il existe une restriction sans ligne correspondante dans la collection de restrictions. @@ -342,42 +276,21 @@ La collection '{0}' est manquante dans le XML de métadonnées. - - Le XML de métadonnées n'est pas valide. - - - Le XML de métadonnées n'est pas valide. La collection {0} doit comporter une colonne {1}. Il doit s'agir d'une colonne de chaîne. - Le XML de métadonnées n'est pas valide. La colonne {1} de la collection {0} doit comporter une chaîne non vide. - - Action - - - Comportement - Remplissage InfoMessage - - Mappage - StatementCompleted - - UDT - Notification - - Schéma - XML @@ -393,9 +306,6 @@ Regroupement - - ConnectionString nommé - Sécurité @@ -408,2073 +318,204 @@ Résilience de la connexion - - La collection qui contient les informations utilisateur personnalisées. - - - Indique si la comparaison de chaînes dans le DataSet respecte la casse. - - - Le nom de ce DataSet. - - - Indique une "vue" personnalisée des données contenues dans le DataSet. Cette vue permet le filtrage, la recherche et la navigation dans la vue de données personnalisée. - - - Indique si les règles de contrainte doivent être suivies. - - - Indique que le DataSet contient des erreurs. - - - Indique des paramètres régionaux avec lesquels comparer des chaînes dans le DataSet. - - - Indique l'espace de noms URI XML pour l'élément racine vers lequel pointe ce DataSet. - - - Indique le préfixe de l'espace de noms utilisé pour ce DataSet. - - - Collection qui contient les relations pour ce DataSet. - - - La collection qui contient les tables pour ce DataSet. - - - Se produit lorsqu'il n'est pas possible de fusionner des schémas pour deux tables avec le même nom. - - - Intervient après la fin de l'Initialization. - - - Représente un cache de données en mémoire. - - - Indique si les chaînes de comparaison contenues dans la table respectent la casse. - - - Retourne les relations enfants pour cette table. - - - La collection qui contient les colonnes pour cette table. - - - La collection qui contient les contraintes pour cette table. - - - Indique le DataSet auquel appartient cette table. - - - DataView par défaut pour la table. - - - L'expression utilisée pour calculer la valeur liée aux données de cette ligne. - - - Indique si la table contient des erreurs. - - - Indique des paramètres régionaux avec lesquels comparer des chaînes dans la table. - - - Indique une taille de début initiale pour cette table. - - - Indique l'espace de noms URI XML pour les éléments contenus dans cette table. - - - Indique le préfixe de l'espace de noms utilisé pour cette table dans la représentation XML. - - - Retourne les relations parentes pour cette table. - - - Indique les colonnes qui représentent la clé primaire pour cette table. - - - Indique la collection qui contient les lignes de données pour cette table. - - - Indique le nom utilisé pour rechercher dans cette table dans la collection Tables d'un DataSet. - - - Se produit lorsqu'une ligne de la table a été modifiée avec succès. - - - Se produit lorsque la ligne est en cours de modification afin que le gestionnaire d'événements puisse modifier ou annuler la modification. L'utilisateur peut modifier les valeurs dans la ligne et doit lever une exception pour annuler la modification. - - - Se produit une fois qu'une ligne de la table a été supprimée avec succès. - - - Se produit lorsqu'une ligne de la table est marquée pour suppression. Levez une exception pour annuler la suppression. - - - Se produit lorsqu'une valeur a été envoyée pour cette colonne. L'utilisateur peut modifier la valeur proposée et doit lever une exception pour annuler la modification. - - - Se produit lorsqu'une valeur a été modifiée pour cette colonne. - - - Se produit avant que toutes les lignes de la table aient été effacées. - - - Se produit lorsque toutes les lignes de la table ont été effacées avec succès. - - - Se produit lorsqu'un nouveau DataRow a été instancié. - - - Le nom utilisé pour rechercher cette relation dans la collection Relations d'un DataSet. - - - Indique les colonnes enfants de cette relation. - - - Indique les colonnes parentes de cette relation. - - - Indique si les relations sont imbriquées. - - - Pour les suppressions, indique le type de cascade qui doit avoir lieu dans cette relation. - - - Pour les mises à jour, indique le type de cascade qui doit avoir lieu dans cette relation. - - - Pour l'acceptation ou le refus des modifications, indique le type de cascade qui doit avoir lieu dans cette relation. - - - Indique les colonnes enfants de cette contrainte. - - - Indique les colonnes parentes de cette contrainte. - - - Indique la table enfant de cette contrainte. - - - Indique les colonnes de cette contrainte. - - - Indique si cette contrainte est une clé primaire. - - - Indique le nom de cette contrainte. - - - Indique la table de cette contrainte. - - - Indique si des valeurs null sont autorisées dans cette colonne. - - - Indique si la colonne s'incrémente automatiquement pour les lignes ajoutées à la table. Le type de cette colonne doit être Int16, Int32 ou Int64. - - - Indique la valeur de départ pour une colonne AutoIncrement. - - - Indique l'incrément utilisé par une colonne AutoIncrement. - - - Indique la légende interface utilisateur par défaut pour cette colonne. - - - Indique le nom utilisé pour rechercher cette colonne dans la collection Columns d'un DataTable. - - - Retourne le DataTable auquel appartient cette colonne. - - - Indique le type de données stockées dans cette colonne. - - - Indique la valeur de la colonne par défaut utilisée lors de l'ajout de lignes à la table. - - - Indique la valeur calculée par cette colonne pour chaque ligne à partir d'autres colonnes plutôt qu'à partir de l'entrée de l'utilisateur. - - - Indique comment cette colonne persiste en XML : en tant qu'attribut, élément, noeud de contenu simple ou rien. - - - Indique l'URI XML pour les éléments ou les attributs stockés dans cette colonne. - - - Indique le Prefix utilisé pour ce DataColumn en représentation xml. - - - Indique l'index de cette colonne dans la collection Columns. - - - Indique si cette colonne peut être modifiée une fois qu'une ligne a été ajoutée à la table. - - - Indique si cette colonne doit limiter ses valeurs dans les lignes de la table afin d'être unique. - - - Indique la longueur maximale de la valeur autorisée dans cette colonne. - - - Indique le DateTimeMode de cette propriété DataColumn. - - - Indique si ce DataView et l'interface utilisateur associée permettent les suppressions. - - - Indique si ce DataView et l'interface utilisateur associée permettent les modifications. - - - Indique si ce DataView et l'interface utilisateur associée permettent l'ajout de nouvelles lignes. - - - Retourne le nombre d'éléments actuellement dans cette vue. - - - Un pointeur est retourné au DataViewManager qui possède ce DataSet (éventuel). - - - Indique si la vue est ouverte. - - - Indique une expression utilisée pour filtrer les données retournées par ce DataView. - - - Indique les versions des données retournées par ce DataView. - - - Indique les noms de la colonne et l'ordre dans lequel les données sont retournées par ce DataView. - - - Indique si l'ordre de tri par défaut doit être utilisé lorsque la propriété Sort n'est pas définie. - - - Indique la table que ce DataView utilise pour obtenir les données. - - - Indique que les données retournées par ce DataView ont été modifiées. - - - Indique la source des données pour ce DataViewManager. - - - Indique les paramètres de tri/filtre/état pour une table dans le DataSet correspondant. - - - DataSet ne prend pas en charge 'union' ou 'list' en tant que simpleType. - - - Syntaxe {0} non valide : attribut requis '{1}' manquant. - - - La valeur '{1}' n'est pas valide pour l'attribut '{0}'. - - - La valeur de l'attribut '{0}' doit être '{1}' ou '{2}'. - - - Impossible de trouver ElementType name='{0}'. - - - Le nom de la table parente est absent de la relation '{0}'. - - - Le nom de la table enfant est absent de la relation '{0}'. - - - La clé de la table parente est absente de la relation '{0}'. - - - La clé de la table enfant est absente de la relation '{0}'. - - - Type de données non défini : '{0}'. - - - Type de données non défini. - - - Sélection XPath non valide dans le noeud de champ. Impossible de trouver : {0}. - - - Sélection XPath non valide à l'intérieur du noeud selector : {0}. - - - Noeud 'Key' non valide dans la contrainte nommée : {0}. - - - Le nom de la contrainte {0} est déjà utilisé dans le schéma. - - - Impossible de convertir '{0}' en type '{1}'. - - - Partie '{0}' manquante dans la contrainte '{1}' nommée '{2}'. - - - Définition de Relation non valide : clés de longueur différente. - - - DataSet n'autorise pas la référence circulaire dans le ComplexType nommé '{0}'. - - - DataSet ne peut pas instancier un ComplexType abstrait pour le noeud {0}. - - - Une erreur s'est produite au niveau du convertisseur multi-cibles lors de l'écriture d'un Schema Xml. Pour plus de détails, reportez-vous à l'exception interne. - - - Une erreur s'est produite au niveau du convertisseur multi-cibles lors de l'écriture d'un Schema Xml. Une chaîne null ou vide a été renvoyée. - - - Déclaration '{0}' dupliquée. - - - Impossible de charger diffGram. La table '{0}' est absente du groupe de données de destination. - - - Impossible de charger diffGram. Le noeud 'sql' est absent. - - - Le nom de la colonne '{0}' est défini pour différents types de mappages. - - - Le préfixe '{0}' n'est pas valide, car il contient des caractères spéciaux. - - - Référence circulaire dans une table à imbrication automatique '{0}'. - - - DataSet ne peut pas développer d'entités. Utilisez XmlValidatingReader et définissez la propriété EntityHandling en conséquence. - - - Le type '{0}' n'implémente pas l'interface IXmlSerializable. Par conséquent, la sérialisation ne peut pas se poursuivre. - - - Impossible de poursuivre la désérialisation. Les données n'implémentent pas IXMLSerializable. Par conséquent, le polymorphisme n'est pas pris en charge. - - - DataTable ne prend pas en charge l'inférence de schéma de Xml. - - - Impossible de poursuivre la sérialisation du DataTable '{0}'. Il contient un DataRow doté de plusieurs lignes parent au niveau de la même clé étrangère. - - - L'attribut IsDataSet est manquant dans le Schema d'entrée. - - - Désolé... Nous ne pouvons pas déterminer l’élément DataSet. L’attribut IsDataSet existe plusieurs fois. - - - DataSet ne sérialise pas les types qui implémentent IDynamicMetaObjectProvider mais n'implémentent pas IXmlSerializable. - - - La fonctionnalité n'est pas implémentée. {0}. - - - Erreur de syntaxe : opérande manquant après l'opérateur '{0}'. - - - Incompatibilité de type dans l'expression '{0}'. - - - Expression trop complexe. - - - Impossible de trouver la colonne [{0}]. - - - L'expression contient une constante de chaîne non valide : {0}. - - - L'expression contient un appel de fonction non défini {0}(). - - - Erreur de syntaxe dans l'expression. - - - Nombre d'arguments non valide : fonction {0}(). - - - La parenthèse fermante est absente de l'expression. - - - Impossible d'interpréter le jeton '{0}' à la position {1}. - - - {0} attendu, mais jeton à la position {2} est {1}. - - - Désolé... Nous ne pouvons pas convertir {0} en {1}. - - - Désolé... Nous ne pouvons pas convertir une valeur « {0} » en un type « {1} ». - - - Nom de colonne [{0}] non valide. - - - L'expression contient une constante de date non valide '{0}'. - - - Seules les expressions de constante sont autorisées dans la liste des expressions pour l'opérateur IN. - - - Erreur dans l'opérateur Like : le modèle de chaîne '{0}' n'est pas valide. - - - Erreur de syntaxe : les éléments qui suivent le mot clé IN doivent être séparés par des virgules et être entre parenthèses. - - - Incompatibilité de type dans l'argument de fonction : {0}(), argument {1}, {2} attendu. - - - Incompatibilité de type dans l'argument de fonction : {0}(), argument {1}, l'un des types Integer attendu. - - - Impossible d'effectuer une opération '{0}' sur {1} et {2}. - - - L'opérateur '{0}' est ambigu sur les opérandes de type '{1}' et '{2}'. Impossible de mélanger des types signés et non signés. Utilisez une fonction Convert() explicite. - - - Erreur de syntaxe : le mot clé IN doit être suivi d'une liste d'expressions non vide séparées par des virgules et entre parenthèses. - - - L'expression contient un opérateur '{0}' non pris en charge. - - - L'expression contient un nom non valide : '{0}'. - - - Erreur de syntaxe : opérande manquant avant l'opérateur '{0}'. - - - Parenthèses fermantes trop nombreuses dans l'expression. - - - La table [{0}] est incluse dans plusieurs relations. Vous devez mentionner explicitement un nom de relation dans l'expression '{1}'. - - - Erreur de syntaxe dans l'argument d'agrégation : argument de colonne unique attendu avec un possible qualificateur 'Child'. - - - Référence indépendante dans l'expression d'agrégation '{0}'. - - - Impossible d'évaluer une expression autre que de constante sans la ligne actuelle. - - - Référence indépendante dans l'expression '{0}'. - - - Impossible de calculer. L'expression '{0}' n'est pas un agrégat. - - - L'expression de filtre « {0} » ne peut pas correspondre à un terme booléen. - - - Nom de type '{0}' non valide. - - - Erreur de syntaxe dans l'expression de recherche : mot clé 'Parent' attendu suivi d'un argument de colonne avec un qualificateur de relation si nécessaire : Parent[(<nom_relation>)].<nom_colonne>. - - - Ligne ou table requise pour appeler DataFilter. - - - Argument {0}() hors limites. - - - Erreur de syntaxe : utilisation non valide de l'opérateur 'Is'. La syntaxe correcte est : <expression> Is [Not] Null. + + Offset et length étaient hors limites pour ce tableau ou bien le nombre est supérieur au nombre d'éléments de l'index à la fin de la collection source. - - La valeur est trop grande ou trop petite pour le type '{0}'. + + Impossible de convertir l'objet de type « {0} » en objet de type « {1} ». - - Erreur : division par zéro. + + La seule valeur acceptable pour la propriété '{0}' est '{1}'. - - Relation parent '{0}' introuvable. + + Les valeurs acceptables de la propriété '{0}' sont '{1}' ou '{2}'. - - Argument 'hours' hors limites. Spécifiez une valeur comprise entre -14 et +14. + + Les valeurs spécifiées de QuotePrefix et de QuoteSuffix ne correspondent pas. - - Argument 'minutes' hors limites. Spécifiez une valeur comprise entre -59 et +59. + + La longueur de l’argument «{0}» dépasse sa limite de «{1}». - - La plage indiquée pour l'heure dépasse le total de 14 heures. + + Valeur non valide pour la clé « {0} ». - - La propriété Kind de l'argument DateTime fourni ne correspond pas aux arguments 'hours' et 'minutes'. + + L'utilisation de la clé '{0}' nécessite la présence de la clé '{1}'. - - Le DataColumn de type '{0}' ne prend pas en charge l'expression. + + La longueur de la valeur pour la clé «{0}» dépasse sa limite de «{1}». - - Impossible d'activer les contraintes. Une ou plusieurs lignes contiennent des valeurs qui violent les contraintes de type non null, unique ou de clé externe. + + Mot clé non pris en charge : « {0} ». - - La collection elle-même n'est pas modifiable. + + Erreur interne du fournisseur de données .NET Framework {0}. - - Le nom '{0}' spécifié correspond au moins à deux noms de l'objet Collection avec une casse différente, mais ne correspond à aucun d'eux avec une casse identique. + + {0} « {1} ». - - Le nom spécifié '{0}' correspond au moins à deux noms de l'objet Collection, avec des espaces de noms différents. + + {0} "{1}", utilisation incorrecte de guillemets. - - Offset et length étaient hors limites pour ce tableau ou bien le nombre est supérieur au nombre d'éléments de l'index à la fin de la collection source. + + {0} "{1}", la limite actuelle de "{2}" est insuffisante. - - Argument '{0}' hors limites. + + Échec de SqlBulkCopy.WriteToServer car SqlBulkCopy.DestinationTableName est un nom en plusieurs parties non valide - - L'argument '{0}' ne peut pas être null. + + Échec du traitement des résultats provenant de SQL Server à cause d'un nom en plusieurs parties non valide - - L'argument '{0}' contient la valeur null. + + SqlParameter.UdtTypeName est un nom en plusieurs parties non valide - - Impossible de trouver la colonne {0}. + + SqlParameter.TypeName est un nom en plusieurs parties non valide - - La colonne '{0}' appartient déjà à ce DataTable. + + Échec de SqlCommand.DeriveParameters car la valeur de la propriété SqlCommand.CommandText est un nom en plusieurs parties non valide - - La colonne '{0}' appartient déjà à un autre DataTable. + + La connexion à une instance de SQL Server mise en miroir à l'aide de l'option de connexion MultiSubnetFailover n'est pas prise en charge. - - Impossible d'avoir plus d'une colonne SimpleContent dans un DataTable. + + La valeur d'énumération {0}, {1}, n'est pas prise en charge par la méthode {2}. - - Impossible d'ajouter une colonne SimpleContent à une table contenant des colonnes d'éléments ou des relations imbriquées. + + La valeur d'énumération {0}, {1}, n'est pas prise en charge par le fournisseur de données .NET Framework SqlClient. - - Une colonne nommée '{0}' appartient déjà à ce DataTable. + + {0} : la propriété CommandText n'a pas été initialisée - - Impossible d'ajouter une colonne nommée '{0}' : un tableau imbriqué avec le même nom appartient déjà à ce DataTable. + + {0} : la propriété Connection n'a pas été initialisée. - - Une colonne nommée '{0}' appartient déjà à ce DataTable : impossible de définir un nom de tableau imbriqué avec ce même nom. + + {0} nécessite une Connection ouverte et disponible. {1} - - Impossible de supprimer une colonne qui n'appartient pas à cette table. + + La procédure stockée '{0}' n'existe pas. - - Impossible de supprimer cette colonne, car elle fait partie de la clé primaire. + + La transaction n'est pas associée à la connexion en cours ou s'est terminée. - - Impossible de supprimer cette colonne, car elle fait partie de la clé parente pour la relation {0}. + + La transaction associée à la connexion active est terminée mais n'a pas été supprimée. Pour pouvoir utiliser la transaction pour exécuter des instructions SQL vous devez la supprimer. - - Impossible de supprimer cette colonne, car elle fait partie de la contrainte {0} dans la table {1}. + + {0} implique que la commande comporte une transaction lorsque la connexion attribuée à la commande se trouve dans une transaction locale en attente. La propriété Transaction de la commande n'a pas été initialisée. - - Impossible de supprimer cette colonne, car elle fait partie d'une expression : {0} = {1}. + + Un DataReader ouvert est déjà associé à {0} et doit d'abord être fermé. - - Impossible de définir une propriété AutoIncrement pour une colonne calculée. + + DeriveParameters {0} ne prend en charge que CommandType.StoredProcedure, et pas CommandType.{1}. - - Impossible de définir la propriété AutoIncrement pour une colonne pour laquelle DefaultValue est défini. - - - Impossible de définir un DefaultValue sur une colonne AutoIncrement. - - - AutoIncrementStep doit être une valeur différente de zéro. - - - ColumnName est requis lorsqu'il fait partie d'un DataTable. - - - Impossible de changer DataType d'une colonne lorsqu'il contient des données. - - - La colonne requiert un DataType valide. - - - Le DefaultValue pour la colonne {0} est de type {1} et ne peut pas être converti en {2}. - - - Le DefaultValue pour la colonne est de type {0} et ne peut pas être converti en {1}. - - - Le DefaultValue pour la colonne {0} est de type {1}, mais la colonne est de type {2}. - - - Impossible de changer la propriété ReadOnly pour la colonne d'expression. - - - Impossible de changer la propriété Unique pour la colonne d'expression. - - - Impossible de créer une expression sur une colonne qui a AutoIncrement ou Unique. - - - Impossible de définir l'expression, car la colonne ne peut pas être ReadOnly. - - - Impossible de définir la propriété Expression sur la colonne {0}, car elle fait partie d'une contrainte. - - - Impossible de créer une contrainte à partir d'une colonne Expression {0}. - - - Impossible de définir la propriété Expression en raison d'une référence circulaire dans l'expression. - - - La colonne '{0}' contient des valeurs null. - - - La colonne '{0}' n'autorise pas les valeurs nulls. - - - La colonne '{0}' est en lecture seule. - - - La colonne '{0}' contient des valeurs qui ne sont pas uniques. - - - La colonne '{0}' n'appartient pas à la table {1}. - - - La colonne doit appartenir à une table. - - - Impossible de stocker <{0}> dans la colonne {1}. Type attendu est {2}. - - - Impossible de définir Column '{0}' à null. Utilisez DBNull à la place. - - - Impossible de définir la colonne '{0}'. La valeur dépasse la limite MaxLength de cette colonne. - - - MaxLength s'applique uniquement au type de données string. Vous ne pouvez pas définir la propriété MaxLength de Column '{0}' avec un nombre non négatif. - - - Impossible de définir la propriété MaxLength en '{1}' pour Column'{0}'. Au moins une chaîne dans la table est plus longue que la nouvelle limite. - - - Impossible de définir la propriété MaxLength de Column '{0}'. La colonne est de type SimpleContent. - - - Impossible de définir la propriété DataType à {1} pour Column '{0}'. La colonne est de type SimpleContent. - - - Impossible de définir la propriété MappingType à SimpleContent pour la colonne '{0}'. Le DataType de Column est {1}. - - - Column '{0}' dépasse la limite MaxLength. - - - La colonne '{0}' n'autorise pas DBNull.Value. - - - Impossible de modifier la propriété Namespace de Column '{0}'. La colonne est de type SimpleContent. - - - Impossible de modifier AutoIncrement d'un DataColumn du type '{0}' lorsqu'il comporte des données. - - - La colonne '{0}' n'appartient pas à la table sous-jacente '{1}'. - - - DataColumn de type '{0}' est de type complexType. Impossible de sérialiser la valeur d'un type complexe comme Attribute - - - Possibilité de définir DateTimeMode uniquement sur les DataColumns du type DateTime. - - - La valeur du DataSetDateTime de '{0}' n'est pas valide. - - - Impossible de modifier DateTimeMode de '{0}' en '{1}' lorsque la table contient des données. - - - Type '{0}' ne contient pas la propriété ou le champ Null static. - - - Le type '{0}' n'implémente pas IRevertibleChangeTracking ; impossible de poursuivre RejectChanges(). - - - SetAdded et SetModified peuvent uniquement être appelés sur DataRows avec l'état DataRowState Unchanged. - - - Le nombre ordinal « {0} » dépasse le nombre maximal. - - - DataSet ne prend pas en charge System.Nullable<>. - - - Impossible de changer le nom d'une contrainte en chaîne vide lorsqu'elle se trouve dans ConstraintCollection. - - - Impossible d'appliquer des contraintes sur la contrainte {0}. - - - La colonne '{0}' doit être unique. La valeur '{1}' est déjà présente. - - - La contrainte '{0}' n'appartient pas à ce DataTable. - - - Impossible de trouver la contrainte {0}. - - - La contrainte correspond à la contrainte appelée {0} qui se trouve déjà dans la collection. - - - Une Constraint appelée '{0}' appartient déjà à ce DataTable. - - - Ces colonnes n'ont pas actuellement de valeurs uniques. - - - Ces colonnes ne pointent pas vers cette table. - - - Cette contrainte ne peut pas être activée, car toutes les valeurs n'ont pas de valeurs parentes correspondantes. - - - Cette contrainte ne peut pas être ajoutée, car ForeignKey n'appartient pas à la table {0}. - - - Impossible de supprimer une contrainte qui n'appartient pas à cette table. - - - Impossible de supprimer la contrainte unique '{0}'. Supprimez d'abord la contrainte de clé étrangère '{1}'. - - - Impossible de supprimer cette ligne, car les contraintes sont appliquées sur la relation {0}. La suppression de cette ligne rendra les lignes enfants orphelines. - - - Impossible d'apporter cette modification, car les contraintes sont appliquées sur la relation {0}. La modification de cette valeur rendra les lignes enfants orphelines. - - - Impossible d'effacer la table {0}, car ForeignKeyConstraint {1} applique les contraintes et {2} contient des lignes enfants. - - - ForeignKeyConstraint {0} a besoin des valeurs de clé enfant ({1}) dans la table parente. - - - Propriété inaccessible, car '{0}'. - - - Impossible de supprimer cette ligne, car elle a des lignes enfants et les contraintes sur la relation {0} sont appliquées. - - - Impossible d'ajouter une contrainte de clé primaire, car la clé primaire est déjà définie pour la table. - - - Impossible d'ajouter une contrainte à DataTable '{0}' qui est une table enfant dans deux relations imbriquées. - - - Impossible de créer une clé à partir de colonnes qui appartiennent à des tables différentes. - - - Impossible d'avoir 0 colonne. - - - Impossible d'avoir plus de {0} colonnes. - - - Impossible de créer une clé lorsque la même colonne est répertoriée plusieurs fois : '{0}' - - - Impossible de supprimer une contrainte unique, car il s'agit de la clé primaire d'une table. - - - Impossible de supprimer une contrainte unique, car il s'agit de la clé primaire d'une table {0}. - - - Les types des colonnes parentes et des colonnes enfants ne correspondent pas. - - - ParentKey et ChildKey sont identiques. - - - ParentColumns et ChildColumns doivent avoir la même longueur. - - - ParentColumns et ChildColumns ne doivent pas avoir une longueur égale à zéro. - - - La ligne n'appartient pas au même DataSet que cette relation. - - - RelationName est requis lorsqu'il fait partie d'un DataSet. - - - GetChildRows requiert une ligne dont la table est {0}, mais la Table de la ligne spécifiée est {1}. - - - Cette relation doit connecter deux tables dans ce DataSet pour être ajoutée à ce DataSet. - - - GetParentRow requiert une ligne dont la table est {0}, mais la table de la ligne spécifiée est {1}. - - - SetParentRow requiert une ligne enfant dont la table est {0}, mais la table de la ligne spécifiée est {1}. - - - Impossible d'avoir une relation entre des tables dans des DataSets différents. - - - Impossible de créer une relation entre des tables dans des DataSets différents. - - - Une relation existe déjà pour ces colonnes enfants. - - - Cette relation n'appartient pas à cette collection relation. - - - Cette relation appartient déjà à un autre DataSet. - - - Cette relation appartient déjà à ce DataSet. - - - Une Relation nommée '{0}' appartient déjà à ce DataSet. - - - La Relation {0} n'appartient pas à ce DataSet. - - - Impossible de trouver la relation {0}. - - - Impossible de créer une collection sur une table null. - - - La table dont cette collection affiche les relations a été supprimée de son DataSet. - - - Impossible d'ajouter une relation à la collection ParentRelation de cette table lorsque cette table n'est pas la table enfant. - - - Impossible d'ajouter une relation à la collection ChildRelation de cette table lorsque cette table n'est pas la table parente. - - - Impossible de définir la valeur false pour la propriété 'Nested' pour cette relation. - - - La même table '{0}' ne peut pas être la table enfant dans deux relations imbriquées. - - - La table ({0}) ne peut pas être sa table enfant dans les relations imbriquées. - - - Impossible d'ajouter un DataRelation ou un Constraint ayant des paramètres Locale ou CaseSensitive différents entre ses tables parentes et enfants. - - - Impossible de créer un DataRelation si les colonnes parent ou enfant ne se trouvent pas dans un DataSet. - - - La table imbriquée '{0}' héritant de son espace de noms ne peut pas comporter plusieurs tables parent dans des espaces de noms distincts. - - - La table imbriquée '{0}' avec espace de noms vide ne peut pas avoir plusieurs tables parentes dans différents espaces de noms. - - - La ligne n'appartient pas au même DataSet que cette relation. - - - Impossible d'effectuer cette opération sur une ligne qui ne se trouve pas dans la table. - - - Cette relation et cette ligne enfant n'appartiennent pas au même DataSet. - - - Impossible de changer une valeur proposée dans l'événement RowChanging. - - - Impossible d'appeler EndEdit() dans un événement OnRowChanging. - - - Impossible d'appeler BeginEdit() dans l'événement RowChanging. - - - Impossible d'appeler CancelEdit() dans un événement OnRowChanging. Levez une exception pour annuler cette mise à jour. - - - Impossible d'appeler Delete dans un événement OnRowDeleting. Levez une exception pour annuler cette suppression. - - - Le tableau en entrée est plus long que le nombre de colonnes de cette table. - - - Il n'y a pas de données Current auxquelles accéder. - - - Il n'y a pas de données Original auxquelles accéder. - - - Il n'y a pas de données Proposed auxquelles accéder. - - - Cette ligne a été supprimée d'une table et ne contient pas de données. BeginEdit() permettra la création de nouvelles données dans cette ligne. - - - Impossible d'accéder via la ligne aux informations de ligne supprimées. - - - La version doit être Original, Current, ou Proposed. - - - Aucune ligne à la position {0}. - - - La position d'insertion de la ligne {0} n'est pas valide. - - - rowOrder value={0} a été trouvé deux fois pour la table nommée '{1}'. - - - Des valeurs manquent dans la séquence rowOrder pour la table '{0}'. - - - Le DataRow donné ne se trouve pas dans le DataRowCollection actuel. - - - Cette ligne appartient déjà à une autre table. - - - Cette ligne appartient déjà à cette table. - - - Impossible de supprimer cette ligne, car elle est déjà supprimée. - - - Cette ligne est vide. - - - Impossible de supprimer une ligne qui a déjà été supprimée. - - - Une ligne enfant a plusieurs parents. - - - Modèle de bit non reconnu pour l'état de ligne. - - - Impossible de changer le nom du DataSet en une chaîne vide. - - - Le nom '{0}' n'est pas valide. Un DataSet ne peut pas avoir le même nom que le DataTable. - - - L'espace de noms du schéma est non valide. Utilisez plutôt celui-ci : {0}. - - - Impossible de modifier une propriété CaseSensitive ou Locale. Cette modification obligerait au moins un DataRelation ou un Constraint à posséder des paramètres Locale ou CaseSensitive différents entre ses tables connexes. - - - La propriété SchemaSerializationMode ne peut être définie que si elle est remplacée par un DataSet dérivé. - - - Les colonnes PrimaryKey n'appartiennent pas à cette table. - - - Impossible d'ajouter une relation imbriquée ou une colonne d'éléments à une table contenant une colonne SimpleContent. - - - TableName est requis lorsqu'il fait partie d'un DataSet. - - - DataTable comporte déjà une colonne de contenu simple. - - - La table n'a pas une clé primaire. - - - {0} n'est pas une entrée de chaîne Sort valide. - - - Impossible de sérialiser le DataTable. Un DataTable utilisé dans des expressions DataColumn n'est pas un descendant du DataTable actuel. - - - Ce DataTable peut uniquement être distant s'il fait partie de DataSet. Des colonnes d'expression font référence à d'autres DataTable(s). - - - DataSet et DataTable ne peuvent pas comporter des valeurs de propriété de format de communication à distance différentes. - - - Impossible de sérialiser le DataTable. Le nom du DataTable n'est pas défini. - - - Un DataTable appelé '{0}' appartient déjà à ce DataSet. - - - Un DataTable nommé '{0}' avec le même Namespace '{1}' appartient déjà à ce DataSet. - - - La table ({0}) ne peut pas être sa table enfant dans une relation imbriquée : le nom DataSet est en conflit avec le nom de la table. - - - Le nom '{0}' n'est pas valide. Un DataTable ne peut pas avoir le même nom que le DataSet. - - - DataTable appartient déjà à un autre DataSet. - - - DataTable appartient déjà à ce DataSet. - - - La table {0} n'appartient pas à ce DataSet. - - - Impossible de trouver la table {0}. - - - Impossible de supprimer une table qui a des relations existantes. Supprimez tout d'abord les relations. - - - Impossible de supprimer la table {0}, car elle est référencée dans ForeignKeyConstraint {1}. Supprimez tout d'abord la contrainte. - - - Le DataTable '{0}' ne correspond à aucun DataTable de la source. - - - Définition DataSet cible manquante pour {0}. - - - DataSet cible manquant {0} {1}. - - - <target>.{0} et <source>.{0} ont des propriétés en conflit : incompatibilité de propriété DataType. - - - Length différent pour <target>.PrimaryKey et <source>.PrimaryKey. - - - Colonnes incompatibles dans PrimaryKey : <target>.{0} versus <source>.{1}. - - - La Relation {0} ne peut pas être fusionnée, car les clés ont des colonnes incompatibles. - - - La table cible {0} n'a pas de définition pour la colonne {1}. - - - La colonne PrimaryKey {0} n'existe pas dans la table source. - - - Le paramètre RowStates doit être défini à une combinaison valide de valeurs à partir de l'énumération DataViewRowState. - - - Find recherche une ligne en fonction d'un ordre de tri et aucun ordre de tri n'est spécifié. - - - {0} valeurs attendues pour la clé indexée, mais {1} valeurs reçues. - - - Utilisation non valide de la fonction d'agrégation {0}() et du type : {1}. - - - Type d'enregistrement non valide : {0}. - - - La persistance Xml DataSet ne prend pas en charge la valeur '{0}' en tant que valeur Char, utilisez plutôt le stockage Byte. - - - Le type de valeur ne correspond pas au type de colonne - - - Le type '{0}' n'implémente pas l'interface IComparable. Comparaison impossible. - - - Impossible de définir {0}. - - - Impossible de changer DataSet dans un DataViewManager qui est déjà la vue par défaut d'un DataSet. - - - RowStateFilter ne peut afficher ModifiedOriginals et ModifiedCurrents à la fois. - - - Impossible de changer la propriété Table dans un DefaultView ou un DataView provenant d'un DataViewManager. - - - Impossible de changer la propriété DataSet une fois qu'elle a été définie. - - - DataSet doit être défini avant d'utiliser DataViewManager. - - - Impossible de changer la propriété Table une fois qu'elle a été définie. - - - DataTable doit être défini avant d'utiliser DataView. - - - Impossible d'effectuer une liaison à DataTable sans nom. - - - Impossible de définir un objet dans cette liste. - - - Impossible d'appeler AddNew dans un DataView lorsque AllowNew a la valeur false. - - - DataView n'est pas ouvert. - - - La relation n'est pas apparentée à la table vers laquelle ce DataView pointe. - - - Impossible de supprimer dans un DataSource lorsque AllowDelete a la valeur false. - - - Impossible de modifier dans un DataSource lorsque AllowEdit a la valeur false. - - - L'index {0} est négatif ou supérieur au nombre de lignes. - - - Impossible d'ajouter des objets externes à cette liste. - - - Impossible d'effacer cette liste. - - - Impossible d'insérer des objets externes dans cette liste. - - - Impossible de supprimer des objets qui ne sont pas dans la liste. - - - {0} n'est pas un DataColumn ou un DataRelation pour la table {1}. - - - Min ({0}) doit être inférieur ou égal à max ({1}) dans un objet Range. - - - Il s'agit d'une plage null. - - - MinimumCapacity ne doit pas être négatif. - - - Impossible de générer l'identificateur pour le nom '{0}'. - - - Plusieurs tables portent le même nom '{0}' (même si l'espace de noms est différent). - - - Colonne '{0}' : Le type '{1}' ne peut pas être null. - - - Colonne '{0}' : le type '{1}' n'a pas de constructeur sans paramètre. - - - Colonne '{0}' : le type '{1}' n'a pas de constructeur avec un argument string. - - - Impossible de convertir l'objet de type « {0} » en objet de type « {1} ». - - - Exception Data. - - - Exception Constraint. - - - Lignes supprimées inaccessibles. - - - Nom dupliqué non autorisé. - - - Opération non prise en charge dans l'événement RowChanging. - - - Contrainte non valide. - - - Clé primaire manquante. - - - Valeur Null non autorisée. - - - Colonne indiquée en lecture seule. - - - Ligne introuvable dans la table. - - - Version introuvable. - - - Les données ReadOnly ont été modifiées. - - - DataTableReader non valide pour le DataTable '{0}' actuel. - - - Le schéma du DataTable '{0}' actuel de DataTableReader a été modifié, DataTableReader n'est pas valide. - - - Impossible de créer DataTableReader. DataSet ne contient aucun DataTable. - - - Impossible de créer DataTableReader. L'argument est vide. - - - Impossible de créer DataTableReader. Les arguments contiennent la valeur null. - - - Le DataRow actuel porte l'état Deleted ou Detached. - - - Le DataTable actuel '{0}' est vide. DataTable ne contient aucun DataRow. - - - L'index interne DataTable est endommagé : '{0}'. - - - La collection a été modifiée ; l'opération d'énumération risque de ne pas s'exécuter. - - - Le type simple « {0} » a déjà été déclaré avec une autre valeur « {1} ». - - - Foliation non valide. - - - Impossible de changer le nom de la table une fois que le DataSet associé est mappé à un document XML chargé. - - - Impossible de changer l'espace de noms de la table une fois que le DataSet associé est mappé à un document XML chargé. - - - Impossible de changer le nom de la colonne une fois que le DataSet associé est mappé à un document XML chargé. - - - Impossible de changer l'espace de noms de la colonne une fois que le DataSet associé est mappé à un document XML chargé. - - - Impossible de changer la propriété ColumnMapping une fois que le DataSet associé est mappé à un document XML chargé. - - - Impossible d'ajouter ou de supprimer des colonnes à partir de la table une fois que le DataSet est mappé à un document XML chargé. - - - Impossible d'ajouter ou de supprimer des tables du DataSet une fois que le DataSet est mappé à un document XML chargé. - - - Impossible d'ajouter, de supprimer ou de changer des relations imbriquées à partir du DataSet une fois que le DataSet est mappé à un document XML chargé. - - - Le paramètre DataSet n'est pas valide. Il ne peut pas être null. - - - Impossible de changer le nom DataSet une fois que le DataSet est mappé à un document XML chargé. - - - Ce type de noeud ne peut pas être cloné : {0}. - - - Impossible de charger XmlDataDocument s'il contient déjà des données. Utilisez un nouveau XmlDataDocument. - - - Le DataSet peut être associé à un XmlDataDocument maximum. Il est impossible d'associer le DataSet au XmlDataDocument actif, car le DataSet est déjà associé à un autre XmlDataDocument. - - - Définissez DataSet.EnforceConstraints == false avant d'essayer de modifier XmlDataDocument à l'aide d'opérations XML. - - - GetElementById() n'est pas pris en charge dans DataDocument. - - - Impossible de créer des références d'entité sur DataDocument. - - - La fonction Clear sur DateSet et DataTable n'est pas prise en charge sur XmlDataDocument. - - - Impossible de supprimer la colonne, car elle est intégrée dans ce dataSet. - - - Désolé... Nous ne pouvons pas supprimer la relation, car elle est intégrée à ce jeu de données. - - - Se produit lorsqu'une propriété pour ce contrôle change. - - - Se produit lorsque l'appartenance de cette collection change. - - - Impossible d'obtenir la valeur, car il s'agit d'une valeur DBNull. - - - La propriété '{0}' requiert Microsoft WindowsNT ou un système d'exploitation WindowsNT. - - - Le fournisseur de données .NET Framework demandé est introuvable. Il n'est peut-être pas installé. - - - L'implémentation du fournisseur de données .NET Framework demandée ne comporte pas de champ d'instance d'un type dérivé de System.Data.Common.DbProviderFactory. - - - La recherche ou le chargement du fournisseur de données .NET Framework inscrit a échoué. - - - Le nom qualifié d'assembly du fournisseur de données .NET Framework est obligatoire. - - - Seuls éléments autorisés. - - - Nœuds enfants non autorisés. - - - Attribut '{0}' non reconnu. - - - Élément non reconnu. - - - La section '{0}' ne peut figurer qu'une seule fois par fichier de configuration. - - - L'attribut obligatoire '{0}' est introuvable. - - - L'attribut requis '{0}' ne peut pas être vide. - - - Tableau non vide attendu pour le paramètre '{0}'. - - - La seule valeur acceptable pour la propriété '{0}' est '{1}'. - - - Les valeurs acceptables de la propriété '{0}' sont '{1}' ou '{2}'. - - - Les valeurs spécifiées de QuotePrefix et de QuoteSuffix ne correspondent pas. - - - La longueur de l’argument «{0}» dépasse sa limite de «{1}». - - - Argument de type {1} attendu, mais type {0} reçu. - - - Valeur non valide pour la clé « {0} ». - - - L'utilisation de la clé '{0}' nécessite la présence de la clé '{1}'. - - - La longueur de la valeur pour la clé «{0}» dépasse sa limite de «{1}». - - - Mot clé non pris en charge : « {0} ». - - - Impossible de charger le fichier UDL. - - - Fichier UDL non valide. - - - Erreur interne du fournisseur de données .NET Framework {0}. - - - Les propriétés QuotePrefix et QuoteSuffix ne peuvent pas être changées lorsqu'une commande Insert, Update ou Delete a été générée. - - - La propriété DataAdapter.SelectCommand doit être initialisée. - - - La propriété DataAdapter.SelectCommand.Connection doit être initialisée ; - - - {0} « {1} ». - - - {0} "{1}", utilisation incorrecte de guillemets. - - - {0} "{1}", la limite actuelle de "{2}" est insuffisante. - - - Échec de SqlBulkCopy.WriteToServer car SqlBulkCopy.DestinationTableName est un nom en plusieurs parties non valide - - - Échec du traitement des résultats provenant de SQL Server à cause d'un nom en plusieurs parties non valide - - - SqlParameter.UdtTypeName est un nom en plusieurs parties non valide - - - SqlParameter.TypeName est un nom en plusieurs parties non valide - - - Échec de SqlCommand.DeriveParameters car la valeur de la propriété SqlCommand.CommandText est un nom en plusieurs parties non valide - - - Échec de OdbcCommandBuilder.DeriveParameters car la valeur de la propriété OdbcCommand.CommandText est un nom en plusieurs parties non valide - - - Échec de OleDbCommandBuilder.DeriveParameters car la valeur de la propriété OleDbCommandBuilder.CommandText est un nom en plusieurs parties non valide - - - La connexion à une instance de SQL Server mise en miroir à l'aide de l'option de connexion MultiSubnetFailover n'est pas prise en charge. - - - Le mappage de colonnes de SourceColumn '{0}' a échoué car le DataColumn '{1}' est une colonne calculée. - - - Incompatibilité de type ne pouvant pas être converti entre SourceColumn '{0}' de {1} et DataColumn '{2}' de {3}. - - - DataColumn '{0}' manquant dans DataTable '{1}' pour SourceColumn '{2}'. - - - SourceColumn doit être une chaîne non vide. - - - Mappage SourceColumn manquant pour '{0}'. - - - La valeur d'énumération {0}, {1}, n'est pas prise en charge par la méthode {2}. - - - La valeur d'énumération {0}, {1}, n'est pas prise en charge par le fournisseur de données .NET Framework ODBC. - - - La valeur d'énumération {0}, {1}, n'est pas prise en charge par le fournisseur de données .NET Framework OLEDB. - - - La valeur d'énumération {0}, {1}, n'est pas prise en charge par le fournisseur de données .NET Framework SqlClient. - - - Impossible d'extraire le nom complet ComputerNameDnsFullyQualified, {0}. - - - DataTable '{0}' manquant pour le SourceTable '{1}'. - - - SourceTable doit être une chaîne non vide - - - Mappage SourceTable manquant : '{0}' - - - {0} : la propriété CommandText n'a pas été initialisée - - - {0} : la propriété Connection n'a pas été initialisée. - - - {0} nécessite une Connection ouverte et disponible. {1} - - - Fill : la propriété SelectCommand.Connection n'a pas été initialisée. - - - FillPage : la propriété SelectCommand.Connection n'a pas été initialisée. - - - FillSchema : la propriété SelectCommand.Connection n'a pas été initialisée. - - - Pour la mise à jour, la propriété InsertCommand doit posséder un objet connection. La propriété Connection de InsertCommand n'a pas été initialisée. - - - UpdateCommand doit avoir un objet connection pour la mise à jour. La propriété Connection de UpdateCommand n'a pas été initialisée. - - - DeleteCommand doit avoir un objet connection pour la mise à jour. La propriété Connection de DeleteCommand n'a pas été initialisée. - - - Un objet de connexion doit être utilisé pour la mise à jour. La propriété Connection n'a pas été initialisée. - - - Le clone de commande a besoin d'un objet connection pour la mise à jour. La propriété Connection du clone de la commande n'a pas été initialisée. - - - La mise à jour nécessaire une connexion. - - - {0}Command doit avoir un objet open connection pour Update. {1} - - - {0}Command doit avoir un objet open connection pour Update. {1} - - - {0}Command doit avoir un objet open connection pour Update. {1} - - - La commande de mise à jour doit avoir un objet open connection pour Update. {1} - - - La procédure stockée '{0}' n'existe pas. - - - La transaction assignée à cette commande doit être la transaction locale en attente la plus imbriquée. - - - La transaction n'est pas associée à la connexion en cours ou s'est terminée. - - - La transaction associée à la connexion active est terminée mais n'a pas été supprimée. Pour pouvoir utiliser la transaction pour exécuter des instructions SQL vous devez la supprimer. - - - {0} implique que la commande comporte une transaction lorsque la connexion attribuée à la commande se trouve dans une transaction locale en attente. La propriété Transaction de la commande n'a pas été initialisée. - - - Un SqlResultSet associé à cette commande est déjà ouvert. Il doit d'abord être fermé. - - - Un DataReader ouvert est déjà associé à {0} et doit d'abord être fermé. - - - DeriveParameters {0} ne prend en charge que CommandType.StoredProcedure, et pas CommandType.{1}. - - - La méthode '{0}' ne peut être appelée qu'une fois pour la même exécution. - - - Le résultat asynchrone est incorrect. - - - La propriété SelectCommand n'a pas été initialisée avant l'appel de '{0}'. - - - StatementType {0} non attendu ici. - - - FillSchema : chaîne non valide attendue pour le nom SourceTable. - - - La valeur MaxRecords de {0} n'est pas valide ; la valeur doit être >= 0. - - - La valeur StartRecord de {0} n'est pas valide ; la valeur doit être >= 0. - - - Fill : chaîne non vide attendue pour le nom SourceTable. - - - Les colonnes des chapitres hiérarchiques doivent mapper AutoIncrement DataColumn. - - - DataReader.GetFieldType({0}) a retourné la valeur null. - - - Ne spécifiez qu'un seul élément dans le tableau dataTables lors de l'utilisation de valeurs non nulles pour startRecords ou maxRecords. - - - Impossible pour Update de trouver TableMapping['{0}'] ou DataTable '{0}'. - - - Update : nom SourceTable non vide attendu. - - - TableMapping manquant quand TableMapping.DataSetTable='{0}'. - - - Le clone de commande doit être valide pour Update. - - - La génération SQL automatique pendant une Update requiert un SelectCommand valide. - - - Update requiert un InsertCommand valide lors du passage de la collection DataRow avec de nouvelles lignes. - - - Update requiert un UpdateCommand valide lors du passage de la collection DataRow avec des lignes modifiées. - - - Update requiert un DeleteCommand valide lors du passage de la collection DataRow avec des lignes supprimées. - - - DataRow[0] est d'un autre DataTable que DataRow[{0}]. - - - RowUpdatedEvent : des erreurs se sont produites ; aucune information supplémentaire n'est disponible. - - - RowUpdatingEvent : des erreurs se sont produites ; aucune information supplémentaire n'est disponible. - - - Dans un traitement par lots, la valeur de la propriété UpdatedRowSource de la commande pour UpdateRowSource.FirstReturnedRecord ou pour UpdateRowSource.Both n'est pas valide. - - - Violation de l'accès concurrentiel : UpdateCommand a affecté {0} des enregistrements {1} attendus. - - - Violation de l'accès concurrentiel : DeleteCommand a affecté {0} des enregistrements {1} attendus. - - - Violation de l'accès concurrentiel : la commande automatisée a affecté {0} des enregistrements {1} attendus. + + La méthode '{0}' ne peut être appelée qu'une fois pour la même exécution. Valeur CommandTimeout {0} non valide ; la valeur doit être >= 0. - - {1}[{0}] : la propriété Size a une taille non valide, 0. - - - La méthode {0}.Prepare requiert que tous les paramètres aient un type défini explicitement. - - - La méthode {0}.Prepare requiert que tous les paramètres de longueur variable aient une valeur Size explicitement définie différente de zéro. - - - La méthode {0}.Prepare requiert que les paramètres de type '{1}' aient des valeurs Precision et Scale explicitement définies. - - - Appel de la méthode End incompatible avec asyncResult. Appel de {0} attendu, mais c'est la méthode {1} qui a été appelée. - - - Opération non valide. La connexion est fermée. - - - La connexion a été désactivée. - - - Inscription impossible dans la transaction car une transaction locale est en cours sur la connexion. Terminez la transaction locale et réessayez. - - - La connexion comporte actuellement une transaction inscrite. Terminez la transaction en cours et réessayez. - - - La base de données ne peut pas être null, une chaîne vide ou une chaîne exclusivement composée d'espaces. - - - L'argument est trop long. - - - Valeur 'Connect Timeout' non valide qui doit être un entier >= 0. - - - Mémoire tampon source non valide (taille de {0}) offset : {1} - - - Mémoire tampon de destination non valide (taille de {0}) offset : {1} - - - La valeur numérique est trop élevée pour tenir dans un décimal à 96 bits. - - - Tentative non valide de {0} lorsque le flux est fermé. - - - La valeur de SeekOrigin spécifiée n'est pas valide. - - - La génération SQL dynamique n'est pas prise en charge pour plusieurs tables de base. - - - La génération SQL dynamique n'est pas prise en charge pour un SelectCommand qui ne retourne pas des informations de table de base. - - - La génération SQL dynamique de DeleteCommand n'est pas prise en charge pour un SelectCommand qui ne retourne pas des informations de colonne clé. - - - La génération SQL dynamique de UpdateCommand n'est pas prise en charge pour un SelectCommand qui ne retourne pas des informations de colonne clé. - - - La génération SQL dynamique de DeleteCommand n'est pas prise en charge pour un SelectCommand qui ne contient pas de colonne version de ligne. - - - La génération SQL dynamique de UpdateCommand n'est pas prise en charge pour un SelectCommand qui ne contient pas de colonne version de ligne. - - - La génération SQL dynamique n'est pas prise en charge pour les noms de table '{0}' qui contiennent le caractère QuotePrefix ou QuoteSuffix '{1}'. - - - Tentative non valide de lecture du numéro de colonne '{0}'. Avec CommandBehavior.SequentialAccess, vous ne pouvez lire qu'à partir du numéro de colonne '{1}' ou supérieur. - - - Le type de données '{0}' ne peut pas adopter le format littéral car sa valeur de date et d'heure n'est pas valide. - - - La valeur ne peut pas adopter le format littéral du type demandé. - - - Le type de données '{0}' ne peut pas adopter le format littéral car son échelle maximale n'est pas valide. - - - La valeur littérale fournie n'est pas valide pour le type de données '{0}'. - - - '{0}' : la longueur de la valeur littérale doit être paire. - - - '{0}' : la valeur littérale doit être une chaîne composée de chiffres hexadécimaux - - - {0} nécessite une connexion ouverte lorsque le préfixe de guillemet n'a pas été défini. - - - Impossible de déterminer les valeurs littérales booléennes correctes. Impossible de créer des littéraux Boolean. - - - Les littéraux du type de données natif associé au type de données '{0}' ne sont pas pris en charge. - - - Le type de données du paramètre de {0} n'est pas valide. - - - Aucun mappage n'existe à partir du type d'objet {0} vers un type connu de fournisseur natif managé. - - - Impossible de gérer un TypeCode inconnu {0} retourné par le Type {1}. - - - Aucun mappage n'existe à partir de DbType {0} vers un {1} connu. - - - La version de SQL Server utilisée ne prend pas en charge le type de données '{0}'. - - - Valeur du paramètre '{0}' hors limites. - - - Le nom de paramètre spécifié, '{0}', n'est pas valide. - - - Les paramètres de valeur de retour multiples ne sont pas pris en charge. - - - Valeur Size du paramètre '{0}' non valide. La valeur doit être supérieure ou égale à 0. - - - Valeur non valide pour l'argument '{0}'. La valeur doit être supérieure ou égale à 0. - - - Valeur non valide pour ces métadonnées. - - - Les métadonnées doivent être SqlDbType.Row - - - Échec de la conversion de la valeur de paramètre d'un {0} en un {1}. - - - Les transactions parallèles ne sont pas prises en charge par {0}. - - - {0} est terminé ; il n'est plus utilisable. - - - '{0}' ne peut pas être appelé lorsque l'enregistrement est en lecture seule. - - - '{0}' ne peut pas être appelé lorsque DbDataRecord est en lecture seule. - - - La conversion implicite du type d'objet '{0}' en type de données '{1}' n'est pas prise en charge. - - - L'offset de la mémoire tampon « {1} » plus les octets disponibles « {0} » sont supérieurs à la longueur de la mémoire tampon passée. - - - La longueur des données « {0} » est inférieure à 0. - - - La longueur spécifiée '{0}' est hors limites. - - - Tentative {2} non valide au niveau de dataIndex '{0}'. Avec CommandBehavior.SequentialAccess ; vous ne pouvez lire qu'à partir de dataIndex '{1}' ou supérieur. - - - Offset doit faire référence à un emplacement de la valeur. - - - Pour le fournisseur managé par ODBC, la restriction TABLE_NAME doit être spécifiée et non null pour la collection d'index GetSchema. - - - Valeur d'argument non valide pour la méthode '{0}'. - - - Le fournisseur ODBC n'a pas renvoyé de résultats à partir de SQLGETTYPEINFO. - - - Argument DataTable null inattendu - - - Argument DataSet null inattendu. - - - La chaîne de connexion dépasse la longueur maximale autorisée {0}. - - - {0} - Impossible de mapper le type. - - - Type SQL inconnu - {0}. - - - Type URT inconnu - {0}. - - - Argument négatif non valide ! - - - Impossible de définir une propriété sur une connexion ouverte. - - - Aucun mappage valide d'une valeur SQL_TRANSACTION '{0}' vers une valeur d'énumération System.Data.IsolationLevel. - - - {0} - impossible d'activer le regroupement de connexions... - - - {0} - impossible d'allouer un handle d'environnement. - - - {0} - impossible d'obtenir le handle du descripteur. - - - Ne se trouve pas dans une transaction - - - Énumération OdbcType value={0} non valide. - - - Utilisez IsDBNull lorsque des données DBNull.Value sont attendues. - - - {0} [{1}] {2} - - - {0} - aucune information d'erreur disponible - - - La connexion est fermée. - - - Une connexion interne n'a pas un propriétaire. - - - Le fournisseur de données .NET Framework ODBC nécessite MDAC (Microsoft Data Access Components) version 2.6 ou ultérieure. La version {0} est actuellement installée. - - - Le fournisseur de données .NET Framework OLEDB nécessite MDAC (Microsoft Data Access Components) version 2.6 ou ultérieure. La version {0} est actuellement installée. - - - L'interface '{0}' n'est pas prise en charge par le fournisseur '{1}'. GetOleDbSchemaTable n'est pas disponible avec le fournisseur actuel. - - - Échec de'{0}' sans aucun message d'erreur disponible, code de résultat : {1}. - - - Aucune message d'erreur disponible, code de résultat : {0}. - - - Les fournisseurs de données .NET Framework nécessitent MDAC (Microsoft Data Access Components). Installez MDAC version 2.6 ou ultérieure. - - - Le fournisseur de données .NET Framework pour OLEDB (Microsoft.Data.OleDb) ne prend pas en charge le fournisseur Microsoft OLE DB pour les pilotes ODBC (MSDASQL). Utilisez le fournisseur de données .NET Framework pour ODBC (System.Data.Odbc). - - - Le fournisseur de données .NET Framework pour OLEDB n'autorisera pas le fournisseur OLE DB à afficher des invites utilisateur dans un environnement non interactif. - - - Le fournisseur '{0}' n'est pas inscrit sur l'ordinateur local. - - - L'interface ICommandText n'est pas prise en charge par le fournisseur '{0}'. Utilisez CommandType.TableDirect à la place. - - - L'interface ITransactionLocal n'est pas prise en charge par le fournisseur '{0}'. Les transactions locales ne sont pas disponibles avec le fournisseur actuel. - - - Le format de la chaîne d'initialisation n'est pas conforme à la spécification OLE DB. La chaîne de connexion commence aux alentours du caractère char[{0}]. - - - La fonctionnalité de traitement asynchrone n'est pas prise en charge par le fournisseur de données .NET Framework OLE DB (Microsoft.Data.OleDb). - - - Un fournisseur OLE DB n'était pas spécifié dans ConnectionString. Par exemple, 'Provider=SQLOLEDB;'. - - - Le fournisseur OLE DB spécifié dans le ConnectionString est trop long. - - - Aucune restriction n'est attendue pour le DbInfoKeywords OleDbSchemaGuid. - - - Aucune restriction n'est attendue pour DbInfoLiterals OleDbSchemaGuid. - - - Aucune restriction n'est attendue pour le guid de schéma OleDbSchemaGuid. - - - Le schéma OleDbSchemaGuid {0} n'est pas pris en charge par le fournisseur '{1}'. - - - Le paramètre de configuration '{0}' possède un nombre de valeurs incorrect. - - - Impossible de charger le fichier XML spécifié dans le paramètre de configuration '{0}'. - - - Le paramètre de commande [{0}] '{1}' n'est pas valide. - - - La valeur de données [{0}] '{1}' du paramètre de commande n'a pas pu être convertie pour des raisons autres que l'incompatibilité de signe ou le dépassement de données. - - - Échec de la conversion pour le paramètre de commande [{0}] '{1}', car la valeur des données était signée et le type utilisé par le fournisseur n'était pas signé. - - - La conversion a échoué pour le paramètre de commande [{0}] '{1}', car la valeur des données a dépassé le type utilisé par le fournisseur. - - - Le fournisseur a rencontré une erreur lors de l'envoi d'une valeur [{0}] '{1}' de paramètre de commande et a arrêté le traitement. - - - Le paramètre[{0}] '{1}' n'a pas de valeur par défaut. - - - Une erreur s'est produite avec le paramètre de commande [{0}] : {1}. - - - Erreur interne Microsoft.Data.OleDb.OleDbDataAdapter : accesseur de paramètre non valide : {0} {1}. - - - Parameter [{0}] : la propriété OleDbType n'est pas initialisée : OleDbType.{1}. - - - L'interface ICommandWithParameters n'est pas prise en charge par le fournisseur '{0}'. Les paramètres de commande ne sont pas pris en charge avec le fournisseur actuel. - - - L'extraction des informations sur les paramètres de procédures n'est pas prise en charge par le fournisseur '{0}'. - - - Impossible de déterminer le séparateur décimal du serveur. Impossible de créer des littéraux numériques qui ne sont pas des entiers. - - - L'objet n'est ni un ADODB.RecordSet, ni un ADODB.Record. - - - Impossible d'extraire l'interface '{0}' de l'objet ADODB.RecordSet. + + {1}[{0}] : la propriété Size a une taille non valide, 0. - - Impossible d'extraire une interface IRow de l'objet ADODB.Record. + + La méthode {0}.Prepare requiert que tous les paramètres aient un type défini explicitement. - - Le type ne prend pas en charge l'interface OLE DB ISourcesRowset + + La méthode {0}.Prepare requiert que tous les paramètres de longueur variable aient une valeur Size explicitement définie différente de zéro. - - Impossible de construire la collection de schémas ReservedWords car le fournisseur ne prend pas en charge IDBInfo. + + La méthode {0}.Prepare requiert que les paramètres de type '{1}' aient des valeurs Precision et Scale explicitement définies. - - La valeur de la propriété n'est pas définie car le fournisseur ne prend pas en charge la propriété '{0}', ou le consommateur a tenté d'obtenir ou de définir des valeurs de propriété manquantes dans le groupe de propriétés Initialization et l'objet source de données n'est pas initialisé. + + Appel de la méthode End incompatible avec asyncResult. Appel de {0} attendu, mais c'est la méthode {1} qui a été appelée. - - Échec de l'initialisation de la propriété '{0}' pour l'une des raisons suivantes : - Le type de données de valeur n'est pas le type de la propriété ou n'est pas null. Par exemple, la propriété est DBPROP_MEMORYUSAGE, qui a le type de données Int32. Or, le type de données est Int64. - La valeur n'est pas valide. Par exemple, la propriété est DBPROP_MEMORYUSAGE et sa valeur est négative. - La valeur de la propriété est valide et le fournisseur la prend en charge en tant que propriété définissable, mais il ne prend pas en charge la valeur spécifiée. Cela concerne les cas où la valeur est ajoutée à la propriété dans OLE DB après l'inscription du fournisseur. + + Opération non valide. La connexion est fermée. - - La valeur de Options n'est pas valide. + + Inscription impossible dans la transaction car une transaction locale est en cours sur la connexion. Terminez la transaction locale et réessayez. - - L'élément ColumnID n'est pas valide. + + La connexion comporte actuellement une transaction inscrite. Terminez la transaction en cours et réessayez. - - Une propriété '{0}' a été spécifiée pour être appliquée à toutes les colonnes, mais n'a pas pu l'être à certaines colonnes. + + La base de données ne peut pas être null, une chaîne vide ou une chaîne exclusivement composée d'espaces. - - La propriété '{0}' est en lecture seule ou le consommateur a tenté de définir des valeurs de propriété dans le groupe de propriétés Initialization après l'initialisation de l'objet source de données. Les consommateurs peuvent définir la valeur d'une propriété en lecture seule sur sa valeur actuelle. Cet état est également retourné si une propriété de colonne définissable n'a pas pu être définie pour la colonne en question. + + Mémoire tampon source non valide (taille de {0}) offset : {1} - - La valeur de la propriété '{0}' facultative n'a pas été définie sur la valeur spécifiée. Ce type de définition est impossible. + + Mémoire tampon de destination non valide (taille de {0}) offset : {1} - - La valeur de la propriété '{0}' n'est pas définie pour ne pas entrer en conflit avec une propriété existante. + + Tentative non valide de {0} lorsque le flux est fermé. - - (Réservé). + + La valeur de SeekOrigin spécifiée n'est pas valide. - - Le fournisseur a retourné une valeur DBPROPSTATUS_ '{0}' inconnue. + + Tentative non valide de lecture du numéro de colonne '{0}'. Avec CommandBehavior.SequentialAccess, vous ne pouvez lire qu'à partir du numéro de colonne '{1}' ou supérieur. - - La validation de l'accesseur a été différée et a été effectuée lors du retour de données par la méthode. La liaison n'était pas valide pour cette colonne ou ce paramètre. + + Le type de données du paramètre de {0} n'est pas valide. - - Erreur interne OleDbDataAdapter : accesseur d'ensemble de lignes non valide : Ordinal={0} Status={1}. + + Aucun mappage n'existe à partir du type d'objet {0} vers un type connu de fournisseur natif managé. - - La valeur des données n'a pas pu être convertie pour des raisons autres que l'incompatibilité de signe ou le dépassement de données. Par exemple, les données étaient endommagées dans le magasin de données mais la ligne pouvait toujours être extraite. + + Impossible de gérer un TypeCode inconnu {0} retourné par le Type {1}. - - Le fournisseur n'a pas pu allouer de la mémoire pour retourner les données {0}. + + Aucun mappage n'existe à partir de DbType {0} vers un {1} connu. - - Échec de la conversion, car la valeur des données {0} a dépassé le type spécifié pour la partie de la valeur {0} dans la mémoire tampon du consommateur. + + La version de SQL Server utilisée ne prend pas en charge le type de données '{0}'. - - Erreur interne OleDbDataAdapter : [get] type de données OLE DB inconnu : 0x{0} ({1}). + + Valeur du paramètre '{0}' hors limites. - - Échec de la conversion, car la valeur de données {0} était signée et le type spécifié pour la partie de la valeur {0} dans la mémoire tampon du consommateur n'était pas signé. + + Le nom de paramètre spécifié, '{0}', n'est pas valide. - - Erreur interne OleDbDataAdapter : [set] type de données OLE DB inconnu : 0x{0} ({1}). + + Valeur Size du paramètre '{0}' non valide. La valeur doit être supérieure ou égale à 0. - - Le fournisseur n'a pas pu déterminer la valeur {0}. Par exemple, la ligne venait d'être créée, la valeur par défaut pour la colonne {0} n'était pas disponible et le consommateur n'avait pas encore défini une nouvelle valeur {0}. + + Valeur non valide pour l'argument '{0}'. La valeur doit être supérieure ou égale à 0. - - Le fournisseur OLE DB a retourné une valeur d'état {0} inattendue. + + Valeur non valide pour ces métadonnées. - - OleDbDataReader.Read doit être utilisé à partir du même thread que celui ayant servi à le créer si l'ApartmentState de ce thread n'est pas ApartmentState.MTA. + + Échec de la conversion de la valeur de paramètre d'un {0} en un {1}. - - Erreur non spécifiée : {0} + + Les transactions parallèles ne sont pas prises en charge par {0}. - - Échec de IErrorInfo.GetDescription avec {0}. + + {0} est terminé ; il n'est plus utilisable. - - Échec de IErrorInfo.GetSource avec {0}. + + La longueur spécifiée '{0}' est hors limites. - - Les données DBTYPE_VECTOR ne sont pas prises en charge par le fournisseur de données .NET Framework OLE DB (Microsoft.Data.OleDb). + + Tentative {2} non valide au niveau de dataIndex '{0}'. Avec CommandBehavior.SequentialAccess ; vous ne pouvez lire qu'à partir de dataIndex '{1}' ou supérieur. Valeurs de taille de pool min et max non valides, la taille min ne peut pas être supérieure à la taille max. - - Le mot clé '{0}' est obsolète. Utilisez plutôt '{1}'. - Impossible d'obtenir du serveur l'adresse du coordinateur des transactions distribuées du serveur. DTC est-il activé sur le serveur ? - - La longueur de la valeur pour le paramètre de connexion <{0}> dépasse la valeur maximale autorisée de 65 535 caractères. - 'Packet Size' non valide. La valeur doit être un entier compris entre 512 et 32768. Transaction non valide ou nom non valide pour un point sur lequel enregistrer dans la transaction. - - La valeur d'énumération {0}, {1}, n'est pas prise en charge par SQL Server 7.0 ni par SQL Server 2000. - User Instance et Failover ne sont pas des options compatibles. Ne choisissez que l'une d'elles dans la chaîne de connexion. @@ -2499,33 +540,21 @@ Impossible de définir la propriété Credential si 'Authentication=Active Directory Integrated' a été spécifié dans la chaîne de connexion. - - Des paramètres d'authentification incorrects ont été spécifiés avec l'authentification par certificat. - L'instance de SQL Server à laquelle vous avez tenté de vous connecter doit être cryptée, mais cet ordinateur ne prend pas en charge le cryptage. L'instance de SQL Server à laquelle vous avez tenté de vous connecter ne prend pas en charge le cryptage. - - L'instance de SQL Server à laquelle vous avez tenté de vous connecter ne prend pas en charge CTAIP. - Version de SQL Server non prise en charge. Le fournisseur de données .NET Framework SqlClient peut être utilisé avec SQL Server version 7.0 ou ultérieure uniquement. {0} ne peut pas être modifié tant qu'une opération asynchrone est en cours. - - 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. - Échec d'instance. @@ -2556,27 +585,18 @@ Code d'erreur 0x{0} - - ChangePassword requiert SQL Server 9.0 ou version ultérieure. - La redirection d'instance SSE n'est pas prise en charge pour les instances d'utilisateurs non locaux. L'authentification '{0}' n'est pas prise en charge. - - La méthode d'authentification SQL '{0}' n'est pas prise en charge. - Échec de l'instanciation d'un fournisseur d'authentification ayant le type '{1}' pour '{0}'. Échec de l'instanciation d'un SqlAuthenticationInitializer ayant le type '{0}'. - - Le fournisseur '{0}' a levé une exception durant l'initialisation. - Le fournisseur '{0}' ne prend pas en charge l'authentification '{1}'. @@ -2586,24 +606,15 @@ Impossible de trouver un fournisseur d'authentification pour « {0} ». Installez le package NuGet « Microsoft.Data.SqlClient.Extensions.Azure » (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) pour utiliser les méthodes d'authentification Active Directory Domain Services (Entra ID). - - Échec de lecture de la section de configuration des fournisseurs d'authentification. - Le paramètre « {0} » ne peut pas être Null ou vide. - - L'opération asynchrone est déjà terminée. - Impossible de poursuivre l'exécution de la commande : une opération asynchrone en attente est déjà en cours. Commande non valide envoyée vers ExecuteXmlReader. La commande doit retourner un résultat Xml. - - Les notifications requièrent SQL Server 9.0 ou version ultérieure. - Format de nom en 3 parties non valide pour UdtTypeName. @@ -2697,9 +708,6 @@ Impossible d'initialiser le package SSPI. - - Expiration du Timeout. Le délai d'attente s'est écoulé avant la fin de l'opération ou le serveur ne répond pas. - Le délai d'attente pour la connexion expiré. Le délai d'attente s'est écoulé au début de la phase de préconnexion. Cela peut être dû à un manque de temps au niveau du délai d'attente de connexion. @@ -2745,12 +753,6 @@ Une instance d'utilisateur a été demandée dans la chaîne de connexion, mais le serveur spécifié ne prend pas en charge cette option. - - La validation de l'application du certificat de serveur a échoué. - - - La longueur de données spécifiée, {0}, est supérieure à la longueur maximale autorisée, {1}. - Tentative de lecture non valide lorsque aucune donnée n'est présente. @@ -2775,9 +777,6 @@ La longueur des données « {0} » est inférieure à 0. - - Impossible d'appeler '{0}' si le SqlDataRecord est en lecture seule. - La collection mappée est utilisée et inaccessible pour l'instant ; @@ -2847,21 +846,6 @@ La connexion à la connexion de contexte à l’aide de Microsoft.Data.SqlClient n’est pas prise en charge. - - Les TransactionScopes imbriquées ne sont pas prises en charge. - - - Événement serveur inattendu : {0}. - - - 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. - - - Trop de valeurs. - Le Stream ne prend pas en charge l'écriture. @@ -2886,192 +870,36 @@ Null - - Message - - - Dépassement arithmétique. - - - Erreur : division par zéro. - - - Les données sont null. Cette méthode ou propriété ne peut pas être appelée sur des valeurs Null. - - - L'arithmétique numérique génère une troncation. - Dépassement SqlDateTime. Doit être compris entre 1/1/1753 12:00:00 AM et 31/12/9999 11:59:59 PM. - - Les deux chaînes à concaténer ont un classement différent. - - - Les deux chaînes à comparer ont un classement différent. - - - Valeur d'indicateur non valide. - - - Dépassement lors de la conversion de SqlDecimal en Decimal. - Dépassement de la conversion. - - SqlDateTime non valide. - - - Un fuseau horaire était spécifié. SqlDateTime ne prend pas en charge les fuseaux horaires. - Taille de tableau non valide. Précision numérique/échelle non valide. - - L'entrée n'était pas dans un format correct. - - - Erreur SqlType. - - - Les SqlBytes et SqlChars ne prennent pas en charge de longueur supérieure à 2GB dans cette version. - Erreur lors de la lecture. - - Le Stream a été fermé ou supprimé. - - - La mémoire tampon est manquante. Échec de l'opération de lecture ou d'écriture. - - - Définition sur une longueur non nulle impossible : la valeur actuelle est Null. - - - La mémoire tampon est insuffisante. Échec de l'opération de lecture ou d'écriture. - - - Écriture dans l'offset non nul impossible : la valeur actuelle est Null. - - - Aucune donnée ne peut être écrite à partir d'un offset supérieur à la longueur actuelle. Sinon, la mémoire tampon risque de contenir des données non initialisées. - La taille des données retournées est supérieure à 2Gb. Utilisez le command behavior SequentialAccess pour obtenir l'ensemble de ces données. - - L'argument utilisé pour GetDayOfWeek doit être un entier compris entre 1 et 7. - - - Le Type SQL n'a pas été chargé avec les données. - - - Le Type SQL a déjà été chargé avec les données. - - - Tentative d'accès à un XmlReader fermé non valide. - - - Tentative d'appel de {0} non valide si le flux est fermé. - - - Tentative d'appel de {0} non valide si des données ne peuvent pas être écrites dans le flux. - - - Tentative d'appel de {0} non valide lorsque le flux ne peut pas être lu. - - - Tentative d'appel de {0} non valide lorsque le flux ne peut pas être recherché. - - - La sous-classe n'a pas substituée une méthode requise. - Impossible de créer un normaliseur pour '{0}'. Erreur interne - - Un paramètre de commande doit contenir un texte de commande non null et non vide. - - - La longueur du tableau de paramètres MetaData doit être équivalente à celle de l'argument de tableau ParameterDirection. - - - Erreur de mappage de l'adaptateur de données. - - - Erreur de l'adaptateur de données. - - - Violation de l'accès concurrentiel à la base de données. - Opération abandonnée. Opération abandonnée en raison d'une exception (pour plus de détails, consultez InnerException). - - Indique si Fill appelle DataRow.AcceptChanges. - - - Indique si Update appelle DataRow.AcceptChanges. - - - Indique s'il faut aller au DataRow suivant lorsque Status égale UpdateStatus.ErrorsOccurred pour les événements Update, RowUpdating et RowUpdated. - - - Indique comment l'adaptateur remplit DataTable à partir de DataReader. - - - L'action entreprise lorsqu'une table ou une colonne de TableMappings manque. - - - L'action entreprise lorsqu'une table ou une colonne dans DataSet manque. - - - Comment mapper une table source à une table DataSet. - - - Événement déclenché lorsqu'une erreur récupérable se produit lors d'un remplissage (Fill). - - - Indique si Fill doit retourner les valeurs propres au fournisseur ou les valeurs CLSCompliant communes. - - - DataColumn.ColumnName - - - Nom de la colonne source - respecte la casse. - - - Le nombre d'éléments dans la collection - - - L'objet DataColumnMapping spécifié. - - - Mappages de colonnes individuelles lorsqu'une correspondance de ce mappage de table est identifiée. - - - DataTable.TableName - - - Le nom de table source DataTableMapping. Ce nom respecte la casse. - - - Le nombre d'éléments dans la collection - - - L'objet DataTableMapping spécifié - Utilisé lors de la mise à jour (Update) pour les lignes supprimées dans DataSet. @@ -3090,63 +918,9 @@ Événement déclenché après chaque DataRow lors de la mise à jour (Update). - - Nombre de lignes à traiter par lots simultanément avant l'exécution pour la source de données. - - - Connexion utilisée si les commandes Select/Insert/Update/DeleteCommands n'en possèdent pas encore. - - - Utilisé lors de la mise à jour pour les lignes supprimées du DataTable. - - - Utilisé lors de Update pour les nouvelles lignes du DataTable. - - - Utilisé lors de Fill. - - - Utilisé lors de Update pour les lignes modifiées du DataTable. - - - Indique si Fill doit retourner les valeurs propres au fournisseur ou les valeurs CLSCompliant communes. - - - Indique la manière dont une table source doit être mappée vers DataTable. - - - Indique la manière dont les Insert/Update/DeleteCommands sont générées lorsqu'elles ne sont pas définies par l'utilisateur. - - - Nombre de lignes à traiter par lots simultanément avant l'exécution pour la source de données. - - - Chaîne de connexion utilisée pour la connexion à la source de données. - - - Nom du pilote ODBC à utiliser lors d'une connexion au Data Source. - - - DSN à utiliser lors d'une connexion au Data Source. - - - Lorsque sa valeur est true, indique que le regroupement de connexions managé doit être utilisé. - - - Fichier UDL à utiliser lors d'une connexion au Data Source. - - - Spécifie les OLE DB Services à activer ou à désactiver avec le OleDb Provider. - - - Nom du fournisseur OLE DB à utiliser lors d'une connexion au Data Source. - Le nom de l'application. - - Quand la valeur est true, active l'utilisation de la fonctionnalité asynchrone du fournisseur de données .NET Framework. - Nom du fichier principal, comprenant le nom du chemin complet, d'une base de données pouvant être attachée. @@ -3156,9 +930,6 @@ Lorsque sa valeur est true, indique que l'état de la connexion est rétabli lorsqu'elle est supprimée du pool. - - Lorsque sa valeur est true, indique que la connexion doit provenir du contexte Sql Server. Disponible uniquement en cas d'exécution dans le processus Sql Server. - Nom de l'enregistrement de la langue de SQL Server. @@ -3252,54 +1023,9 @@ Spécifie la méthode d'authentification avec SQL Server. - - Certificat client spécifié pour l'authentification auprès de SQL Server. - - - Les informations utilisées pour connecter une source de données. - - - Valeur du délai d'attente de la connexion actuelle, non définissable dans ConnectionString. - - - Valeur du catalogue de la source de données actuelle, 'Database=X' dans la chaîne de connexion. - - - Source de données actuelle, 'Server=X' dans la chaîne de connexion. - - - Pilote ODBC actif. - - - Version du produit auquel accède le fournisseur ODBC. - - - Les informations utilisées pour connecter une source de données. - - - Valeur du délai d'attente de la connexion actuelle, 'Connect Timeout=X' dans le ConnectionString. - - - Valeur du catalogue de la source de données actuelle, 'Initial Catalog=X' dans la chaîne de connexion. - - - Source de données actuelle, 'Data Source=X' dans la chaîne de connexion. - - - ProgID du fournisseur OLE DB actuel, 'Provider=X' dans la chaîne de connexion. - - - Version du produit auquel accède le fournisseur OLE DB. - Jeton d’accès à utiliser pour l’authentification. - - État de la connexion (synchrone ou asynchrone). La fonctionnalité 'Asynchronous Processing=x' est utilisée dans la chaîne de connexion. - - - Informations utilisées pour la connexion en vue de la réplication. - Informations utilisées pour la connexion à une DataSource, par exemple 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'. @@ -3324,9 +1050,6 @@ Statistiques de collecte de cette connexion. - - Fournisseurs de magasins de clés de chiffrement de colonne personnalisée. - Un guid pour représenter la connexion physique. @@ -3363,99 +1086,9 @@ Inscription automatique aux notifications utilisée par Microsoft SQL Server. - - Indique comment la clause where est automatiquement générée pour les commandes Update et Delete lorsqu'elle n'a pas été spécifiée par l'utilisateur. - - - Indique la position du nom de catalogue dans le nom de table qualifié d'une commande de texte. - - - Caractère séparant le nom du catalogue du reste de l'identificateur dans une commande de texte. - - - Caractère séparant le nom du schéma du reste de l'identificateur dans une commande de texte. - - - Chaîne de préfixe autour des objets sql. - - - Chaîne de suffixe autour des objets sql. - - - DataAdapter pour lequel générer automatiquement des commandes. - - - Utilisez le schéma provenant de DataTable ou SelectCommand. - - - Indique comment la clause set est automatiquement générée pour la commande Update lorsqu'elle n'est pas spécifiée par l'utilisateur. - - - DataAdapter pour lequel il faut générer automatiquement OdbcCommands - - - Caractère utilisé dans une commande de texte en tant que guillemet ouvrant pour les identificateurs qui contiennent des caractères spéciaux. - - - Caractère utilisé dans une commande de texte en tant que guillemet fermant pour les identificateurs qui contiennent des caractères spéciaux. - - - Le DataAdapter pour lequel générer automatiquement OleDbCommands - - - Séparateur décimal utilisé dans les littéraux numériques. - - - La chaîne de préfixe autour des objets sql - - - La chaîne de suffixe autour des objets sql - Le DataAdapter pour lequel générer automatiquement SqlCommands - - Séparateur décimal utilisé dans les littéraux numériques. - - - Caractère utilisé dans une commande de texte en tant que guillemet ouvrant pour les identificateurs qui contiennent des caractères spéciaux. - - - Caractère utilisé dans une commande de texte en tant que guillemet fermant pour les identificateurs qui contiennent des caractères spéciaux. - - - Définition nécessaire uniquement pour les paramètres décimaux et numériques en cas d'utilisation avec les scénarios Prepare, FillSchema et CommandBuilder. - - - Définition nécessaire uniquement pour les paramètres décimaux et numériques en cas d'utilisation avec les scénarios Prepare, FillSchema et CommandBuilder. - - - Le type natif du paramètre. - - - Le type natif du paramètre. - - - Nom du paramètre, par exemple '@p1' - - - Le type natif du paramètre. - - - Nom du serveur pour le type. - - - Offset dans les types de données de longueur variable. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - Le ParameterDirection '{0}' spécifié pour le paramètre '{1}' n'est pas pris en charge. Les Table-valued parameters prennent uniquement en charge ParameterDirection.Input. @@ -3468,9 +1101,6 @@ TypeName spécifié pour le paramètre '{0}'. TypeName ne doit être défini que pour les paramètres Structured. - - Le type SqlDbType.Structured n'est pris en charge que pour plusieurs types avec valeur. - La colonne DateType du champ '{0}' dans la table de schéma a la valeur null. Elle doit avoir une valeur autre que Null. @@ -3483,18 +1113,9 @@ Le nombre de champs de l'enregistrement '{0}' ne correspond pas au nombre de l'enregistrement d'origine. - - plage : 0-8 000 - Une erreur inattendue a été détectée chez le fournisseur de données SqlClient. {0} - - Impossible d'obtenir les Type Info pour {0},{1} - - - Le fournisseur n'est pas parvenu à charger l'assembly suivant : {0} - La propriété UdtTypeName doit être définie pour les paramètres UDT. @@ -3504,72 +1125,9 @@ Le type spécifié n'est pas inscrit sur le serveur cible. {0}. - - Les paramètres UDT ne sont pas autorisés dans la clause WHERE, sauf s'ils font partie de la clé primaire. - - - '{0}' est un type défini par l'utilisateur non valide, raison : {1}. - - - prend en charge les formats en mémoire et ceux définis par l'utilisateur - - - Le format natif ne peut pas être pris en charge. - - - n'implémente pas IBinarySerialize - - - non sérialisable - - - aucun constructeur public - - - n'implémente pas INullable - - - ne dispose d'aucun constructeur public - aucun attribut UDT - - La sérialisation sans mappage n'est pas encore prise en charge. - - - la méthode 'public static x Parse(System.Data.SqlTypes.SqlString)' est manquante - - - la méthode 'public override string ToString()' est manquante - - - la méthode 'public static x Null { get; }' est manquante - - - Le format natif ne prend pas en charge les champs (directement ou via un autre champ) de type '{0}' - - - Type non public - - - Native UDT n'est pas une disposition séquentielle en raison du type '{0}' - - - Native UDT spécifie une taille maximale, en byte - - - le champ '{0}' est marqué comme étant non sérialisé - - - Le type de champ « {0} » est marqué comme disposition explicite qui n’est pas autorisée dans le format natif - - - Plusieurs références d'assembly avec valeur doivent avoir un ID d'assembly non nul. - - - Les types structurés à plusieurs valeurs ne peuvent être utilisés que pour les paramètres, et ne peuvent pas être imbriqués dans un autre type. - Le nom du chemin d'accès n'est pas valide. @@ -3585,9 +1143,6 @@ Échec lors de la tentative de promotion de la transaction. - - Objet de dépendance permettant de recevoir des notifications de requête. - Propriété permettant d'indiquer si cette dépendance est non valide. @@ -3600,9 +1155,6 @@ Permet d'ajouter une commande à un objet de dépendance existant. - - Command déjà associée à un autre objet de dépendance. Remplacement impossible. - Cet objet SqlCommand est déjà associé à un autre objet SqlDependency. @@ -3630,12 +1182,6 @@ Le Timeout spécifié n'est pas valide. Il ne peut pas être < 0. - - Erreur de notification. Type={0}, Info={1}, Source={2}. - - - GetMetaData n'est pas valide pour ce SqlDbType. - Le dbType {0} n'est pas valide pour ce constructeur. @@ -4104,9 +1650,6 @@ Erreur interne. La valeur en texte en clair ne peut pas être null. - - Impossible d’effectuer le chiffrement. À l’issue du chiffrement, {0} octets de texte de chiffrement dépassent la limite maximale autorisée de {1} octets. La valeur en texte clair spécifiée est probablement trop grande (taille du texte en clair : {2} octets). - Erreur interne. La valeur du texte de chiffrement ne peut pas être null. @@ -4224,9 +1767,6 @@ Les informations d'attestation n'ont pas été retournées par SQL Server. Le type d'enclave est '{0}' et l'URL d'attestation d'enclave est '{1}'. - - Impossible d'établir un canal sécurisé. Message d'erreur : {0} - Erreur interne. L'argument '{0}' de valeur Null spécifié durant la construction d'un objet de type '{1}' est vide. '{0}' ne peut pas avoir la valeur null. @@ -4242,15 +1782,6 @@ Spécifie le point de terminaison d'un service d'attestation d'enclave pour vérifier si l'enclave, configurée dans l'instance SQL Server pour les calculs des colonnes de base de données chiffrées via Always Encrypted, est valide et sécurisée. - - Échec de lecture de la section de configuration des fournisseurs d'enclaves. Vérifiez que la section est correctement mise en forme dans le fichier de configuration de votre application. Message d'erreur : {0} - - - Échec de l'instanciation d'un fournisseur d'enclave ayant le type '{1}' pour le nom '{0}'. Message d'erreur : {2} - - - Erreur interne. SqlColumnEncryptionEnclaveProviderName ne peut pas avoir une valeur null ou être vide. - Une erreur s'est produite lors de la lecture du jeu de résultats « {0} ». L'URL d'attestation n'a pas été spécifiée dans la chaîne de connexion, mais la requête nécessite des calculs d'enclave. Le type d'enclave est « {1} ». @@ -4260,9 +1791,6 @@ Erreur interne. Le type d'enclave reçu de la part de SQL Server a une valeur null ou est vide durant l'exécution d'une requête qui nécessite des calculs d'enclave. - - L'exécution d'une requête nécessite des calculs d'enclave. Toutefois, il manque la section relative aux fournisseurs d'enclaves dans la configuration de l'application. - Aucun fournisseur d'enclave pour le type d'enclave « {0} » et le protocole d'attestation « {1} ». Spécifiez le protocole d'attestation approprié dans la chaîne de connexion. @@ -4320,9 +1848,6 @@ Paramètre de chiffrement de colonne par défaut pour toutes les commandes sur la connexion. - - Force le chiffrement du paramètre avant l’envoi de données sensibles vers le serveur. - Objet Dictionary contenant les noms SQL Server et leurs chemins d’accès de clé principale de colonne approuvés. @@ -4332,9 +1857,6 @@ La connexion à une instance SQL Server en miroir à l'aide de l'option de connexion ApplicationIntent ReadOnly n'est pas prise en charge. - - Informations de routage inattendues reçues. - Informations de routage non valides reçues. @@ -4485,9 +2007,6 @@ Authentification non prise en charge spécifiée dans ce contexte : {0} - - L'authentification interactive Active Directory a expiré. L'utilisateur a mis trop de temps à répondre à la demande d'authentification. - Impossible d'utiliser « Authentication=Active Directory Interactive », si la propriété Credential a été définie. @@ -4497,9 +2016,6 @@ ID de processus serveur (SPID) de la connexion active. - - L'authentification Active Directory Device Code Flow a expiré. L'utilisateur a mis trop de temps à répondre à la demande d'authentification. - Impossible de définir la propriété Credential si « Authentication=Active Directory Device Code Flow » est spécifié dans la chaîne de connexion. @@ -4533,9 +2049,6 @@ Seuls les tableaux unidimensionnels sont pris en charge pour l'action demandée. - - Impossible de supprimer l'élément spécifié, car il est introuvable dans la collection. - Nombre non négatif obligatoire. @@ -4563,87 +2076,36 @@ Impossible de définir la propriété AccessTokenCallback si 'Authentication=Active Directory Default' a été spécifié dans la chaîne de connexion. - - {0} retourné {1}. - - - Erreur de protocole : un message reçu contient une signature valide mais n’a pas été chiffré conformément au niveau de protection en application. - - - {0} a échoué avec l’erreur : {1} - Cette méthode n’est pas implémentée par cette classe. - - Le package de sécurité demandé n’est pas pris en charge. - Type physicalConnection incorrect. Une exception s’est produite lors de la tentative de définition du commutateur AppContext '{0}'={1}. - - Type {0} n’est pas pris en charge sur cette plateforme. - Le mode invariant de la globalisation n’est pas pris en charge. - - Authentification impossible à l’aide de Kerberos. Vérifiez que Kerberos a été initialisé sur le client avec « kinit » et qu’un nom de principal du service a été inscrit pour le SQL Server pour autoriser l’authentification Kerberos. - La mot clé « Bibliothèque réseau » n’est pas prise en charge sur cette plateforme, préfixez la « Source de données » avec le protocole souhaité à la place (« tcp: » pour une connexion TCP ou « np: » pour une connexion de canal nommé). Le socket n’a pas levé le '{0}' attendu avec le code d’erreur '{1}'. - - Le serveur tente d’utiliser une fonctionnalité qui n’est pas prise en charge sur cette plateforme. - Le mot-clé '{0}' n’est pas pris en charge sur cette plate-forme. Le fichier System.Transactions.dll actuellement chargé ne prend pas en charge les transactions globales. - - Réception d’un jeton non pris en charge '{0}' lors de la lecture des données du serveur. - - - '{0}' n’est pas un type de handle pris en charge. - SqlFileStream n’est pas pris en charge sur cette plateforme. La Base de données locale n’est pas prise en charge sur cette plateforme. - - Espace de mémoire tampon insuffisant. Obligatoire : {0} réel : {1}. - - - Échec de l’opération GSSAPI avec le statut : {0} (statut mineur : {1}). - - - L’opération GSSAPI a échoué avec une erreur – {0} ({1}). - - - Aucune prise en charge de la liaison de canal sur les systèmes d’exploitation autres que Windows. - - - Le nom cible ne doit pas être vide si les informations d’identification par défaut sont transmises. - - - Le niveau de protection demandé n’est pas pris en charge avec l’implémentation GSSAPI actuellement installée. - - - L’implémentation du serveur n’est pas prise en charge. - - - L’authentification NTLM n’est pas possible avec les informations d’identification par défaut sur cette plateforme. - Lorsqu’elle est utilisée par DataAdapter.Update, la valeur de paramètre DBNull.Value est remplacée par (Int32)1 ou par (Int32)0 si elle n’est pas null. @@ -4668,9 +2130,6 @@ 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. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx index 3b78cfd596..129310c5eb 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Crescente - Indice {0} non valido per {1} con Count={2}. @@ -132,18 +129,12 @@ {0} è già contenuto in un altro {1}. - - {0} è già contenuto in questo {1}. - {0} accetta solo oggetti di tipo {1} non null. Tentativo di rimuovere un {0} non contenuto in {1}. - - {0}.{1} deve essere unico, '{2}' esiste già nella raccolta. - La connessione non è stata chiusa. {0} @@ -171,12 +162,6 @@ Tentativo non valido di chiamare {0} con reader chiuso. - - Impossibile inserire nella transazione, perché la connessione è la connessione primaria per una transazione delegata o promossa. - - - Decrescente - Prevista stringa non vuota per il parametro "{0}". @@ -204,9 +189,6 @@ La proprietà ConnectionString non è stata inizializzata. - - È stata rilevata Exception non CLS. - L'elemento di sicurezza specificato non è un elemento di autorizzazione. @@ -252,9 +234,6 @@ Dati - - StateChange - Aggiornamento @@ -264,48 +243,12 @@ ConnectionState indicante se la connessione è aperta o chiusa. - - Evento generato quando cambia lo stato della connessione. - - - Il tipo generico del parametro. - - - Parametro di input, di output o bidirezionale. - - - proprietà ora design utilizzata per generazione di codici fortemente marcata. - - - Offset dei tipi di dati a lunghezza variabile. - - - Nome del parametro. - - - Dimensione dei tipi di dati a lunghezza variabile (stringhe e matrici). - - - Se utilizzato da DataAdapter.Update, indica il nome della colonna di origine utilizzato per trovare il nome di DataSetColumn negli elementi ColumnMappings. Consente di copiare un valore tra il parametro e una riga di dati. - - - Se utilizzato da DataAdapter.Update (solo UpdateCommand), indica la versione del valore DataRow utilizzato per aggiornare l'origine dati. - - - Se utilizzato da un DataAdapter.Update, il valore del parametro viene modificato da DBNull.Value in (Int32)1 o (Int32)0 se diverso da null. - - - Valore del parametro. - Impossibile compilare la raccolta "{0}". Esecuzione della query SQL non riuscita. Per ulteriori informazioni, vedere l'eccezione interna. Sono state fornite più restrizioni di quante ne possa supportare lo schema richiesto ("{0}"). - - "{2}" non è un valore valido per la restrizione "{1}" della raccolta di schemi "{0}". - La raccolta richiesta ({0}) non è definita. @@ -315,15 +258,6 @@ La raccolta richiesta ({0}) non è supportata da questa versione del provider. - - Manca una delle colonne obbligatorie della tabella DataSourceInformation. - - - la tabella DataSourceInformation deve contenere esattamente una riga. - - - Mancano una o più colonne obbligatorie della raccolta di restrizioni. - Esiste una restrizione per la quale non c'è nessuna riga corrispondente nella raccolta di restrizioni. @@ -342,42 +276,21 @@ Dai metadati XML manca la raccolta "{0}". - - I metadati XML non sono validi. - - - I metadati XML non sono validi. La raccolta {0} deve contenere una colonna stringa {1}. - I metadati XML non sono validi. La colonna {1} della raccolta {0} deve contenere una stringa non vuota. - - Azione - - - Comportamento - Riempimento InfoMessage - - Mapping - StatementCompleted - - UDT - Notifica - - Schema - XML @@ -393,9 +306,6 @@ Pooling - - ConnectionString denominata - Sicurezza @@ -408,2073 +318,204 @@ Resilienza connessione - - Raccolta che contiene le informazioni utente personalizzate. - - - Indica se il confronto di stringhe all'interno del DataSet distingue tra maiuscole e minuscole. - - - Nome di questo DataSet. - - - Indica una "vista" personalizzata dei dati contenuti nel DataSet. Tale vista consente di filtrare, eseguire ricerche ed esplorare all'interno della vista dei dati personalizzata. - - - Indica se è necessario osservare regole di vincolo. - - - Indica la presenza di errori nel DataSet. - - - Indica le impostazioni locali in base a cui confrontare le stringhe all'interno del DataSet. - - - Indica lo spazio dei nomi URI XML dell'elemento radice a cui si riferisce questo DataSet. - - - Indica il prefisso dello spazio dei nomi utilizzato per questo DataSet. - - - Raccolta che contiene le relazioni per questo DataSet. - - - Raccolta che contiene le tabelle per questo DataSet. - - - Generato quando è impossibile unire gli schemi per due tabelle con lo stesso nome. - - - Avviene quando Initialization è completato. - - - Rappresenta una cache di dati in memoria. - - - Indica se il confronto di stringhe all'interno della tabella distingue tra maiuscole e minuscole. - - - Restituisce le relazioni figlio per questa tabella. - - - Raccolta che contiene le colonne per questa tabella. - - - Raccolta che contiene i vincoli per questa tabella. - - - Indica il DataSet a cui appartiene questa tabella. - - - DataView predefinito per la tabella. - - - Espressione utilizzata per calcolare il valore con associazione a dati di questa riga. - - - Restituisce informazioni su eventuali errori della tabella. - - - Indica le impostazioni locali in base a cui confrontare le stringhe all'interno della tabella. - - - Indica una dimensione iniziale per questa tabella. - - - Indica lo spazio dei nomi URI XML per gli elementi contenuti in questa tabella. - - - Indica il prefisso dello spazio dei nomi utilizzato per questa tabella nella rappresentazione XML. - - - Restituisce le relazioni padre per questa tabella. - - - Indica una o più colonne che rappresentano la chiave primaria di questa tabella. - - - Indica la raccolta che contiene le righe di dati di questa tabella. - - - Indica il nome utilizzato per cercare questa tabella nella raccolta Tables di un DataSet. - - - Generato in seguito alla modifica di una riga della tabella. - - - Generato durante la modifica della riga affinché il gestore dell'evento possa cambiare o annullare la modifica. L'utente può modificare i valori della riga e deve generare un'eccezione per annullare la modifica. - - - Generato in seguito all'eliminazione di una riga della tabella. - - - Generato quando una riga della tabella viene contrassegnata per l'eliminazione. Generare un'eccezione per annullare l'eliminazione. - - - Generato quando si propone un valore per questa colonna. L'utente può modificare il valore proposto e deve generare un eccezione per annullare la modifica. - - - Generato quando si modifica un valore di questa colonna. - - - Si verifica prima di eliminare tutte le righe dalla tabella. - - - Si verifica dopo che tutte le righe nella tabella sono state eliminate correttamente. - - - Si verifica dopo che è stata creata l'istanza di un nuovo DataRow. - - - Nome utilizzato per cercare questa relazione nella raccolta Relations di un DataSet. - - - Indica le colonne figlio di questa relazione. - - - Indica le colonne padre di questa relazione. - - - Indica se le relazioni sono annidate. - - - Indica il tipo di azioni a cascata associato a questa relazione per le eliminazioni. - - - Indica il tipo di azioni a cascata associato a questa relazione per gli aggiornamenti. - - - Indica il tipo di azioni a cascata associato a questa relazione per la conferma o l'annullamento delle modifiche. - - - Indica le colonne figlio di questo vincolo. - - - Indica le colonne padre di questo vincolo. - - - Indica la tabella figlio del vincolo. - - - Indica le colonne di questo vincolo. - - - Indica se il vincolo è una chiave primaria. - - - Indica il nome del vincolo. - - - Indica la tabella del vincolo. - - - Indica se sono consentiti valori null nella colonna. - - - Indica se il valore della colonna viene incrementato automaticamente quando si aggiungono nuove righe alla tabella. La colonna deve essere di tipo Int16, Int32 o Int64. - - - Indica il valore iniziale di una colonna AutoIncrement. - - - Indica l'incremento utilizzato da una colonna AutoIncrement. - - - Indica l'etichetta di interfaccia predefinita della colonna. - - - Indica il nome usato per cercare questa colonna nella raccolta Columns di un oggetto DataTable. - - - Restituisce la DataTable a cui appartiene questa colonna. - - - Indica il tipo di dati memorizzati in questa colonna. - - - Indica il valore predefinito che assume la colonna quando si aggiungono nuove righe alla tabella. - - - Indica il valore che questa colonna calcola per ciascuna riga derivandolo dai valori di altre colonne invece di utilizzare l'input dell'utente. - - - Indica la modalità di persistenza di questa colonna nell'XML: attributo, elemento, nodo di contenuto semplice o nessuna. - - - Indica l'URI XML per elementi o attributi archiviati in questa colonna. - - - Indica Prefix utilizzato per questa DataColumn nella rappresentazione xml. - - - Indica l'indice che identifica questa colonna nella raccolta Columns. - - - Indica se questa colonna consente le modifiche dopo che è stata aggiunta una riga alla tabella. - - - Indica se in questa colonna occorre impostare restrizioni sui valori contenuti nelle diverse righe della tabella affinché siano univoci. - - - Indica la lunghezza massima del valore che può essere inserito in questa colonna. - - - Indica il DateTimeMode di questa DataColumn. - - - Indica se questo DataView e l'interfaccia utente ad esso associata consentono le eliminazioni. - - - Indica se questo DataView e l'interfaccia utente ad esso associata consentono le modifiche. - - - Indica se questo DataView e l'interfaccia utente ad esso associata consentono l'aggiunta di righe. - - - Restituisce il numero di elementi presenti in questa vista. - - - Restituisce un puntatore al DataViewManager proprietario del DataSet (se esiste). - - - Indica se la vista è aperta. - - - Indica un'espressione utilizzata per filtrare i dati restituiti da questo DataView. - - - Indica le versioni dei dati restituiti da questo DataView. - - - Indica i nomi della colonna e l'ordine in cui vengono restituiti i dati da questo DataView. - - - Indica se utilizzare l'ordinamento predefinito, quando la proprietà Sort non è impostata. - - - Indica la tabella da cui questo DataView recupera i dati. - - - Indica che i dati restituiti da questo DataView hanno subito modifiche. - - - Indica l'origine dati del DataViewManager. - - - Indica le impostazioni di ordinamento/filtro/stato per le tabelle del DataSet corrispondente. - - - Il DataSet non supporta 'union' e 'list' come simpleType. - - - Sintassi {0} non valida: attributo richiesto '{1}' mancante. - - - Valore '{1}' non valido per l'attributo '{0}'. - - - Il valore dell'attributo '{0}' deve essere '{1}' o '{2}'. - - - Impossibile trovare ElementType name='{0}'. - - - Nome tabella padre mancante nella relazione '{0}'. - - - Nome tabella figlio mancante nella relazione '{0}'. - - - Chiave tabella padre mancante nella relazione '{0}'. - - - Chiave tabella figlio mancante nella relazione '{0}'. - - - Tipo di dati non definito: '{0}'. - - - Tipo di dati non definito. - - - Selezione XPath non valida all'interno del nodo campo. Impossibile trovare {0}. - - - Selezione XPath non valida all'interno del nodo selettore: {0}. - - - Nodo 'Key' non valido all'interno del vincolo denominato {0}. - - - Nome di vincolo {0} già utilizzato nello schema. - - - Impossibile convertire "{0}" nel tipo "{1}". - - - Parte "{0}" mancante nel vincolo "{1}" denominato "{2}". - - - Definizione di Relation non valida: chiavi di lunghezza diversa. - - - Il DataSet non consente il riferimento circolare nel ComplexType denominato '{0}'. - - - DataSet non è in grado di creare un'istanza di ComplexType astratto per il nodo {0}. - - - Errore del convertitore di più destinazioni durante la scrittura di un Xml Schema. Per dettagli, vedere l'eccezione interna. - - - Errore del convertitore di più destinazioni durante la scrittura di un Xml Schema. È stata restituita una stringa null o vuota. - - - Dichiarazione '{0}' duplicata. - - - Impossibile caricare diffGram. Tabella "{0}" mancante nel DataSet di destinazione. - - - Impossibile caricare diffGram. Nodo 'sql' mancante. - - - Nome di colonna '{0}' definito per diversi tipi di mapping. - - - Prefisso '{0}' non valido perché contiene caratteri speciali. - - - Riferimento circolare nella tabella autoannidata '{0}'. - - - Il DataSet non può espandere entità. Utilizzare XmlValidatingReader e impostare la proprietà EntityHandling in modo appropriato. - - - Il tipo '{0}' non implementa l'interfaccia IXmlSerializable, pertanto non può procedere con la serializzazione. - - - Non è possibile continuare con la deserializzazione. I dati non implementano IXMLSerializable, pertanto il polimorfismo non è supportato. - - - DataTable non supporta l'inferenza di schema proveniente da Xml. - - - Impossibile continuare la serializzazione di DataTable "{0}" poiché contiene un DataRow con più righe padre nello stesso Foreign Key. - - - Attributo IsDataSet assente nello Schema input. - - - Non è possibile determinare l'elemento DataSet. L'attributo IsDataSet esiste più di una volta. - - - DataSet non serializzerà i tipi che implementano IDynamicMetaObjectProvider ma che non implementano anche IXmlSerializable. - - - Funzionalità non implementata. {0}. - - - Errore di sintassi: operando mancante dopo l'operatore '{0}'. - - - Tipo non corrispondente nell'espressione '{0}'. - - - Espressione troppo complessa. - - - Impossibile trovare la colonna [{0}]. - - - L'espressione contiene una costante di stringa non valida: {0}. - - - L'espressione contiene una chiamata di funzione indefinita: {0}(). - - - Errore di sintassi nell'espressione. - - - Numero di argomenti non valido: funzione {0}(). - - - Nell'espressione manca la parentesi di chiusura. - - - Impossibile interpretare il token '{0}' nella posizione {1}. - - - Previsto {0}. Il token nella posizione {2} è invece {1}. - - - Non è possibile eseguire la conversione da {0} a {1}. - - - Non è possibile convertire il valore '{0}' nel tipo {1}. - - - Nome di colonna non valido: [{0}]. - - - L'espressione contiene una costante di data non valida: '{0}'. - - - Nell'elenco di espressioni per l'operatore IN è possibile specificare solo espressioni costanti. - - - Errore nell'operatore Like: modello di stringa '{0}' non valido. - - - Errore di sintassi: le voci che seguono la parola chiave IN devono essere separate da virgole e racchiuse tra parentesi. - - - Tipo non corrispondente nell'argomento della funzione: {0}(), argomento {1}, previsto {2}. - - - Tipo non corrispondente nell'argomento della funzione: {0}(), argomento {1}, previsto uno dei tipi Integer. - - - Impossibile eseguire l'operazione '{0}' su {1} e {2}. - - - Operatore '{0}' ambiguo su operandi di tipo '{1}' e '{2}'. Impossibile combinare tipi con e senza segno. Utilizzare la funzione Convert() esplicita. - - - Errore di sintassi: la parola chiave IN deve essere seguita da un elenco non vuoto di espressioni separate da virgole e racchiuse tra parentesi. - - - L'espressione contiene l'operatore non supportato '{0}'. - - - L'espressione contiene un nome non valido: '{0}'. - - - Errore di sintassi: operando mancante prima dell'operatore '{0}'. - - - L'espressione presenta troppe parentesi di chiusura. - - - La tabella [{0}] è parte di più relazioni. Occorre specificare esplicitamente un nome di relazione nell'espressione '{1}'. - - - Errore di sintassi nell'argomento di aggregazione: previsto un argomento a colonna singola con possibile qualificatore 'Child'. - - - Riferimento non associato nell'espressione di aggregazione '{0}'. - - - Impossibile valutare un'espressione non costante senza riga corrente. - - - Riferimento non associato nell'espressione '{0}'. - - - '{0}' non è un'espressione di aggregazione. Impossibile valutarla. - - - L'espressione filtro '{0}' non restituisce un valore booleano. - - - Nome di tipo non valido: '{0}'. - - - Errore di sintassi nell'espressione di ricerca: prevista la parola chiave 'Parent' seguita da un argomento che individua una sola colonna con possibile qualificatore di relazione: Parent[(<nome_relazione>)].<nome_colonna>. - - - Per richiamare DataFilter occorre specificare una riga o una tabella. - - - Argomento {0}() non compreso nell'intervallo. - - - Errore di sintassi: utilizzo non valido dell'operatore 'Is'. Sintassi corretta: <espressione> Is [Not] Null. + + Offset e lunghezza eccedono i limiti della matrice oppure il conteggio è maggiore del numero di elementi presenti dall'indice alla fine della raccolta di origine. - - Valore troppo grande o troppo piccolo per il tipo '{0}'. + + Non è possibile convertire oggetti di tipo '{0}' in oggetti di tipo '{1}'. - - Errore di divisione per zero. + + L'unico valore accettabile per la proprietà "{0}" è "{1}". - - Impossibile trovare la relazione padre "{0}". + + I valori accettabili per la proprietà "{0}" sono "{1}" oppure "{2}". - - L'argomento 'hours' non è compreso nell'intervallo. Il valore deve essere compreso tra -14 e +14. + + I valori specificati per QuotePrefix e QuoteSuffix non corrispondono. - - L'argomento 'minutes' non è compreso nell'intervallo. Il valore deve essere compreso tra -59 e +59. + + La lunghezza dell'argomento '{0}' supera il limite di '{1}'. - - L'intervallo fornito per l'ora supera il totale di 14 ore. + + Valore non valido per la chiave '{0}'. - - La proprietà Kind dell'argomento DateTime fornito non corrisponde agli argomenti 'hours' e 'minutes'. + + L'utilizzo della chiave "{0}" richiede la presenza della chiave "{1}". - - Un DataColumn di tipo '{0}' non supporta espressioni. + + La lunghezza del valore per la chiave '{0}' supera il limite di '{1}'. - - Impossibile attivare i vincoli. Una o più righe contengono valori che violano il vincolo non-null, unique o foreign-key. + + Parola chiave non supportata: '{0}'. - - La raccolta stessa non è modificabile. + + Errore interno del provider di dati .NET Framework {0}. - - Il nome '{0}' corrisponde ad almeno due nomi nell'oggetto Collection, ma con una diversa combinazione di maiuscole e minuscole. + + {0} "{1}". - - Il nome "{0}" corrisponde ad almeno due nomi nell'oggetto raccolta, ma con spazi dei nomi diversi. + + {0} "{1}", uso incorretto delle virgolette. - - Offset e lunghezza eccedono i limiti della matrice oppure il conteggio è maggiore del numero di elementi presenti dall'indice alla fine della raccolta di origine. + + {0} "{1}", il limite corrente di "{2}" è insufficiente. - - Argomento '{0}' non compreso nell'intervallo. + + SqlBulkCopy.WriteToServer non riuscito perché SqlBulkCopy.DestinationTableName è un nome a più parti non valido - - L'argomento '{0}' non può essere null. + + Elaborazione dei risultati da SQL Server fallita a causa di un nome a più parti non valido - - L'argomento "{0}" contiene un valore null. + + SqlParameter.UdtTypeName è un nome a più parti non valido - - Impossibile trovare la colonna {0}. + + SqlParameter.TypeName è un nome a più parti non valido - - La colonna '{0}' appartiene già a questa DataTable. + + SqlCommand.DeriveParameters fallito perché il valore di proprietà SqlCommand.CommandText è un nome a più parti non valido - - La colonna '{0}' appartiene già a un'altra DataTable. + + La connessione a un'istanza di SQL Server con mirroring utilizzando l'opzione di connessione MultiSubnetFailover non è supportata. - - Impossibile avere più di una colonna SimpleContent in una DataTable. + + Il valore {1} dell'enumerazione {0} non è supportato dal metodo {2}. - - Impossibile aggiungere una colonna SimpleContent a una tabella contenente colonne di elementi o relazioni annidate. + + Il valore di enumerazione di {0}, {1}, non è supportato dal provider di dati .NET Framework SqlClient. - - Una colonna denominata '{0}' appartiene già a questa DataTable. + + {0}: la proprietà CommandText non è stata inizializzata - - Impossibile aggiungere colonna con nome '{0}': una tabella annidata con lo stesso nome appartiene già alla DataTable corrente. + + {0}: la proprietà Connection non è stata inizializzata. - - Una colonna con nome '{0}' appartiene già alla DataTable corrente. Impossibile impostare una tabella annidata con lo stesso nome. + + {0} richiede una oggetto Connection aperto e disponibile. {1} - - Impossibile rimuovere una colonna che non appartiene a questa tabella. + + Stored procedure '{0}' inesistente. - - Impossibile rimuovere questa colonna, perché è parte della chiave primaria. + + La transazione non è associata alla connessione corrente oppure è stata completata. - - Impossibile rimuovere questa colonna, perché è parte della chiave padre della relazione {0}. + + La transazione associata alla connessione corrente è stata completata, ma non è stata eliminata. Per poter utilizzare la connessione per eseguire istruzioni SQL, è necessario eliminare la transazione. - - Impossibile rimuovere questa colonna, perché è una parte del vincolo {0} imposto sulla tabella {1}. + + {0} richiede che il comando abbia una transazione quando la connessione assegnata al comando è in una transazione locale in sospeso. La proprietà Transaction del comando non è stata inizializzata. - - Impossibile rimuovere questa colonna, perché è parte di un'espressione: {0} = {1}. + + A {0} è già associato un DataReader aperto, che deve prima essere chiuso. - - Impossibile impostare la proprietà AutoIncrement per una colonna calcolata. + + DeriveParameters {0} supporta solo CommandType.StoredProcedure, non CommandType.{1}. - - Impossibile impostare la proprietà AutoIncrement per una colonna con DefaultValue impostato. - - - Impossibile impostare DefaultValue su una colonna AutoIncrement. - - - Il valore di AutoIncrementStep deve essere diverso da zero. - - - Quando è parte di DataTable, ColumnName è obbligatorio. - - - Impossibile modificare il DataType di una colonna contenente dati. - - - La colonna richiede un DataType valido. - - - Il DefaultValue della colonna {0} è di tipo {1} e non può essere convertito in {2}. - - - Il DefaultValue della colonna è di tipo {0} e non può essere convertito in {1}. - - - Il DefaultValue della colonna {0} è di tipo {1}, ma la colonna è di tipo {2}. - - - Impossibile modificare la proprietà ReadOnly della colonna espressioni. - - - Impossibile modificare la proprietà Unique della colonna espressioni. - - - Impossibile impostare la proprietà Expression di una colonna con AutoIncrement o Unique. - - - Impossibile impostare la proprietà Expression, perché la colonna non può essere impostata su ReadOnly. - - - Impossibile impostare la proprietà Expression sulla colonna {0}, perché è parte di un vincolo. - - - Impossibile creare un vincolo basato sulla colonna Expression {0}. - - - Impossibile impostare la proprietà Expression, perché contiene riferimenti circolari. - - - La colonna '{0}' contiene valori null. - - - La colonna '{0}' non accetta valori nulls. - - - La colonna '{0}' è di sola lettura. - - - Column '{0}' contiene valori non univoci. - - - La colonna '{0}' non appartiene alla tabella {1}. - - - La colonna deve appartenere a una tabella. - - - Impossibile archiviare <{0}> nella colonna {1}. Il tipo previsto è {2}. - - - Impossibile impostare Column '{0}' come null. Utilizzare invece DBNull. - - - Impossibile impostare la colonna "{0}". Il valore viola il limite MaxLength della colonna. - - - MaxLength è applicabile solo ai dati di tipo stringa. Non è possibile impostare la proprietà MaxLength di Column '{0}' come numero non negativo. - - - Impossibile impostare su '{1}' la proprietà MaxLength di Column '{0}'. La tabella contiene almeno una stringa di lunghezza superiore al nuovo limite. - - - Impossibile impostare la proprietà MaxLength di Column '{0}'. Column è SimpleContent. - - - Impossibile impostare su {1} la proprietà DataType di Column '{0}'. Column è SimpleContent. - - - Impossibile impostare su SimpleContent la proprietà MappingType della colonna '{0}'. DataType di Column è {1}. - - - Column '{0}' supera il limite MaxLength. - - - DBNull.Value non consentito per la colonna '{0}'. - - - Impossibile modificare la proprietà Namespace di Column '{0}'. Column è SimpleContent. - - - Impossibile modificare con il tipo "{0}" la proprietà AutoIncrement di una DataColumn contenente dati. - - - La colonna "{0}" non appartiene alla tabella sottostante "{1}". - - - La DataColumn con il tipo "{0}" è una complexType. Impossibile serializzare il valore di un tipo complex come Attribute - - - La proprietà DateTimeMode può essere impostata solo su DataColumns di tipo DateTime. - - - "{0}" è un valore di DataSetDateTime non valido. - - - Impossibile modificare la proprietà DateTimeMode da '{0}' a '{1}' di una colonna contenente dati. - - - Type "{0}" non contiene proprietà o campi Null statici. - - - Il tipo "{0}" non implementa IRevertibleChangeTracking, pertanto non può procedere con RejectChanges(). - - - È possibile chiamare SetAdded e SetModified sui DataRows con DataRowState Unchanged. - - - L'ordinale '{0}' supera il numero massimo. - - - DataSet non supporta System.Nullable<>. - - - Impossibile modificare in una stringa vuota il nome di un vincolo contenuto in ConstraintCollection. - - - Impossibile imporre vincoli sul vincolo {0}. - - - La colonna '{0}' deve contenere valori univoci. Il valore '{1}' è già presente. - - - Il vincolo '{0}' non appartiene a questa DataTable. - - - Impossibile trovare il vincolo {0}. - - - Il vincolo corrisponde al vincolo {0} già presente nella raccolta. - - - Un Constraint denominato '{0}' appartiene già a questo DataTable. - - - Queste colonne non contengono valori univoci. - - - Queste colonne non puntano a questa tabella. - - - Impossibile attivare questo vincolo. Non tutti i valori hanno valori padre corrispondenti. - - - Impossibile aggiungere il vincolo, perché ForeignKey non appartiene alla tabella {0}. - - - Impossibile rimuovere un vincolo che non appartiene a questa tabella. - - - Non è possibile rimuovere il vincolo univoco '{0}'. Rimuovere prima il vincolo '{1}' della chiave esterna. - - - Impossibile rimuovere questa colonna. I vincoli imposti sulla relazione {0} comporterebbero ripercussioni sulle righe figlio. - - - Impossibile effettuare questa modifica. I vincoli imposti sulla relazione {0} comporterebbero ripercussioni sulle righe figlio. - - - Impossibile cancellare la tabella {0}. I vincoli imposti da ForeignKeyConstraint {1} comporterebbero ripercussioni sulle righe figlio in {2}. - - - ForeignKeyConstraint {0} richiede l'esistenza dei valori delle chiavi figlio ({1}) nella tabella padre. - - - Proprietà non accessibile. '{0}'. - - - Questa riga ha righe figlio. Impossibile rimuoverla per l'esistenza di vincoli sulla relazione {0}. - - - Impossibile aggiungere vincoli per la chiave primaria, perché la chiave primaria è già impostata per la tabella. - - - Impossibile aggiungere un vincolo a DataTable '{0}' che è una tabella figlia con due relazioni annidate. - - - Impossibile creare una chiave da colonne appartenenti a tabelle diverse. - - - Impossibile avere 0 colonne. - - - Impossibile avere più di {0} colonne. - - - Impossibile creare una chiave quando la stessa colonna è elencata più di una volta: '{0}' - - - Impossibile rimuovere il vincolo univoco perché è la chiave primaria di una tabella. - - - Impossibile rimuovere il vincolo univoco perché è la chiave primaria della tabella {0}. - - - Le colonne padre e le colonne figlio presentano tabelle di tipo non corrispondente. - - - ParentKey e ChildKey sono identiche. - - - La lunghezza di ParentColumns e ChildColumns deve essere uguale. - - - La lunghezza di ParentColumns e ChildColumns deve essere diversa da zero. - - - La riga non appartiene allo stesso DataSet di questa relazione. - - - RelationName è richiesto quando è parte di un DataSet. - - - GetChildRows richiede una riga il cui oggetto Table sia {0}. L'oggetto Table della riga specificata è invece {1}. - - - Per poter essere aggiunta a questo DataSet, la relazione deve connettere due tabelle del DataSet. - - - GetParentRow richiede una riga la cui tabella sia {0}. La tabella della riga specificata è invece {1}. - - - SetParentRow richiede una riga figlio la cui tabella sia {0}. La tabella della riga specificata è invece {1}. - - - Impossibile avere una relazione tra tabelle di DataSet diversi. - - - Impossibile creare una relazione tra tabelle di DataSet diversi. - - - Esiste già una relazione per queste colonne figlio. - - - Questa relazione non appartiene a questa raccolta di relazioni. - - - La relazione appartiene già a un DataSet diverso. - - - La relazione appartiene già a questo DataSet. - - - Un oggetto Relation denominato '{0}' appartiene già a questo DataSet. - - - L'oggetto Relation {0} non appartiene a questo DataSet. - - - Impossibile trovare la relazione {0}. - - - Impossibile creare una raccolta su una tabella null. - - - La tabella le cui relazioni sono visualizzate da questa raccolta è stata rimossa dal relativo DataSet. - - - Impossibile aggiungere una relazione alla raccolta ParentRelation di questa tabella se non è la tabella figlio. - - - Impossibile aggiungere una relazione alla raccolta ChildRelation della tabella se non è la tabella padre. - - - Impossibile impostare la proprietà 'Nested' su false per questa relazione. - - - La stessa tabella '{0}' non può essere la tabella figlia in due relazioni annidate. - - - La tabella ({0}) non può essere figlia di se stessa nelle relazioni annidate. - - - Impossibile aggiungere DataRelation o Constraint con impostazioni Locale o CaseSensitive delle tabelle padre diverse da quelle delle tabelle figlio. - - - Impossibile creare una DataRelation se non sono presenti colonne padre o figlio in un DataSet. - - - La tabella annidata "{0}" che eredita il proprio spazio dei nomi non può avere più tabelle padre in spazi dei nomi differenti. - - - La tabella annidata '{0}' con spazio dei nomi vuoto non può avere più tabelle padre in spazi dei nomi diversi. - - - La riga non appartiene allo stesso DataSet di questa relazione. - - - Impossibile eseguire questa operazione su una riga non presente nella tabella. - - - Questa relazione e la riga figlio non appartengono allo stesso DataSet. - - - Impossibile modificare un valore proposto nell'evento RowChanging. - - - Impossibile chiamare EndEdit() dall'interno di un evento OnRowChanging. - - - Impossibile chiamare BeginEdit() dall'interno dell'evento RowChanging. - - - Impossibile chiamare CancelEdit() dall'interno di un evento OnRowChanging. Generare exception per annullare questo aggiornamento. - - - Impossibile chiamare Delete dall'interno di un evento OnRowDeleting. Generare exception per annullare questa eliminazione. - - - La matrice di input è più lunga del numero di colonne di questa tabella. - - - Nessun dato corrente a cui accedere. - - - Nessun dato originale a cui accedere. - - - Nessun dato proposto a cui accedere. - - - Questa riga è stata rimossa da una tabella e non contiene alcun dato. BeginEdit() consentirà di inserirvi nuovi dati. - - - Impossibile accedere alle informazioni sulla riga eliminata tramite la riga. - - - Version deve essere Original, Current o Proposed. - - - Nessuna riga alla posizione {0}. - - - La posizione di inserimento della riga, {0}, non è valida. - - - Il valore {0} di rowOrder per la tabella denominata '{1}' è stato rilevato due volte. - - - Valori mancanti nella sequenza rowOrder della tabella '{0}'. - - - Il DataRow specificato non si trova nel DataRowCollection corrente. - - - La riga appartiene già a un'altra tabella. - - - La riga appartiene già a questa tabella. - - - La riga è già stata eliminata. Impossibile eliminarla di nuovo. - - - Riga vuota. - - - Impossibile rimuovere una riga già rimossa. - - - Una riga figlio ha più padri. - - - Modello di bit dello stato della riga non riconosciuto. - - - Impossibile modificare il nome del DataSet in una stringa vuota. - - - Il nome '{0}' non è valido. Il nome di un DataSet non può essere uguale a quello del DataTable. - - - Spazio dei nomi dello schema non valido. Utilizzare {0}. - - - Impossibile modificare la proprietà CaseSensitive o Locale. In seguito alla modifica, almeno un DataRelation o Constraint avrebbe impostazioni Locale o CaseSensitive diverse da quelle delle tabelle correlate. - - - La proprietà SchemaSerializationMode può essere impostata soltanto se ignorata dal DataSet derivato. - - - Le colonne PrimaryKey non appartengono a questa tabella. - - - Impossibile aggiungere una relazione annidata o una colonna di elementi a una tabella contenente una colonna SimpleContent. - - - TableName è obbligatorio quando è parte di un DataSet. - - - DataTable dispone già di una colonna di contenuto semplice. - - - La tabella non ha una chiave primaria. - - - {0} non è una stringa Sort valida. - - - Impossibile serializzare il DataTable. Un DataTable utilizzato in una o più espressioni DataColumn non è un discendente della DataTable corrente. - - - È possibile rimuovere questa DataTable solo come parte di DataSet. Una o più colonne Expression hanno riferimenti ad altre DataTable. - - - Impossibile avere valori differenti della proprietà di formattazione remota per DataSet e DataTable. - - - Impossibile serializzare la DataTable. Nome di DataTable non impostato. - - - Un DataTable di nome '{0}' appartiene già a questo DataSet. - - - Una DataTable denominata "{0}" con lo stesso Namespace "{1}" appartiene già a questo DataSet. - - - La tabella ({0}) non può essere figlia di se stessa in una relazione annidata: il nome del DataSet è in conflitto con quello della tabella. - - - Il nome '{0}' non è valido. Il nome di un DataTable non può essere uguale a quello del DataSet. - - - DataTable già appartenente a un DataSet diverso. - - - DataTable già appartenente a questo DataSet. - - - La tabella {0} non appartiene a questo DataSet. - - - Impossibile trovare la tabella {0}. - - - Impossibile rimuovere una tabella con relazioni. Rimuovere prima le relazioni. - - - Impossibile rimuovere la tabella {0} perché esiste un riferimento ad essa in ForeignKeyConstraint {1}. Impossibile rimuovere la tabella senza prima rimuovere il vincolo. - - - La DataTable "{0}" non corrisponde ad alcuna DataTable nell'origine. - - - Nel DataSet di destinazione manca la definizione per {0}. - - - Nel DataSet di destinazione manca {0} {1}. - - - Conflitto tra le proprietà <target>.{0} e <source>.{0} : proprietà DataType non corrispondente. - - - <target>.PrimaryKey e <source>.PrimaryKey hanno Length differenti. - - - Errata corrispondenza di colonne nella chiave PrimaryKey tra <target>.{0} e <source>.{1}. - - - Non è possibile unire la relazione {0} perché le chiavi hanno colonne non corrispondenti. - - - Nella tabella di destinazione {0} manca la definizione per la colonna {1}. - - - La colonna PrimaryKey {0} non esiste nella tabella di origine. - - - Il parametro RowStates deve essere impostato su una combinazione valida di valori appartenenti all'enumerazione DataViewRowState. - - - La funzione Trova individua una riga in base a un criterio di ordinamento, ma non ne è stato specificato nessuno. - - - Previsto il valore {0} per la chiave che si sta indicizzando. Ricevuto valore {1}. - - - Utilizzo non valido della funzione {0}() e del tipo di aggregazione: {1}. - - - Tipo di archiviazione non valido: {0}. - - - La persistenza DataSet Xml non supporta il valore '{0}' come Char. Utilizzare un archivio Byte. - - - Il tipo di valore non è conforme al tipo di colonna - - - Il tipo "{0}" non implementa l'interfaccia IComparable. Impossibile eseguire il confronto. - - - Impossibile impostare {0}. - - - Impossibile modificare DataSet su un DataViewManager che è già la vista predefinita di un DataSet. - - - RowStateFilter non può mostrare ModifiedOriginals e ModifiedCurrents contemporaneamente. - - - Impossibile modificare la proprietà Table su un DefaultView o un DataView di un DataViewManager. - - - Impossibile modificare la proprietà DataSet dopo che è stata impostata. - - - DataSet deve essere impostata prima dell'utilizzo di DataViewManager. - - - Impossibile modificare la proprietà Table dopo che è stata impostata. - - - DataTable deve essere impostata prima dell'utilizzo di DataView. - - - Impossibile eseguire l'associazione a DataTable senza nome. - - - Impossibile impostare un oggetto in questo elenco. - - - Impossibile chiamare AddNew su un DataView in cui AllowNew è false. - - - DataView non aperto. - - - La relazione non interessa la tabella a cui fa riferimento questo DataView. - - - Impossibile effettuare un'eliminazione su un DataSource in cui AllowDelete è false. - - - Impossibile effettuare una modifica su un DataSource in cui AllowEdit è false. - - - L'indice {0} è negativo oppure superiore al totale delle righe. - - - Impossibile aggiungere oggetti esterni all'elenco. - - - Impossibile cancellare questo elenco. - - - Impossibile inserire oggetti esterni nell'elenco. - - - Impossibile rimuovere oggetti non presenti nell'elenco. - - - {0} non è né una DataColumn, né una DataRelation per la tabella {1}. - - - Min ({0}) deve essere minore o uguale a max ({1}) in un oggetto Range. - - - Intervallo null. - - - MinimumCapacity non può essere negativa. - - - Impossibile generare l'identificatore per il nome '{0}'. - - - Ci sono più tabelle con lo stesso nome "{0}" (anche se lo spazio dei nomi è diverso). - - - Colonna '{0}': il tipo '{1}' non può essere null. - - - Colonna '{0}': il tipo '{1}' non ha costruttore senza parametri. - - - Colonna '{0}': il tipo '{1}' non ha costruttore con argomento stringa. - - - Non è possibile convertire oggetti di tipo '{0}' in oggetti di tipo '{1}'. - - - Data Exception. - - - Constraint Exception. - - - Righe eliminate inaccessibili. - - - Nome duplicato non consentito. - - - Operazione non supportata nell'evento RowChanging. - - - Vincolo non valido. - - - Chiave primaria mancante. - - - Valore Null non consentito. - - - La colonna è di sola lettura. - - - Riga non trovata nella tabella. - - - Versione non trovata. - - - ReadOnly Data è Modified. - - - DataTableReader non valido per la DataTable "{0}" corrente. - - - Lo schema della DataTable "{0}" corrente in DataTableReader è cambiato. DataTableReader non è valido. - - - Impossibile creare DataTableReader. Nessuna DataTable presente nel DataSet. - - - Impossibile creare DataTableReader. L'argomento è vuoto. - - - Impossibile creare DataTableReader. Gli argomenti contengono valori null. - - - Lo stato della raccolta DataRow corrente è Deleted o Detached. - - - DataTable "{0}" corrente vuota. Nessun DataRow presente in DataTable. - - - Indice interno di DataTable corrotto: "{0}". - - - La raccolta è stata modificata; impossibile eseguire l'operazione di enumerazione. - - - Il tipo semplice '{0}' è già stato dichiarato con un elemento '{1}' diverso. - - - Foliazione non valida. - - - Impossibile modificare il nome della tabella una volta che il DataSet associato è stato mappato a un documento XML caricato. - - - Impossibile modificare lo spazio dei nomi della tabella una volta che il DataSet associato è stato mappato a un documento XML caricato. - - - Impossibile modificare il nome della colonna dopo che il DataSet associato è stato mappato a un documento XML caricato. - - - Impossibile modificare lo spazio dei nomi della colonna dopo che il DataSet associato è stato mappato a un documento XML caricato. - - - Impossibile modificare la proprietà ColumnMapping dopo che il DataSet associato è stato mappato su un documento XML. - - - Impossibile aggiungere o rimuovere colonne dalla tabella dopo che il DataSet è stato mappato a un documento XML caricato. - - - Impossibile aggiungere o rimuovere tabelle dal DataSet dopo che è stato mappato a un documento XML caricato. - - - Impossibile aggiungere, rimuovere o modificare le relazioni Nested del DataSet dopo che questo è stato mappato a un documento XML caricato. - - - Parametro di DataSet non valido. Non può essere null. - - - Impossibile modificare il nome del DataSet dopo che questo è stato mappato a un documento XML caricato. - - - Impossibile duplicare questo tipo di nodo: {0}. - - - Impossibile caricare XmlDataDocument se contiene già dati. Utilizzare una nuova classe XmlDataDocument. - - - Non è possibile associare un DataSet a più di un XmlDataDocument. Non è possibile associare il DataSet al XmlDataDocument corrente perché è già associato a un altro XmlDataDocument. - - - Impostare DataSet.EnforceConstraints == false prima di tentare di modificare XmlDataDocument mediante operazioni XML. - - - GetElementById() non supportato su DataDocument. - - - Impossibile creare riferimenti a entità su DataDocument. - - - La funzione Clear in DateSet e DataTable non è supportata da XmlDataDocument. - - - Impossibile rimuovere una colonna incorporata nel dataSet. - - - Non è possibile rimuovere la relazione perché è incorporata in questo set di dati. - - - Generato quando una proprietà di questo controllo cambia. - - - Generato quando si modifica l'appartenenza a questa raccolta. - - - Impossibile ottenere il valore perché è DBNull. - - - La proprietà '{0}' richiede Microsoft WindowsNT o un altro sistema operativo basato su WindowsNT. - - - Non è possibile trovare il provider di dati .NET Framework richiesto. Potrebbe non essere installato. - - - L'implementazione richiesta del provider di dati .NET Framework non possiede un campo Istanza di un tipo derivato System.Data.Common.DbProviderFactory. - - - Non è stato possibile trovare o caricare il provider di dati .NET Framework registrato. - - - È richiesto il nome qualificato dall'assembly del provider di dati .NET Framework mancante. - - - Solo gli elementi consentiti. - - - Nodi figlio non consentiti. - - - Attributo "{0}" non riconosciuto. - - - Elemento non riconosciuto. - - - La sezione '{0}' può essere presente una sola volta in ogni file di configurazione. - - - L'attributo obbligatorio '{0}' non è stato trovato. - - - L'attributo obbligatorio "{0}" non può essere vuoto. - - - Prevista matrice non vuota per il parametro "{0}". - - - L'unico valore accettabile per la proprietà "{0}" è "{1}". - - - I valori accettabili per la proprietà "{0}" sono "{1}" oppure "{2}". - - - I valori specificati per QuotePrefix e QuoteSuffix non corrispondono. - - - La lunghezza dell'argomento '{0}' supera il limite di '{1}'. - - - Previsto un argomento di tipo {1}. Ricevuto tipo {0}. - - - Valore non valido per la chiave '{0}'. - - - L'utilizzo della chiave "{0}" richiede la presenza della chiave "{1}". - - - La lunghezza del valore per la chiave '{0}' supera il limite di '{1}'. - - - Parola chiave non supportata: '{0}'. - - - Impossibile caricare il file UDL. - - - File UDL non valido. - - - Errore interno del provider di dati .NET Framework {0}. - - - Impossibile modificare le proprietà QuotePrefix e QuoteSuffix dopo la generazione di un comando Insert, Update o Delete. - - - È necessario inizializzare la proprietà DataAdapter.SelectCommand. - - - La proprietà DataAdapter.SelectCommand.Connection deve essere inizializzata. - - - {0} "{1}". - - - {0} "{1}", uso incorretto delle virgolette. - - - {0} "{1}", il limite corrente di "{2}" è insufficiente. - - - SqlBulkCopy.WriteToServer non riuscito perché SqlBulkCopy.DestinationTableName è un nome a più parti non valido - - - Elaborazione dei risultati da SQL Server fallita a causa di un nome a più parti non valido - - - SqlParameter.UdtTypeName è un nome a più parti non valido - - - SqlParameter.TypeName è un nome a più parti non valido - - - SqlCommand.DeriveParameters fallito perché il valore di proprietà SqlCommand.CommandText è un nome a più parti non valido - - - OdbcCommandBuilder.DeriveParameters fallito perché il valore di proprietà OdbcCommand.CommandText è un nome a più parti non valido - - - OleDbCommandBuilder.DeriveParameters fallito perché il valore di proprietà OleDbCommandBuilder.CommandText è un nome a più parti non valido - - - La connessione a un'istanza di SQL Server con mirroring utilizzando l'opzione di connessione MultiSubnetFailover non è supportata. - - - Impossibile eseguire il mapping dalla SourceColumn '{0}' perché la DataColumn '{1}' è una colonna calcolata. - - - 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 nella DataTable '{1}' per la SourceColumn '{2}'. - - - SourceColumn deve essere una stringa non vuota. - - - Mapping di SourceColumn mancante per '{0}'. - - - Il valore {1} dell'enumerazione {0} non è supportato dal metodo {2}. - - - Il valore di enumerazione di {0}, {1}, non è supportato dal provider di dati .NET Framework Odbc. - - - Il valore di enumerazione di {0}, {1}, non è supportato dal provider di dati .NET Framework OleDb. - - - Il valore di enumerazione di {0}, {1}, non è supportato dal provider di dati .NET Framework SqlClient. - - - Impossibile recuperare ComputerNameDnsFullyQualified, {0}. - - - DataTable '{0}' mancante per la SourceTable '{1}'. - - - SourceTable deve essere una stringa non vuota. - - - Mapping di SourceTable mancante: '{0}' - - - {0}: la proprietà CommandText non è stata inizializzata - - - {0}: la proprietà Connection non è stata inizializzata. - - - {0} richiede una oggetto Connection aperto e disponibile. {1} - - - Fill: la proprietà SelectCommand.Connection non è stata inizializzata. - - - FillPage: la proprietà SelectCommand.Connection non è stata inizializzata. - - - FillSchema: la proprietà SelectCommand.Connection non è stata inizializzata. - - - Update richiede che InsertCommand abbia un oggetto connessione. La proprietà Connection di InsertCommand non è stata inizializzata. - - - Update richiede che UpdateCommand abbia un oggetto connessione. La proprietà Connection di UpdateCommand non è stata inizializzata. - - - Update richiede che DeleteCommand abbia un oggetto connessione. La proprietà Connection di DeleteCommand non è stata inizializzata. - - - Update richiede un oggetto connessione. La proprietà Connection non è stata inizializzata. - - - Update richiede che il duplicato di comando abbia un oggetto connessione. La proprietà Connection del duplicato di comando non è stata inizializzata. - - - L'aggiornamento richiede una connessione. - - - Update richiede che {0}Command abbia un oggetto Connection aperto. {1} - - - Update richiede che {0}Command abbia un oggetto Connection aperto. {1} - - - Update richiede che {0}Command abbia un oggetto Connection aperto. {1} - - - Update richiede che il comando di aggiornamento abbia un oggetto Connection aperto. {1} - - - Stored procedure '{0}' inesistente. - - - La transazione assegnata a questo comando deve essere la transazione locale in sospeso più annidata. - - - La transazione non è associata alla connessione corrente oppure è stata completata. - - - La transazione associata alla connessione corrente è stata completata, ma non è stata eliminata. Per poter utilizzare la connessione per eseguire istruzioni SQL, è necessario eliminare la transazione. - - - {0} richiede che il comando abbia una transazione quando la connessione assegnata al comando è in una transazione locale in sospeso. La proprietà Transaction del comando non è stata inizializzata. - - - Al comando è già associato un SqlResultSet aperto, che deve essere chiuso. - - - A {0} è già associato un DataReader aperto, che deve prima essere chiuso. - - - DeriveParameters {0} supporta solo CommandType.StoredProcedure, non CommandType.{1}. - - - Impossibile chiamare il metodo "{0}" più di una volta per la stessa esecuzione. - - - Risultato asincrono non corretto. - - - Proprietà SelectCommand non inizializzata prima della chiamata a '{0}'. - - - StatementType {0} non previsto in questo contesto. - - - FillSchema: prevista stringa non vuota per il nome di SourceTable. - - - Valore di MaxRecords non valido: {0}. Il valore deve essere >= 0. - - - Valore di StartRecord non valido: {0}. Il valore deve essere >= 0. - - - Fill: prevista stringa non vuota per il nome di SourceTable. - - - Le colonne del capitolo gerarchico devono essere mappate su una DataColumn AutoIncrement. - - - DataReader.GetFieldType({0}) ha restituito il valore null. - - - Specificare soltanto un elemento nella matrice dataTables quando si utilizzano valori diversi da zero per startRecords o maxRecords. - - - Update non è in grado di trovare TableMapping['{0}'] o DataTable '{0}'. - - - Update: previsto un nome SourceTable non vuoto. - - - TableMapping mancante per TableMapping.DataSetTable='{0}'. - - - Update richiede che il duplicato di comando sia valido. - - - La generazione SQL automatica durante un Update richiede un SelectCommand valido. - - - Update richiede un InsertCommand valido se la raccolta DataRow viene passata con righe nuove. - - - Update richiede un UpdateCommand valido se la raccolta DataRow viene passata con righe modificate. - - - Update richiede un DeleteCommand valido se la raccolta DataRow viene passata con righe eliminate. - - - DataRow[{0}] proviene da un DataTable diverso da DataRow[0]. - - - Errori in RowUpdatedEvent. Nessuna informazione aggiuntiva disponibile. - - - Errori in RowUpdatingEvent. Nessuna informazione aggiuntiva disponibile. - - - Durante l'esecuzione di batch, il valore UpdateRowSource.FirstReturnedRecord oppure UpdateRowSource.Both della proprietà UpdatedRowSource del comando non è valido. - - - Violazione di concorrenza: UpdateCommand ha modificato {0} dei {1} record previsti. - - - Violazione di concorrenza: DeleteCommand ha modificato {0} dei {1} record previsti. - - - Violazione di concorrenza: il comando in batch ha modificato {0} dei {1} record previsti. + + Impossibile chiamare il metodo "{0}" più di una volta per la stessa esecuzione. Valore di CommandTimeout non valido: {0}. Il valore deve essere >= 0. - - {1}[{0}]: la proprietà Size ha una dimensione 0 non valida. - - - Il metodo {0}.Prepare richiede l'impostazione esplicita del tipo per tutti i parametri. - - - Il metodo {0}.Prepare richiede l'impostazione esplicita di Size diversa da zero per tutti i parametri di lunghezza variabile. - - - Il metodo {0}.Prepare richiede l'impostazione esplicita di Scale e Precision per i parametri di tipo '{1}'. - - - Chiamata non corrispondente al metodo di fine per asyncResult. Era prevista una chiamata a {0} ma è stato chiamato {1}. - - - Operazione non valida. La connessione è chiusa. - - - La connessione è stata disattivata - - - Impossibile inserire la transazione nell'elenco. È in corso una transazione locale sulla connessione. Terminare la transazione locale e riprovare. - - - La connessione ha una transazione correntemente inserita nell'elenco. Completare la transazione corrente e riprovare. - - - Database non può essere null, una stringa vuota o una stringa di soli spazi. - - - L'argomento è troppo lungo. - - - Valore di 'Connect Timeout' non valido. Il valore deve essere un integer >= 0. - - - Offset buffer di origine (dimensione {0}) non valido: {1} - - - Offset buffer di destinazione (dimensione {0}) non valido: {1} - - - Il valore numerico non è contenibile in un numero decimale a 96 bit. - - - Tentativo non valido di {0} con flusso chiuso. - - - Il valore specificato per SeekOrigin non è valido. - - - Generazione SQL dinamica non supportata per più tabelle di base. - - - Generazione SQL dinamica non supportata per un SelectCommand che non restituisce informazioni di tabelle di base. - - - Generazione SQL dinamica per DeleteCommand non supportata per un SelectCommand che non restituisce informazioni di colonne chiave. - - - Generazione SQL dinamica per UpdateCommand non supportata per un SelectCommand che non restituisce informazioni di colonne chiave. - - - Generazione SQL dinamica per DeleteCommand non supportata per un SelectCommand che non contiene una colonna con versione a righe. - - - Generazione SQL dinamica per UpdateCommand non supportata per un SelectCommand che non contiene una colonna con versione a righe. - - - Generazione SQL dinamica non supportata per nomi di tabella "{0}" che contengono il carattere "{1}" nella proprietà QuotePrefix o QuoteSuffix. - - - Tentativo non valido di leggere dall'ordinale di colonna "{0}". Con CommandBehavior.SequentialAccess è possibile leggere solo a partire dall'ordinale di colonna "{1}". - - - Impossibile formattare il tipo di dati "{0}" come rappresentazione formale perché presenta cifre di data/ora non valide. - - - Impossibile formattare il valore come una rappresentazione formale del tipo richiesto. - - - Impossibile formattare il tipo di dati "{0}" come rappresentazione formale perché ha una scala massima non valida. - - - Il valore letterale fornito non è una rappresentazione formale valida per il tipo di dati "{0}". - - - "{0}":la lunghezza del valore letterale deve essere pari. - - - "{0}":il valore letterale deve essere una stringa con cifre esadecimali - - - {0} richiede una connessione aperta se non è stata impostata la virgoletta di prefisso. - - - Impossibile determinare i valori letterali booleani corretti. Impossibile creare valori letterali Boolean. - - - Le rappresentazioni formali del tipo di dati nativo associato al tipo di dati "{0}" non sono supportate. - - - Il tipo di dati del parametro di {0} non è valido. - - - Nessun mapping esistente dal tipo di oggetto {0} a un tipo nativo di provider gestito noto. - - - Impossibile gestire un TypeCode {0} sconosciuto restituito da Type {1}. - - - Nessun mapping esistente dal DbType {0} a un {1} riconosciuto. - - - La versione di SQL Server in uso non supporta il tipo di dati "{0}". - - - Il valore "{0}" del parametro non è compreso nell'intervallo. - - - Il nome di parametro specificato "{0}" non è valido. - - - Non sono supportati più parametri di valori restituiti. - - - Valore '{0}' del parametro Size non valido. Il valore deve essere maggiore o uguale a zero. - - - Valore non valido per l'argomento '{0}'. Il valore deve essere maggiore o uguale a 0. - - - Valore non valido per questi metadati. - - - I metadati devono essere SqlDbType.Row - - - Impossibile convertire il valore del parametro da un {0} a un {1}. - - - {0} non supporta le transazioni parallele. - - - La {0} è completata e non può più essere utilizzata. - - - Impossibile chiamare "{0}" se il record è di sola lettura. - - - Impossibile chiamare "{0}" se DbDataRecord è di sola lettura. - - - Conversione implicita del tipo di oggetto "{0}" nel tipo di dati "{1}" non supportata. - - - L'offset del buffer '{1}' sommato ai byte disponibili '{0}' è maggiore della lunghezza del buffer passato. - - - Lunghezza dei dati '{0}' minore di 0. - - - La lunghezza specificata "{0}" non è compresa nell'intervallo. - - - Tentativo di {2} nel dataIndex '{0}' non valido. Con CommandBehavior.SequentialAccess è possibile iniziare la lettura solo dal dataIndex '{1}'. - - - Offset deve fare riferimento a una posizione all'interno del valore. - - - "per il provider gestito ODBC è necessario specificare la restrizione TABLE_NAME e che i valori della raccolta di indici GetSchema non siano null. - - - Valore di argomento non valido per il metodo "{0}". - - - Il provider ODBC non ha restituito risultati da SQLGETTYPEINFO. - - - Argomento DataTable null imprevisto. - - - Argomento DataSet null imprevisto. - - - La stringa di connessione supera la lunghezza massima consentita, pari a {0}. - - - {0}: impossibile mappare il tipo. - - - Tipo SQL sconosciuto - {0}. - - - Tipo URT sconosciuto - {0}. - - - Argomento negativo non valido. - - - Impossibile impostare proprietà su una connessione aperta. - - - Nessun mapping valido per un SQL_TRANSACTION "{0}" a un valore di enumerazione System.Data.IsolationLevel. - - - {0}. Impossibile attivare la creazione di un pool di connessioni. - - - {0}. Impossibile allocare l'handle di ambiente. - - - {0} - impossibile ottenere un handle descrittore. - - - Non in una transazione. - - - Valore di enumerazione {0} non valido per OdbcType. - - - Utilizzare IsDBNull se si prevedono dati DBNull.Value. - - - {0} [{1}] {2} - - - {0}. Informazioni sull'errore non disponibili. - - - La connessione è chiusa. - - - Una connessione interna non dispone di un proprietario. - - - Il provider di dati .NET Framework Odbc richiede Microsoft Data Access Components (MDAC) versione 2.6 o successiva. Versione correntemente installata: {0}. - - - Il provider di dati .NET Framework OleDb richiede Microsoft Data Access Components (MDAC) versione 2.6 o successiva. Versione correntemente installata: {0}. - - - Il provider "{1}" non supporta l'interfaccia "{0}". GetOleDbSchemaTable non è disponibile con il provider corrente. - - - '{0}' fallito senza alcun messaggio di errore disponibile, codice risultato: {1}. - - - Nessun messaggio di errore disponibile, codice risultato: {0}. - - - I provider di dati .NET Framework richiedono Microsoft Data Access Components (MDAC). Installare Microsoft Data Access Components (MDAC) versione 2.6 o successiva. - - - Il provider di dati .NET Framework per OLEDB (Microsoft.Data.OleDb) non supporta il provider Microsoft OLE DB per driver ODBC (MSDASQL). Usare il provider di dati .NET Framework per ODBC (System.Data.Odbc). - - - Il provider di dati .NET Framework per OLEDB non consente al provider OLE DB di chiedere all'utente di specificare un valore in un ambiente non interattivo. - - - Il provider '{0}' non è registrato nel computer locale. - - - L'interfaccia ICommandText non è supportata dal provider '{0}'. Utilizzare CommandType.TableDirect. - - - L'interfaccia ITransactionLocal non è supportata dal provider "{0}". Le transazioni locali non sono disponibili con il provider corrente. - - - Formato della stringa di inizializzazione non conforme alle specifiche OLE DB, a partire da char[{0}] circa nella stringa di connessione. - - - 'Elaborazione asincrona' non è una funzionalità supportata dal provider di dati .NET Framework OLE DB (Microsoft.Data.OleDb). - - - Provider OLE DB non specificato in ConnectionString. Utilizzare ad esempio 'Provider=SQLOLEDB;'. - - - Il provider OLE DB specificato in ConnectionString è troppo lungo. - - - Non sono previste restrizioni per DbInfoKeywords OleDbSchemaGuid. - - - Non sono previste restrizioni per il campo DbInfoLiterals di OleDbSchemaGuid. - - - Non sono previste restrizioni per il guid dello schema OleDbSchemaGuid. - - - La {0} OleDbSchemaGuid non è uno schema supportato dal provider '{1}'. - - - Le impostazioni di configurazione "{0}" hanno un numero di valori errato. - - - Impossibile caricare il file XML specificato nell'impostazione di configurazione "{0}". - - - Parametro Command [{0}] '{1}' non valido. - - - Impossibile convertire il valore dei dati del parametro Command [{0}] "{1}" per ragioni diverse dalla mancata corrispondenza dei segni o dall'overflow dei dati. - - - Impossibile convertire il parametro Command [{0}] '{1}'. Il valore dei dati è con segno, mentre il tipo utilizzato dal provider è senza segno. - - - Impossibile convertire il parametro Command [{0}] '{1}'. Il valore dei dati ha determinato l'overflow del tipo utilizzato dal provider. - - - Il provider ha rilevato un errore durante l'invio del valore del parametro Command [{0}] '{1}' e ha arrestato l'elaborazione. - - - Il parametro [{0}] "{1}" non ha valore predefinito. - - - Errore nel parametro [{0}]: {1}. - - - Errore interno di Microsoft.Data.OleDb.OleDbDataAdapter: funzione di accesso al parametro non valida: {0} {1}. - - - Parameter[{0}]: proprietà OleDbType non inizializzata: OleDbType.{1}. - - - L'interfaccia ICommandWithParameters non è supportata dal provider '{0}'. I parametri Command non sono supportati dal provider corrente. - - - Il recupero di informazioni sui parametri di procedura non è supportato dal provider '{0}'. - - - Impossibile determinare il separatore decimale del server. Impossibile creare valori numerici letterali non Integer. - - - L'oggetto non è un oggetto ADODB.RecordSet o un ADODB.Record. - - - Impossibile recuperare l'interfaccia "{0}" dall'oggetto ADODB.RecordSet. + + {1}[{0}]: la proprietà Size ha una dimensione 0 non valida. - - Impossibile recuperare l'interfaccia IRow dall'oggetto ADODB.Record. + + Il metodo {0}.Prepare richiede l'impostazione esplicita del tipo per tutti i parametri. - - Il tipo non supporta l'interfaccia OLE DB ISourcesRowset + + Il metodo {0}.Prepare richiede l'impostazione esplicita di Size diversa da zero per tutti i parametri di lunghezza variabile. - - Impossibile costruire la raccolta di schemi ReservedWords. Il provider non supporta IDBInfo. + + Il metodo {0}.Prepare richiede l'impostazione esplicita di Scale e Precision per i parametri di tipo '{1}'. - - Il valore della proprietà non è stato impostato perché il provider non supportava la proprietà "{0}" oppure il consumer ha tentato di ottenere o impostare valori di proprietà non incluse nel gruppo Initialization e l'oggetto origine dati non è stato inizializzato. + + Chiamata non corrispondente al metodo di fine per asyncResult. Era prevista una chiamata a {0} ma è stato chiamato {1}. - - Impossibile inizializzare la proprietà "{0}" per uno dei seguenti motivi: - Il tipo di dati del valore non è il tipo di dati della proprietà oppure non era null. Ad esempio, la proprietà è DBPROP_MEMORYUSAGE, che ha un tipo di dati Int32, ma il tipo di dati è Int64. - Il valore non è valido. Ad esempio, la proprietà è DBPROP_MEMORYUSAGE e il valore è negativo. - Il valore è valido per la proprietà e il provider la supporta come proprietà impostabile, ma non supporta il valore specificato. Ad esempio, nel caso in cui il valore sia stato aggiunto alla proprietà in OLE DB dopo che sia stata eseguita un'operazione di scrittura nel provider. + + Operazione non valida. La connessione è chiusa. - - Valore di Options non valido. + + Impossibile inserire la transazione nell'elenco. È in corso una transazione locale sulla connessione. Terminare la transazione locale e riprovare. - - Elemento ColumnID non valido. + + La connessione ha una transazione correntemente inserita nell'elenco. Completare la transazione corrente e riprovare. - - La proprietà "{0}" è stata specificata per essere applicata a tutte le colonne, ma non è stato possibile applicarla a una o più colonne. + + Database non può essere null, una stringa vuota o una stringa di soli spazi. - - La proprietà "{0}" è di sola lettura oppure il consumer ha provato a impostare valori di proprietà nel gruppo delle proprietà Initialization dopo l'inizializzazione dell'oggetto origine dati. I consumer possono impostare una proprietà di sola lettura sul valore corrente. Questo stato viene inoltre restituito se non è stato possibile impostare una proprietà impostabile di colonna per la colonna specifica. + + Offset buffer di origine (dimensione {0}) non valido: {1} - - Il valore della proprietà facoltativa "{0}" non è stato impostato sul valore specificato e non è stato possibile impostare la proprietà sul valore specificato. + + Offset buffer di destinazione (dimensione {0}) non valido: {1} - - Il valore della proprietà "{0}" non è stato impostato, perché sarebbe stato in conflitto con una proprietà esistente. + + Tentativo non valido di {0} con flusso chiuso. - - (Riservato). + + Il valore specificato per SeekOrigin non è valido. - - Il provider ha restituito un valore DBPROPSTATUS_ value "{0}" sconosciuto. + + Tentativo non valido di leggere dall'ordinale di colonna "{0}". Con CommandBehavior.SequentialAccess è possibile leggere solo a partire dall'ordinale di colonna "{1}". - - La convalida della funzione di accesso è stata rinviata ed eseguita mentre il metodo restituiva i dati. L'associazione per questa colonna o parametro non era valida. + + Il tipo di dati del parametro di {0} non è valido. - - Errore interno di OleDbDataAdapter: funzione di accesso al set di righe non valida: Ordinal={0} Status={1}. + + Nessun mapping esistente dal tipo di oggetto {0} a un tipo nativo di provider gestito noto. - - Il valore dei dati non può essere convertito per ragioni diverse dalla mancata corrispondenza dei segni o dall'overflow. È ad esempio possibile che i dati siano stati danneggiati nell'archivio pur restando possibile il recupero della riga. + + Impossibile gestire un TypeCode {0} sconosciuto restituito da Type {1}. - - Impossibile per il provider allocare la memoria in cui restituire i dati di {0}. + + Nessun mapping esistente dal DbType {0} a un {1} riconosciuto. - - Impossibile effettuare la conversione. Il valore dei dati {0} ha provocato un overflow del tipo specificato per la parte di valore {0} del buffer del consumer. + + La versione di SQL Server in uso non supporta il tipo di dati "{0}". - - Errore interno OleDbDataAdapter: [get]. Tipo di dati OLE DB sconosciuto: 0x{0} ({1}). + + Il valore "{0}" del parametro non è compreso nell'intervallo. - - Impossibile effettuare la conversione. Il valore dei dati {0} è con segno, mentre il tipo specificato per la parte del valore {0} nel buffer del consumer era senza segno. + + Il nome di parametro specificato "{0}" non è valido. - - Errore interno OleDbDataAdapter: [set]. Tipo di dati OLE DB sconosciuto: 0x{0} ({1}). + + Valore '{0}' del parametro Size non valido. Il valore deve essere maggiore o uguale a zero. - - Il provider non ha potuto determinare il valore di {0}. Esempio: la riga è stata appena creata, non è disponibile un valore predefinito per la colonna {0} e il consumer non ha ancora assegnato a {0} un nuovo valore. + + Valore non valido per l'argomento '{0}'. Il valore deve essere maggiore o uguale a 0. - - Il provider OLE DB ha restituito un valore di stato non previsto: {0} + + Valore non valido per questi metadati. - - Se l'ApartmentState del thread non è ApartmentState.MTA, è necessario utilizzare OleDbDataReader.Read dal thread in cui è stato creato. + + Impossibile convertire il valore del parametro da un {0} a un {1}. - - Errore non specificato: {0} + + {0} non supporta le transazioni parallele. - - IErrorInfo.GetDescription non riuscito con {0}. + + La {0} è completata e non può più essere utilizzata. - - IErrorInfo.GetSource non riuscito con {0}. + + La lunghezza specificata "{0}" non è compresa nell'intervallo. - - I dati DBTYPE_VECTOR non sono supportati dal provider di dati .NET Framework OLE DB (Microsoft.Data.OleDb). + + Tentativo di {2} nel dataIndex '{0}' non valido. Con CommandBehavior.SequentialAccess è possibile iniziare la lettura solo dal dataIndex '{1}'. Valori minimo e massimo delle dimensioni di pool non validi. La dimensione di pool minima deve essere inferiore a quella massima. - - La parola chiave "{0}" è obsoleta. Al suo posto usare "{1}". - Impossibile ottenere dal server l'indirizzo del coordinatore della transazione distribuita per il server. Il DTC è abilitato sul server? - - Il valore per il parametro di connessione <{0}> supera la lunghezza massima consentita di 65535 caratteri. - Valore di "Packet Size" non valido. Il valore deve essere un integer >= 512 e <= 32768. Transazione o nome non valido per un punto della transazione in cui effettuare il salvataggio. - - Il valore di enumerazione {0}, {1}, non è supportato da SQL Server 7.0 o SQL Server 2000. - User Instance e Failover non sono opzioni compatibili. Scegliere una delle due nella stringa della connessione. @@ -2499,33 +540,21 @@ Non è possibile impostare la proprietà Credential se nella stringa di connessione è stato specificato 'Authentication=Active Directory Integrated'. - - Parametri di autenticazione non corretti specificati con l'autenticazione del certificato. - L'istanza di SQL Server a cui si è tentato di connettersi richiede la crittografia, ma questo computer non la supporta. L'istanza di SQL Server a cui si è tentato di connettersi non supporta la crittografia. - - L'istanza di SQL Server a cui si è tentato di connettersi non supporta CTAIP. - Versione di SQL Server non supportata. È possibile usare il provider di dati di .NET Framework SqlClient solo con SQL Server versioni 7.0 e successive. {0} non può essere modificata mentre è in corso l'operazione asincrona. - - 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. - Errore dell'istanza. @@ -2556,27 +585,18 @@ Codice errore: 0x{0} - - ChangePassword richiede SQL Server 9.0 o versione successiva. - Il reindirizzamento dell'istanza SSE non è supportato per le istanze di utenti non locali. L'autenticazione '{0}' non è supportata. - - Il metodo di autenticazione SQL '{0}' non è supportato. - Non è stato possibile creare un'istanza di un provider di autenticazione di tipo '{1}' per '{0}'. Non è stato possibile creare un'istanza di un elemento SqlAuthenticationInitializer di tipo '{0}'. - - Il provider '{0}' ha generato un'eccezione durante l'inizializzazione. - Il provider '{0}' non supporta l'autenticazione '{1}'. @@ -2586,24 +606,15 @@ Non è possibile trovare un provider di autenticazione per '{0}'. Installare il pacchetto NuGet 'Microsoft.Data.SqlClient.Extensions.Azure' (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) per usare i metodi di autenticazione di Active Directory (Entra ID). - - Non è stato possibile leggere la sezione di configurazione per i provider di autenticazione. - Il parametro '{0}' non può essere Null o vuoto. - - L'operazione asincrona è già completata. - Impossibile eseguire il comando a causa di un'operazione asincrona in sospeso già in esecuzione. Comando non valido inviato a ExecuteXmlReader. Il comando deve restituire un risultato Xml. - - Le notifiche richiedono SQL Server 9.0 o versioni successive. - Formato del nome in tre parti non valido per UdtTypeName. @@ -2697,9 +708,6 @@ Impossibile inizializzare il pacchetto SSPI. - - Timeout. Il tempo disponibile è scaduto prima del completamento dell'operazione o il server non risponde. - Timeout di connessione scaduto. Il tempo disponibile è scaduto all'inizio della fase di pre-accesso. È possibile che il tempo fornito per il timeout della connessione non sia stato sufficiente. @@ -2745,12 +753,6 @@ Nella stringa della connessione è stata richiesta un'istanza utente, ma il server specificato non supporta questa opzione. - - Il certificato del server non ha superato la convalida dell'applicazione. - - - La lunghezza dati specificata {0} supera la lunghezza massima consentita di {1}. - Tentativo non valido di eseguire la lettura in assenza di dati. @@ -2775,9 +777,6 @@ Lunghezza dei dati '{0}' minore di 0. - - Impossibile chiamare "{0}" quando SqlDataRecord è di sola lettura. - Non è possibile accedere alla raccolta mappata perché è attualmente in uso; @@ -2847,21 +846,6 @@ La connessione alla connessione di contesto tramite Microsoft.Data.SqlClient non è supportata. - - Non sono supportati TransactionScopes annidati. - - - Evento server imprevisto: {0}. - - - La lunghezza di '{0}' deve corrispondere alla lunghezza di '{1}'. - - - SqlDbType '{0}' non è valido per {1}. Solo {2} è supportato. - - - Troppi valori. - Stream non supporta la scrittura. @@ -2886,192 +870,36 @@ Null - - Messaggio - - - Overflow aritmetico. - - - Errore di divisione per zero. - - - I dati hanno valore Null. Impossibile chiamare il metodo o la proprietà su valori Null. - - - Troncamento causato da un'operazione aritmetica. - Overflow di SqlDateTime. Deve essere compreso tra 1/1/1753 12:00:00 AM e 12/31/9999 11:59:59 PM. - - Due stringhe da concatenare hanno regole di confronto diverse. - - - Due stringhe da confrontare hanno regole di confronto diverse. - - - Valore del flag non valido. - - - Overflow durante la conversione da SqlDecimal a Decimal. - Overflow di conversione. - - SqlDateTime non valido. - - - È stato specificato un fuso orario. SqlDateTime non supporta i fusi orari. - Dimensione matrice non valida. Scala/precisione numerica non valida. - - Input in formato non corretto. - - - Errore di SqlType. - - - SqlBytes e SqlChars non supportano una lunghezza superiore a 2GB in questa versione. - Errore durante la lettura. - - Stream è stato chiuso o eliminato. - - - Nessun buffer presente. Operazione di lettura o scrittura non riuscita. - - - Impossibile impostare una lunghezza diversa da zero. Il valore corrente è Null. - - - Il buffer non è sufficiente. Operazione di lettura o scrittura non riuscita. - - - Impossibile scrivere su un offset diverso da zero. Il valore corrente è Null. - - - Impossibile scrivere da un offset di dimensioni maggiori della lunghezza corrente. Resterebbero dati non inizializzati nel buffer. - I dati restituiti hanno una dimensione maggiore di 2Gb. Utilizzare SequentialAccess command behavior per ottenere tutti i dati. - - L'argomento per GetDayOfWeek deve essere un integer compreso tra 1 e 7. - - - SQL Type non è stato caricato con i dati. - - - SQL Type è già stato caricato con i dati. - - - Tentativo non valido di accedere a un XmlReader chiuso. - - - Tentativo non valido di chiamare {0} quando il flusso è chiuso. - - - Tentativo non valido di chiamare {0} quando il flusso non è scrivibile. - - - Tentativo non valido di chiamare {0} quando il flusso non è leggibile. - - - Tentativo non valido di chiamare {0} quando il flusso non è ricercabile. - - - La sottoclasse non ha eseguito l'override di un metodo di richiesta. - Impossibile creare un normalizzatore per '{0}'. Errore interno - - Il parametro del comando deve avere un testo del comando non null e non vuoto. - - - La lunghezza della matrice dei parametri MetaData deve essere equivalente all'argomento di matrice ParameterDirection. - - - Errore di mappatura dell'adattatore dati. - - - Errore adattatore dati. - - - Violazione di concorrenza DB. - Operazione interrotta. Operazione interrotta a causa di un eccezione (per ulteriori informazioni, vedere InnerException). - - Indica se Fill chiamerà o meno DataRow.AcceptChanges. - - - Indica se Update chiamerà o meno DataRow.AcceptChanges. - - - Indica se continuare o meno con il DataRow successivo quando si verificano gli eventi Update, RowUpdating e RowUpdated. Status è UpdateStatus.ErrorsOccurred. - - - Indica il modo in cui l'adattatore riempirà la DataTable dal DataReader. - - - Azione intrapresa quando manca una tabella o una colonna di TableMappings. - - - Azione intrapresa quando manca una tabella o una colonna del DataSet. - - - Indica come effettuare il mapping di una tabella di origine sulla tabella DataSet. - - - Evento generato quando si verifica un errore risolvibile durante un'operazione Fill. - - - Indica se Fill restituisce valori specifici del provider oppure valori comuni conformi a CLSCompliant. - - - DataColumn.ColumnName - - - Nome colonna di origine (con distinzione tra maiuscole e minuscole). - - - Numero di elementi contenuti nella raccolta - - - Oggetto DataColumnMapping specificato. - - - Mapping di singole colonne quando esiste una corrispondenza per il mapping della tabella. - - - DataTable.TableName - - - Nome della tabella di origine di DataTableMapping. Distingue tra maiuscole e minuscole. - - - Numero di elementi contenuti nella raccolta - - - Oggetto DataTableMapping specificato. - Utilizzato durante l'aggiornamento per le righe eliminate dal DataSet. @@ -3090,63 +918,9 @@ Evento generato dopo ogni DataRow durante un'operazione di aggiornamento. - - Numero di righe da riunire in batch prima di eseguirle sull'origine dati. - - - Connessione utilizzata se i comandi Select/Insert/Update/DeleteCommands non dispongono ancora di una connessione. - - - Utilizzato durante Update per le righe eliminate nel DataTable. - - - Utilizzato durante Update per le nuove righe nel DataTable. - - - Utilizzato durante le operazioni di Fill. - - - Utilizzato durante Update per le righe modificate nel DataTable. - - - Indica se Fill restituisce valori specifici del provider oppure valori comuni conformi a CLSCompliant. - - - Indica come mappare la tabella di origine a DataTable. - - - Indica il modo in cui vengono generati i comandi Insert/Update/DeleteCommands se non è stato impostato dall'utente. - - - Numero di righe da riunire in batch prima di eseguirle sull'origine dati. - - - Stringa di connessione utilizzata per la connessione a Data Source. - - - Nome del driver ODBC da utilizzare per la connessione a Data Source. - - - DSN da utilizzare per la connessione a Data Source. - - - Se il valore è true, indica che è necessario utilizzare un pool di connessioni gestito. - - - File UDL da utilizzare per la connessione a Data Source. - - - Specifica quali OLE DB Services attivare o disabilitare con OleDb Provider. - - - Nome del provider OLE DB da utilizzare per la connessione a Data Source. - Nome dell'applicazione. - - Se il valore è true, consente l'utilizzo della funzionalità asincrona nel provider di dati .NET Framework. - Nome del file primario, incluso il nome completo del percorso, di un database collegabile. @@ -3156,9 +930,6 @@ Se il valore è true, indica che lo stato della connessione viene reimpostato quando viene rimosso dal pool. - - Se il valore è true, indica che la connessione proviene dal contesto Sql Server. Disponibile solo quando in esecuzione nel processo Sql Server. - Nome del record di lingua di SQL Server. @@ -3252,54 +1023,9 @@ Specifica il metodo di autenticazione con SQL Server. - - Certificato client specificato per l'autenticazione con SQL Server. - - - Informazioni utilizzate per la connessione a un DataSource. - - - Valore di timeout della connessione attuale, non impostabile nella proprietà ConnectionString. - - - Valore del catalogo dell'origine dati attuale. 'Database=X' nella stringa di connessione. - - - Origine dati corrente. 'Server=X' nella stringa di connessione. - - - Driver ODBC attuale. - - - Versione del prodotto a cui accede il driver ODBC. - - - Informazioni utilizzate per la connessione a un DataSource. - - - Valore di timeout della connessione corrente. 'Connect Timeout=X' in ConnectionString. - - - Valore del catalogo dell'origine dati corrente. 'Initial Catalog=X' nella stringa di connessione. - - - Origine dati corrente. 'Data Source=X' nella stringa di connessione. - - - ProgID del provider OLE DB corrente. "Provider=X" nella stringa di connessione. - - - Versione del prodotto a cui accede il provider OLE DB. - Token di accesso da usare per l'autenticazione. - - Stato della connessione, sincrona o asincrona. "Asynchronous Processing=x" nella stringa di connessione. - - - Informazioni utilizzate per eseguire la connessione per la replica. - Informazioni utilizzate per effettuare la connessione a un DataSource, quali 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'. @@ -3324,9 +1050,6 @@ Raccogliere dati statistici per la connessione. - - Provider dell'archivio chiavi di crittografia di colonna personalizzati. - Guid che rappresenta la connessione fisica. @@ -3363,99 +1086,9 @@ Inserimento automatico nelle notifiche utilizzato da Microsoft SQL Server. - - Come la clausola WHERE viene autogenerata per i comandi Update e Delete quando non specificata dall'utente. - - - Indica la posizione del nome di catalogo in un nome di tabella qualificato in un comando di testo. - - - Carattere che separa il nome di catalogo dal resto dell'identificatore in un comando di testo. - - - Carattere che separa il nome dello schema dal resto dell'identificatore in un comando di testo. - - - Stringa di prefisso utilizzata con gli oggetti sql. - - - Stringa di suffisso utilizzata con gli oggetti sql. - - - DataAdapter per il quale generare automaticamente comandi. - - - Usare lo schema di DataTable o il SelectCommand. - - - Indica il modo in cui viene generata automaticamente la clausola SET pre il comando Update quando non viene specificato dall'utente. - - - Indica il DataAdapter per cui generare automaticamente OdbcCommands. - - - Carattere utilizzato come virgoletta di apertura, necessaria per inserire nei comandi di testo identificatori che contengono caratteri speciali. - - - Carattere utilizzato come virgoletta di chiusura, necessaria per inserire nei comandi di testo identificatori che contengono caratteri speciali. - - - DataAdapter per cui generare automaticamente OleDbCommands - - - Separatore decimale utilizzato nei letterali numerici. - - - Stringa di prefisso utilizzata con gli oggetti sql. - - - Stringa di suffisso utilizzata con gli oggetti sql. - DataAdapter per cui generare automaticamente SqlCommands - - Separatore decimale utilizzato nei letterali numerici. - - - Carattere utilizzato come virgoletta di apertura, necessaria per inserire nei comandi di testo identificatori che contengono caratteri speciali. - - - Carattere utilizzato come virgoletta di chiusura, necessaria per inserire nei comandi di testo identificatori che contengono caratteri speciali. - - - Impostazione necessaria soltanto i parametri con valore decimal e numeric quando si utilizzano gli scenari Prepare, FillSchema e CommandBuilder. - - - Impostazione necessaria soltanto i parametri con valore decimal e numeric quando si utilizzano gli scenari Prepare, FillSchema e CommandBuilder. - - - Tipo nativo del parametro. - - - Tipo nativo del parametro. - - - Nome del parametro, ad esempio '@p1'. - - - Tipo nativo del parametro. - - - Il nome del server per il tipo. - - - Offset dei tipi di dati a lunghezza variabile. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - ParameterDirection '{0}' specificato per il parametro '{1}' non è supportato. Table-valued parameters supportano solo ParameterDirection.Input. @@ -3468,9 +1101,6 @@ TypeName specificato per il parametro '{0}'. TypeName deve essere impostato solo per i parametri Structured. - - Il tipo SqlDbType.Structured è supportato solo per tipi con più valori. - La colonna DateType per il campo '{0}' nello schema di tabella è null. Il valore di DataType deve essere non Null. @@ -3483,18 +1113,9 @@ Il numero di campi nel record '{0}' non corrisponde al numero contenuto nel record originale. - - intervallo: 0-8000 - Errore imprevisto nel provider dati di SqlClient. {0} - - Impossibile ottenere Type Info per {0},{1} - - - Il provider non è riuscito a caricare il seguente assembly: {0} - È necessario impostare la proprietà UdtTypeName per i parametri UDT. @@ -3504,72 +1125,9 @@ I tipo specificato non è registrato sul server di destinazione.{0}. - - Parametri UDT non consentiti nella clausola WHERE a meno che non facciano parte della chiave primaria. - - - "{0}" non è un tipo definito dall'utente valido. Motivo: {1}. - - - supporta sia formati in memoria che formati definiti dall'utente - - - Impossibile supportare il formato nativo. - - - non implementa IBinarySerialize - - - non serializzabile - - - nessun costruttore pubblico disponibile - - - non implementa INullable - - - non dispone di costruttori pubblici - nessun attributo UDT disponibile - - Non è ancora supportata la serializzazione senza il mapping. - - - Metodo "public static x Parse(System.Data.SqlTypes.SqlString)" mancante - - - Metodo "public override string ToString()" mancante - - - Metodo "public static x Null { get; }" mancante - - - Il formato nativo non supporta i campi (in modo diretto o attraverso un altro campo) di tipo "{0}" - - - Tipo non pubblico - - - Layout non sequenziale Native UDT a causa del tipo "{0}" - - - Native UDT specifica le dimensioni massime in byte - - - il campo "{0}" è contrassegnato come non serializzato - - - Il tipo di campo '{0}' è contrassegnato come layout esplicito e questo non è consentito nel formato nativo - - - I riferimenti con più valori agli assembly devono avere un ID assembly diverso da zero. - - - I tipi strutturati con più valori possono essere utilizzati solo per i parametri e non possono essere annidati in un altro tipo. - Nome di percorso non valido. @@ -3585,9 +1143,6 @@ Errore durante il tentativo di promuovere la transazione. - - Oggetto dipendenza utilizzato per ricevere notifiche di query. - Proprietà per indicare se la dipendenza non è valida. @@ -3600,9 +1155,6 @@ Per aggiungere un comando all'oggetto dipendenza esistente. - - Command è già associato a un altro oggetto dipendenza. Impossibile sovrascrivere. - L'oggetto SqlCommand è già associato a un altro oggetto SqlDependency. @@ -3630,12 +1182,6 @@ Timeout specificato non valido. Il timeout non può essere < 0. - - Errore di notifica. Type={0}, Info={1}, Source={2}. - - - GetMetaData non è valido per questo SqlDbType. - dbType {0} non è valido per questo costruttore. @@ -4104,9 +1650,6 @@ Errore interno. Il valore del testo normale non può essere Null. - - Non è possibile crittografare. In seguito alla crittografia sono stati restituiti {0} byte di testo crittografato oltre il limite massimo consentito pari a {1} byte. Il valore del testo normale specificato è probabilmente troppo grande (dimensioni del testo normale: {2} byte). - Errore interno. Il valore del testo crittografato non può essere Null. @@ -4224,9 +1767,6 @@ SQL Server non ha restituito le informazioni sull'attestazione. Il tipo di enclave è '{0}' e l'URL di attestazione dell'enclave è '{1}'. - - Non è possibile stabilire il canale sicuro. Messaggio di errore: {0} - Errore interno. È stato specificato un argomento '{0}' Null durante la costruzione di un oggetto di tipo '{1}'. '{0}' non può essere Null. @@ -4242,15 +1782,6 @@ Specifica un endpoint di un servizio di attestazione enclave che verrà usato per verificare se l'enclave, configurato nell'istanza di SQL Server per i calcoli su colonne di database crittografate con Always Encrypted, è valido e sicuro. - - Non è stato possibile leggere la sezione di configurazione per i provider di enclave. Assicurarsi che la sezione sia formattata correttamente nel file di configurazione dell'applicazione. Messaggio di errore: {0} - - - Non è stato possibile creare un'istanza di un provider di enclave con il tipo '{1}' per il nome '{0}'. Messaggio di errore: {2} - - - Errore interno. l'elemento SqlColumnEncryptionEnclaveProviderName non può essere Null o vuoto. - Si è verificato un errore durante la lettura del set di risultati '{0}'. L'URL di attestazione non è stato specificato nella stringa di connessione, ma la query richiede calcoli enclave. Il tipo di enclave è '{1}'. @@ -4260,9 +1791,6 @@ Errore interno. Il tipo di enclave ricevuto da SQL Server è Null o vuoto quando si esegue una query che richiede calcoli enclave. - - Per eseguire una query, sono richiesti calcoli enclave, ma nella configurazione dell'applicazione manca la sezione relativa al provider di enclave. - Non sono stati trovati provider di enclave per il tipo di enclave '{0}' e il protocollo di attestazione '{1}'. Specificare il protocollo di attestazione corretto nella stringa di connessione. @@ -4320,9 +1848,6 @@ Impostazione di crittografia di colonna predefinita per tutti i comandi della connessione. - - Forza la crittografia del parametro prima dell'invio di dati sensibili al server. - Oggetto Dictionary che contiene i nomi di SQL Server e i relativi percorsi di chiave master di colonna attendibili. @@ -4332,9 +1857,6 @@ La connessione a un'istanza di SQL Server con mirroring mediante l'opzione ApplicationIntent ReadOnly non è supportata. - - Ricevute informazioni di routing impreviste. - Ricevute informazioni di routing non valide. @@ -4485,9 +2007,6 @@ Autenticazione non supportata specificata in questo contesto: {0} - - Timeout dell'autenticazione di Active Directory interattivo. L'utente ha impiegato troppo tempo per rispondere alla richiesta di autenticazione. - Non è possibile usare 'Authentication=Active Directory Interactive' se è stata impostata la proprietà Credential. @@ -4497,9 +2016,6 @@ ID processo server (SPID) della connessione attiva. - - Timeout dell'autenticazione del flusso di codice del dispositivo di Active Directory. L'utente ha impiegato troppo tempo per rispondere alla richiesta di autenticazione. - Non è possibile impostare la proprietà Credential se nella stringa di connessione è stato specificato 'Authentication=Active Directory Device Code Flow'. @@ -4533,9 +2049,6 @@ Per l'azione richiesta sono supportate solo matrici unidimensionali. - - Impossibile rimuovere l'elemento specificato, perché non si trova nella raccolta indicata. - Numero non negativo obbligatorio. @@ -4563,87 +2076,36 @@ Impossibile impostare la proprietà AccessTokenCallback se nella stringa di connessione è stato specificato 'Authentication=Active Directory Default'. - - {0} ha restituito {1}. - - - Errore di protocollo: un messaggio ricevuto contiene una firma valida, ma non è stato crittografato come richiesto dal livello di protezione effettivo. - - - {0} non è riuscito con errore {1}. - Questo metodo non è implementato da questa classe. - - Il pacchetto di sicurezza richiesto non è supportato. - Tipo physicalConnection non corretto. Si è verificata un'eccezione durante il tentativo di impostazione dell’opzione AppContext '{0}'={1}. - - Il tipo {0} non è supportato in questa piattaforma. - La modalità invariante di globalizzazione non è supportata. - - Non è possibile eseguire l'autenticazione tramite Kerberos. Verificare che Kerberos sia stato inizializzato nel client con 'kinit' e che sia stato registrato un nome dell'entità servizio per il server SQL in modo da consentire l'autenticazione Kerberos. - La parola chiave 'Network Library' non è supportata in questa piattaforma. Anteporre a "Origine dati" il protocollo desiderato ("tcp:" per una connessione TCP o "np:" per una connessione Named Pipe). Il socket non ha generato '{0}' previsto con codice errore '{1}'. - - Il server sta tentando di usare una funzionalità non supportata in questa piattaforma. - La parola chiave '{0}' non è supportata in questa piattaforma. Il file System.Transactions.dll attualmente caricato non supporta le transazioni globali. - - È stato ricevuto un token '{0}' non supportato durante la lettura dei dati dal server. - - - '{0}' non è un tipo di handle supportato. - SqlFileStream non è supportato in questa piattaforma. LocalDB non è supportato in questa piattaforma. - - Spazio buffer insufficiente. Richiesto: {0} Effettivo: {1}. - - - Operazione GSSAPI non riuscita con stato: {0} (stato secondario: {1}). - - - Operazione GSSAPI non riuscita. Errore: {0} ({1}). - - - Nessun supporto per l'associazione di canali in sistemi operativi diversi da Windows. - - - Il nome di destinazione non deve essere vuoto se vengono passate le credenziali predefinite. - - - Il livello di protezione richiesto non è supportato con l'implementazione GSSAPI attualmente installata. - - - L'implementazione del server non è supportata. - - - L'autenticazione NTLM non è possibile con le credenziali predefinite in questa piattaforma. - Quando viene usato da DataAdapter.Update, il valore del parametro viene modificato da DBNull.Value in (Int32)1 o (Int32)0 se non è nullo. @@ -4668,9 +2130,6 @@ Superata la dimensione massima del buffer: {0} - - DataColumn '{0}' mancante per la SourceColumn '{2}'. - L'elenco SqlBatchCommand non è stato inizializzato. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx index c6a208b5f7..f8b67f7cba 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 昇順 - Count={2} を持つこの {1} のインデックス {0} は無効です。 @@ -132,18 +129,12 @@ {0} は、既に別の {1} に含まれています。 - - {0} は、既にこの {1} に含まれています。 - {0} は Null でない {1} 型オブジェクトのみ受け入れます。 この {1} に含まれない {0} を削除しようとしました。 - - {0}.{1} は一意でなければなりません。'{2}' は既にコレクションに存在します。 - 接続が閉じられていません。{0}。 @@ -171,12 +162,6 @@ リーダーが閉じている場合は、{0} の呼び出しは無効です。 - - 接続が委任されたトランザクションまたは昇格されたトランザクションのプライマリ接続であるため、トランザクションに参加できません。 - - - 降順 - '{0}' パラメーターには空でない文字列が必要です。 @@ -204,9 +189,6 @@ ConnectionString プロパティが初期化されていません。 - - 非 CLS Exception が発生しました。 - 指定されたセキュリティ要素は、アクセス許可要素ではありません。 @@ -252,9 +234,6 @@ データ - - StateChange - 更新 @@ -264,48 +243,12 @@ 接続中であるか切断されているかを示す ConnectionState です。 - - 接続が状態を変更するときにイベントが発生します。 - - - パラメーターの一般的な型です。 - - - 入力、出力、または入出力のパラメーター。 - - - 厳密に型指定されたコード生成に使用されるデザイン タイムのプロパティです。 - - - 可変長データ型のオフセットです。 - - - パラメーターの名前です。 - - - 可変長データ型 (文字列および配列) のサイズ。 - - - DataAdapter.Update で使用するときに ColumnMappings の DataSetColumn 名の検索に使用する、基になる列の名前。これは、パラメーターとデータ行の間で値をコピーするためのものです。 - - - DataAdapter.Update (UpdateCommand のみ) で使用するときにデータ ソースの更新に使用する、DataRow 値のバージョン。 - - - DataAdapter.Update が使用する場合、Null 値でなければ、パラメーター値は DBNull.Value から (Int32)1 または (Int32)0 に変更されます。 - - - パラメーターの値です。 - SQL クエリの実行に失敗したため、'{0}' コレクションをビルドできません。詳細については、内部例外を参照してください。 要求されたスキーマ ('{0}') がサポートしているよりも多くの制約が指定されています。 - - '{2}' は、'{0}' スキーマ コレクションの '{1}' 制約としては無効な値です。 - 要求されたコレクション ({0}) は定義されていません。 @@ -315,15 +258,6 @@ 要求されたコレクション ({0}) は、プロバイダーのこのバージョンではサポートされていません。 - - 必要な DataSourceInformation テーブルの行のうち 1 つが見つかりません。 - - - DataSourceInformation テーブルには、1 行のみが保持されている必要があります。 - - - 制約コレクションで必須の列が 1 つ以上ありません。 - 制約コレクション内の行に一致しない制約があります。 @@ -342,42 +276,21 @@ メタデータ XML に '{0}' がありません。 - - メタベース XML が無効です。 - - - メタデータの XML が無効です。{0} コレクションには、文字列の列である {1} 列が必要です。 - メタデータの XML が無効です。{0} コレクションの {1} 列には、空でない文字列が設定されている必要があります。 - - アクション - - - 動作 - 塗りつぶし 情報メッセージ - - マッピング - StatementCompleted - - UDT - 通知 - - スキーマ - XML @@ -393,9 +306,6 @@ プーリング - - 名前付き ConnectionString - セキュリティ @@ -408,2073 +318,204 @@ 接続の復元性 - - カスタム ユーザー情報を保有するコレクションです。 - - - DataSet 内部で比較する文字列に関して、大文字小文字を区別するかどうかを示します。 - - - この DataSet の名前です。 - - - DataSet に含まれるデータのカスタム "ビュー" を示します。このビューを使うと、カスタム データ ビューでフィルター、検索およびナビゲーションを行うことができます。 - - - 制約ルールを適用するかどうかを示します。 - - - DataSet にエラーがあることを示します。 - - - DataSet 内の文字列を比較するロケールを示します。 - - - この DataSet で指定されたルート要素の XML URI 名前空間を示します。 - - - この DataSet に使われる名前空間のプレフィックスを示します。 - - - この DataSet のリレーションシップを保有するコレクションです。 - - - この DataSet のテーブルを保持するコレクションです。 - - - 同じ名前を持つ 2 つのテーブルに対してスキーマをマージできない場合に発生します。 - - - Initialization の終了時に発生します。 - - - データのメモリ内キャッシュを表します。 - - - このテーブル内で比較する文字列に対して大文字小文字を区別するかどうかを指定します。 - - - このテーブルの子リレーションシップを返します。 - - - このテーブルの列を含むコレクションです。 - - - このテーブルの制約を含むコレクションです。 - - - このテーブルが属する DataSet を示します。 - - - これは、テーブルの既定 DataView です。 - - - この行のデータ連結値の計算に使用する式です。 - - - テーブルにエラーがあるかどうかを返します。 - - - テーブル内の文字列を比較するロケールを示します。 - - - このテーブルの初期の開始サイズを示します。 - - - このテーブルに含まれる要素の XML URI 名前空間を示します。 - - - このテーブルに使われる名前空間のプレフィックスを XML で示します。 - - - このテーブルの親リレーションシップを返します。 - - - このテーブルの主キーを示す列を示します。 - - - このテーブルのデータの行を保持するコレクションを示します。 - - - DataSet の Tables コレクションでこのテーブルを照合するために使う名前を示します。 - - - テーブルの行が正常に編集された後に発生します。 - - - イベント ハンドラーが変更を修正またはキャンセルできるように行が変更されているときに発生します。ユーザーは行の値を変更することができます。また、編集をキャンセルするためには例外をスローする必要があります。 - - - テーブルの行が正常に削除された後に発生します。 - - - テーブルの行が削除にマークされているときに発生します。削除をキャンセルするには例外をスローしてください。 - - - この列の値が送信されたときに発生します。ユーザーはこの指定された値を変更できます。また、編集をキャンセルするためには例外をスローする必要があります。 - - - この列の値が変更されたときに発生します。 - - - テーブルのすべての行がクリアされる前に発生します。 - - - テーブルのすべての行が正常にクリアされた後に発生します。 - - - 新しい DataRow のインスタンスが作成された後に発生します。 - - - DataSet の Relations コレクションにあるこのリレーションシップを照合するために使われる名前です。 - - - このリレーションシップの子列を示します。 - - - このリレーションシップの親列を示します。 - - - リレーションシップが入れ子かどうかを示します。 - - - 削除操作において、このリレーションシップでどの連鎖処理を実行するかを示します。 - - - 更新操作において、このリレーションシップでどの連鎖処理を実行するかを示します。 - - - 承認および却下の変更において、このリレーションシップでどの連鎖処理を実行するかを示します。 - - - この制約の子列を示します。 - - - この制約の親列を示します。 - - - この制約の子テーブルを示します。 - - - この制約の列を示します。 - - - この制約がプライマリ キーかどうかを示します。 - - - この制約の名前を示します。 - - - この制約のテーブルを示します。 - - - この列で Null を指定できるかどうかを示します。 - - - テーブルに新たに追加された行に対して列が自動的にインクリメントするかどうかを示します。この列の型は、Int16、Int32 または Int64 でなければなりません。 - - - AutoIncrement 列の開始値を示します。 - - - AutoIncrement 列で使用されるインクリメントを示します。 - - - この列の、既定のユーザー インターフェイス タイトルを示します。 - - - DataTable の列コレクションでこの列を検索するために使用される名前を示します。 - - - この列が属する DataTable を返します。 - - - この列に保存されたデータの型を示します。 - - - 新しい行をテーブルに追加するときに使う既定の列値を示します。 - - - この列が、ユーザー入力を取り込まずに、ほかの列に基づいて各行の計算した値を示します。 - - - この列が XML 内に属性、要素、シンプル コンテンツ ノード、またはとして永続するか、あるいは永続しないかを示します。 - - - この列に格納される要素または属性の XML URI を示します。 - - - xml 表現で、この DataColumn に使われる Prefix を示します。 - - - Columns コレクションにおけるこの列のインデックスを示します。 - - - 行がテーブルに追加された後にこの列を変更できるかどうかを示します。 - - - 列の値がテーブルの行内で一意になるように制限するかどうかを示します。 - - - この列に許可された長さの最大値を示します。 - - - この DataColumn の DateTimeMode を示します。 - - - この DataView と、関連するユーザー インターフェイスが削除できるかどうかを示します。 - - - この DataView と、関連するユーザー インターフェイスが編集できるかどうかを示します。 - - - この DataView と、関連するユーザー インターフェイスに新しい行を追加できるかどうかを示します。 - - - 現在このビューで表示されている項目の数を返します。 - - - これにより、この DataSet を所有する DataViewManager にポインターを返します。 - - - ビューが開かれているかどうかを示します。 - - - この DataView によって返されたデータのフィルターに使われる式です。 - - - この DataView によって返されたデータのバージョンを示します。 - - - この DataView によって返されたデータの順序と列の名前を示します。 - - - Sort プロパティが設定されていない場合に、既定の並べ替えを使用するかどうかを示します。 - - - この DataView がデータの取得に使うテーブルを示します。 - - - この DataView によって返されたデータが何らかの理由によって変更されたことを示します。 - - - この DataViewManager のデータのソースを示します。 - - - 対応する DataSet のテーブルに対する並べ替え/フィルター/状態の設定を示します。 - - - DataSet は、'union' または 'list' を simpleType としてサポートしません。 - - - 無効な {0} 構文 : 必要な '{1}' 属性がありません。 - - - 属性 '{0}' の値 '{1}' が無効です。 - - - 属性 '{0}' の値は '{1}' か '{2}' でなければなりません。 - - - ElementType name='{0}' が見つかりません。 - - - リレーションシップ '{0}' に親テーブル名が見つかりません。 - - - リレーションシップ '{0}' に子テーブル名が見つかりません。 - - - リレーションシップ '{0}' に親テーブル キーが見つかりません。 - - - リレーションシップ '{0}' に子テーブル キーが見つかりません。 - - - 未定義のデータ型 : '{0}' です。 - - - データ型が定義されていません。 - - - フィールド ノード内部の XPath 選択が無効です。{0} が見つかりません。 - - - セレクター ノード内で XPath の選択が無効です : {0}. - - - {0} という制約内に無効な 'Key' ノードがあります。 - - - 制約名 {0} は既にスキーマで使われています。 - - - '{0}' から '{1}' に変換できません。 - - - '{2}' という '{1}' 制約に '{0}' の部分が見つかりません。 - - - 無効な Relation 定義 : キーの長さが異なります。 - - - DataSet doesn't は、'{0} という名の ComplexType で循環参照を許可しません。 - - - DataSet がノード {0} の抽象 ComplexType をインスタンス化できません。 - - - Xml Schema の書き込み中に複数のターゲット コンバーターでエラーが発生しました。詳細については、内部例外を参照してください。 - - - Xml Schema の書き込み中に複数のターゲット コンバーターでエラーが発生しました。null または空文字列が返されました。 - - - 宣言 '{0}' が重複しています。 - - - diffGram を読み込めません。テーブル '{0}' がターゲット データセットに見つかりません。 - - - diffGram を読み込めません。'sql' ノードが見つかりません。 - - - 列名 '{0}' は別のマップの種類に対して定義されています。 - - - プレフィックス '{0}' は有効ではありません。特殊な文字が含まれています。 - - - それ自体が入れ子になっているテーブル '{0}' の循環参照です。 - - - DataSet は本体を展開できません。XmlValidatingReader を使用して、EntityHandling プロパティを適切に設定してください。 - - - 型 '{0}' は IXmlSerializable インターフェイスを実装しないため、シリアル化を実行できません。 - - - シリアル化解除を実行できません。Data は IXMLSerializable を実装しないため、多態性はサポートされません。 - - - DataTable は Xml のスキーマの推論をサポートしていません。 - - - DataTable '{0}' のシリアル化を実行できません。この DataTable には、同一の外部キーに複数の親行がある DataRow が含まれています。 - - - 入力 Schema 内に IsDataSet 属性がありません。 - - - DataSet 要素を特定できません。IsDataSet 属性が 2 回以上存在します。 - - - DataSet は、IDynamicMetaObjectProvider を実装して IXmlSerializable を実装しない型をシリアル化しません。 - - - 機能が実装されていません。{0}. - - - 構文エラー : '{0}' 演算子の後にオペランドがありません。 - - - 式 '{0}' に型の不一致があります。 - - - 式が複雑すぎます。 - - - 列 [{0}] が見つかりません。 - - - 式に無効な文字列定数 : {0} が含まれています。 - - - 式に定義されていない関数呼び出し {0}() が含まれています。 - - - 式に構文エラーがあります。 - - - 無効な引数の数 : 関数 {0}() - - - 式に閉じかっこが見つかりません。 - - - 位置 {1} のトークン '{0}' を変換できません。 - - - {0} が必要ですが、位置 {2} の実際のトークンは {1} です。 - - - {0} から {1} に変換できません。 - - - 値 '{0}' は型 '{1} に変換できません。 - - - 列名 [{0}] が無効です。 - - - この式には無効な日付定数 '{0}' があります。 - - - IN 演算子の式リストでは、定数式のみを使用できます。 - - - Like 演算子のエラー : 文字列パターン '{0}' が無効です。 - - - 構文エラー : IN キーワードの後に続く項目は、カンマで区切ってかっこで閉じなければなりません。 - - - 関数引数に型の不一致があります : {0}()、引数 {1} ですが、{2} が必要です。 - - - 関数引数 : {0}() に型の不一致があります : 引数 {1} には Integer 型を指定してください。 - - - {1} および {2} で '{0}' 操作を実行できません。 - - - 型 '{1}' および '{2}' のオペランドの、演算子 '{0}' があいまいです。 符号付き型と符号なしの型を混合することはできません。明示的な Convert() 関数を使ってください。 - - - 構文エラー : IN キーワードの後には、カンマで区切られた、空でない式の一覧を指定しなければなりません。また、最後はかっこで閉じなければなりません。 - - - 式にサポートされない演算子 '{0}' が含まれています。 - - - 式に無効な名前 : '{0}' が含まれています。 - - - 構文エラー : '{0}' 演算子の前にオペランドがありません。 - - - 式に含まれる閉じかっこが多すぎます。 - - - テーブル [{0}] が 2 つ以上のリレーションシップで呼び出されました。式 '{1}' のリレーションシップ名を明示的に記述してください。 - - - 集約引数の構文エラー : 可能な 'Child' 修飾子を含む単一列の引数を指定してください。 - - - 集約式 '{0}' に非連結参照があります。 - - - カレント行なしで定数以外の式を評価することはできません。 - - - 式 '{0}' に非連結参照があります。 - - - 評価できません。式 '{0}' は集約ではありません。 - - - フィルター式 '{0}' は Boolean 型に対して評価しません。 - - - 型名 '{0}' が無効です。 - - - ルックアップ式の構文エラー : キーワード 'Parent' の後にリレーションシップ修飾子を含む単一列引数を指定してください : Parent[(<relation_name>)].<column_name - - - DataFilter を呼び出すには行またはテーブルが必要です。 - - - {0}() 引数が範囲外です。 - - - 構文エラー : 'Is' 演算子の使用が無効です。正しい構文 : <expression> Is [Not] Null + + 配列のオフセットおよび長さが制限を超えているか、カウンターがソース コレクションのインデックスから最後までの要素の数より大きい値です。 - - 値が型 '{0}' に対して大きすぎるか、小さすぎます。 + + 型 '{0}' のオブジェクトを型 '{1}' のオブジェクトに変換できません。 - - 0 除算エラーが発生しました。 + + プロパティ '{0}' に使用できる値は '{1}' のみです。 - - 親リレーションシップ '{0}' が見つかりません。 + + プロパティ '{0}' に使用できる値は '{1}' または '{2}' です。 - - 'hours' 引数が有効範囲を超えています。-14 から +14 までの値を指定してください。 + + 指定された QuotePrefix の値と QuoteSuffix の値が一致しません。 - - 'minutes' 引数が有効範囲を超えています。-59 から +59 までの値を指定してください。 + + 引数 '{0}' の長さが、上限の '{1}' を超えています。 - - タイム ゾーンに指定した範囲が、合計 14 時間を超えています。 + + キー '{0}' の値が無効です。 - - 指定した DateTime 引数の Kind プロパティが、'hours' 引数および 'minutes' 引数と一致しません。 + + '{0}' キーを使用するには、キーとして '{1}' が指定されている必要があります。 - - 型 '{0}' の DataColumn では式がサポートされていません。 + + キー '{0}' の値の長さが、制限 '{1}' を超えています。 - - 制約を有効にできませんでした。行に入力できるのは、Null 以外の値、一意な値、あるいは外部キーですが、この制約の違反が 1 つ以上の行で発生しています。 + + キーワード '{0}' はサポートされていません。 - - コレクション自体を変更することはできません。 + + .NET Framework Data Provider の内部エラー {0} です。 - - 指定された名前 '{0}' は、大文字小文字を区別しない場合は、コレクション オブジェクトの中で一致する名前が少なくとも 2 つあります。大文字小文字を区別する場合は、一致する名前はありません。 + + {0} "{1}"。 - - 指定された名前 '{0}' と同じ名前が、名前空間が異なるコレクション オブジェクト内に少なくとも 2 つあります。 + + {0} "{1}"。引用符の使い方に誤りがあります。 - - 配列のオフセットおよび長さが制限を超えているか、カウンターがソース コレクションのインデックスから最後までの要素の数より大きい値です。 + + {0} "{1}"。現在の制限値 "{2}" は十分ではありません。 - - '{0}' 引数が範囲外です。 + + SqlBulkCopy.DestinationTableName は無効なマルチパート名のため、SqlBulkCopy.WriteToServer が失敗しました。 - - '{0}' 引数を Null にすることはできません。 + + マルチパート名が無効なため、SQL Server からの結果を処理することができませんでした。 - - '{0}' 引数に Null 値が含まれています。 + + SqlParameter.UdtTypeName は、無効なマルチパート名です。 - - 列 {0} が見つかりません。 + + SqlParameter.TypeName が無効なマルチパート名です - - 列 '{0}' は既にこの DataTable に属しています。 + + SqlCommand.CommandText プロパティの値が無効なマルチパート名のため、SqlCommand.DeriveParameters が失敗しました。 - - 列 '{0}' は既に別の DataTable に属しています。 + + ミラー化された SQL Server インスタンスへの MultiSubnetFailover 接続オプションを使用した接続はサポートされていません。 - - DataTable に 2 つ以上の SimpleContent 列を設定することはできません。 + + {0} 列挙値 {1} は、{2} メソッドではサポートされていません。 - - SimpleContent 列を、要素列または入れ子にされたリレーションシップを含むテーブルには追加できません。 + + {0} の列挙値 {1} は、.NET Framework SqlClient データ プロバイダーではサポートされていません。 - - '{0}' という列は既にこの DataTable に属しています。 + + {0}: CommandText プロパティは初期化されていません。 - - '{0}' という列を追加できません。: 同じ名前の、入れ子にされたテーブルが既にこの DataTable に属しています。 + + {0}: Connection プロパティは初期化されていません。 - - '{0}' という列が既にこの DataTable に属しています : 入れ子されたテーブル名を同じ名前に設定できません。 + + {0} には Connection が開かれていて使用可能なことが必要です。{1} - - このテーブルに属していない列を削除することはできません。 + + ストアド プロシージャ '{0}' は存在しません。 - - この列は、主キーの一部であるため、削除できません。 + + トランザクションが、現在の接続に関連付けられていないか、完了していません。 - - この列は、リレーションシップ {0} の親キーの一部になっているため、削除できません。 + + 現在の接続と関連付けられているトランザクションは完了しましたが、破棄されていません。トランザクションは、接続が SQL ステートメントの実行に使用される前に破棄されなければなりません。 - - この列は、テーブル {1} で制約 {0} の一部であるため、削除できません。 + + {0} は、コマンドに割り当てられた接続が保留状態であるローカルのトランザクションにあるとき、トランザクション オブジェクトを持つコマンドが必要です。コマンドの Transaction プロパティがまだ初期化されていません。 - - この列は、式 {0} = {1} の一部であるため、削除できません。 + + この {0} に関連付けられている DataReader が既に開かれています。これを最初に閉じる必要があります。 - - 計算列の AutoIncrement プロパティを設定できません。 + + {0} DeriveParameters は、CommandType.{1} ではなく、 CommandType.StoredProcedure のみサポートします。 - - DefaultValue が設定された列に対して AutoIncrement プロパティを設定できません。 - - - AutoIncrement 列で DefaultValue を設定できません。 - - - AutoIncrementStep は 0 以外の値でなければなりません。 - - - ColumnName は、それが DataTable の一部である場合には必要です。 - - - データを指定した後に DataType を変更することはできません。 - - - 列には有効な DataType が必要です。 - - - 列 {0} の DefaultValue は {1} 型です。{2} に変換することはできません。 - - - 列の DefaultValue は {0} 型です。{1} に変更することはできません。 - - - 列 {0} の DefaultValue は {1} 型ですが、この列は {2} 型です。 - - - Expression 列の ReadOnly プロパティを変更できません。 - - - 式列の Unique プロパティを変更することはできません。 - - - AutoIncrement または Unique を持つ列で式を作成することはできません。 - - - 列を ReadOnly モードにできないため、式を設定できません。 - - - 列 {0} に Expression プロパティを設定できません。これは制約の一部です。 - - - Expression 列 {0} に基づく制約を作成できません。 - - - 式に循環参照が発生したため、Expression プロパティを設定できません。 - - - 列 '{0}' には null 値が含まれています。 - - - 列 '{0}' に nulls を使用することはできません。 - - - 列 '{0}' は読み取り専用です。 - - - 列 '{0}' には一意でない値が含まれています。 - - - 列 '{0}' はテーブル {1} に属していません。 - - - 列はテーブルに属していなければなりません。 - - - 列 {1} に <{0}> を格納できませんでした。 必要な型は {2} です。 - - - Column '{0}' を null に設定できません。DBNull を使用してください。 - - - 列 '{0}' を設定できません。値が、この列に指定できる MaxLength の制限を超えています。 - - - MaxLength は、文字列データ型にのみ適用します。Column '{0}' プロパティの MaxLength を、負でない数に設定することはできません。 - - - Column '{0}' プロパティの MaxLength を '{1}' に設定できません。テーブルに、新しい制限よりも長い文字列が少なくとも 1 つあります。 - - - Column '{0}' プロパティの MaxLength を設定できません。この Column は SimpleContent です。 - - - Column '{0}' プロパティの DataType を {1} に設定できません。この Column は SimpleContent です。 - - - Column '{0}' プロパティの MappingType を SimpleContent に設定できません。Column の DataType は {1} です。 - - - Column '{0}' が MaxLength の制限を超えています。 - - - 列 '{0}' で DBNull.Value を使用できません。 - - - Column '{0}' プロパティの Namespace を変更できません。この Column は SimpleContent です。 - - - '{0}' 型 の DataColumn AutoIncrement は、一度データが設定されたら変更できません。 - - - 列 '{0}' は基になるテーブル {1} に属していません。 - - - '{0}' 型の DataColumn は complexType です。複合型の値を Attribute としてシリアル化することはできません - - - DateTimeMode を設定できるのは、DateTime 型の DataColumns に対してのみです。 - - - '{0}' は無効な DataSetDateTime 値です。 - - - テーブルにデータを指定した後は、DateTimeMode を '{0}' から '{1}' に変更することはできません。 - - - Type '{0}' には static Null プロパティまたはフィールドがありません。 - - - 型 '{0}' は IRevertibleChangeTracking を実装しないため、RejectChanges() を実行できません。 - - - SetAdded および SetModified を呼び出せるのは、DataRowState プロパティの値が Unchanged である DataRows オブジェクトからのみです。 - - - 序数 {0} は最大数を超えています。 - - - DataSet は System.Nullable<> をサポートしていません。 - - - 制約の名前が ConstraintCollection にあるときは、それを空の文字列に変更することはできません。 - - - 制約 {0} の制約を適用できません。 - - - 列 '{0}' は一意であるように制約されています。値 '{1}' は既に存在します。 - - - 制約 '{0}' はこの DataTable に属していません。 - - - 制約 {0} が見つかりません。 - - - {0} という制約に一致する制約が既にコレクションに存在します。 - - - '{0}' という Constraint は既にこの DataTable に属しています。 - - - これらの列には現在一意の値が含まれていません。 - - - これらの列はこのテーブルをポイントしていません。 - - - 対応する親の値が指定されてない値があるため、この制約を有効にできません。 - - - ForeignKey がテーブル {0} に属していないため、この制約を追加できません。 - - - このテーブルに属していない制約を削除することはできません。 - - - 一意性制約 '{0}' を削除できません。外部キー制約 '{1}' を最初に削除してください。 - - - 制約がリレーションシップ {0} で適用され、この行を削除すると子行が孤立するため、この行を削除できません。 - - - 制約がリレーションシップ {0} で適用され、この値を変更すると子行が孤立するため、変更できません。 - - - ForeignKeyConstraint {1} によって制約が実行され、{2} に子行が存在するため、テーブル {0} を消去できません。 - - - ForeignKeyConstraint {0} には親テーブルに存在している子キー値 ({1}) が必要です。 - - - 以下の理由によりプロパティにアクセスできません : '{0}' - - - この行を削除できません。この行には子行が含まれており、リレーションシップ {0} の制約が適用されます。 - - - 主キーは既にテーブルで設定されているので、主キーの制約を追加できません。 - - - 入れ子になった 2 つのリレーションシップでは、子テーブルである DataTable '{0}' に制約を追加することはできません。 - - - 異なるテーブルに属する列からキーを作成することはできません。 - - - 0 個の列を指定することはできません。 - - - {0} 列を超える列を指定することはできません。 - - - 同じ列が 2 つ以上リストにある場合はキーを作成できません : '{0}' - - - テーブルの主キーであるため、一意性制約を削除できません。 - - - テーブル {0} の主キーであるため、一意性制約を削除できません。 - - - 親列および子列に型が一致する列が含まれていません。 - - - ParentKey と ChildKey が同一です。 - - - ParentColumns と ChildColumns は同じ長さでなければなりません。 - - - ParentColumns と ChildColumns の長さを 0 にすることはできません。 - - - この行は、このリレーションシップと同じ DataSet に属していません。 - - - RelationName は、DataSet の一部になっている場合には必要です。 - - - GetChildRows には、Table {0} に属する行が必要ですが、指定された行の Table は {1} です。 - - - このリレーションシップをこの DataSet に追加するには、DataSet にある 2 つのテーブルをこのリレーションシップで関連付けなければなりません。 - - - GetParentRows には、テーブル {0} に属する行が必要ですが、指定された行のテーブルは {1} です。 - - - SetParentRow にはテーブル {0} の子行が必要ですが、指定された行のテーブルは {1} です。 - - - 異なる DataSet のテーブル間にリレーションシップを指定することはできません。 - - - 異なる DataSet のテーブル間にリレーションシップを作成することはできません。 - - - これらの子列には既にリレーションシップが存在します。 - - - このリレーションシップはこのリレーションシップ コレクションに属していません。 - - - このリレーションシップは既に別の DataSet に属しています。 - - - このリレーションシップは既にこの DataSet に属しています。 - - - '{0}' という Relation は既にこの DataSet に属しています。 - - - Relation {0} はこの DataSet に属していません。 - - - リレーションシップ {0} が見つかりません。 - - - null のテーブルにコレクションを作成することはできません。 - - - このコレクションがリレーションシップを示すテーブルは、DataSet から削除されています。 - - - 子テーブルに指定されていないこのテーブルの ParentRelation コレクションにリレーションシップを追加することはできません。 - - - リレーションシップをこのテーブルの ChildRelation コレクションに追加できません。このテーブルは親テーブルではありません。 - - - このリレーションシップに対し、'Nested' プロパティを false に設定することはできません。 - - - 入れ子になった 2 つのリレーションシップでは、同じテーブル '{0}' を子テーブルにすることはできません。 - - - テーブル ({0}) を、入れ子にされたリレーションシップでそれ自体の子テーブルにすることはできません。 - - - 親テーブルと子テーブル間で異なる Locale または CaseSensitive 設定を含む DataRelation または Constraint を追加できません。 - - - 親の列または子の列が DataSet にない場合は、DataRelation を作成できません。 - - - それ自体の名前空間を継承する入れ子テーブル '{0}' は、名前空間が異なる複数の親テーブルを持つことはできません。 - - - 名前空間が空で、入れ子になったテーブル '{0}' は、異なる名前空間の親テーブルを複数持つことはできません。 - - - この行は、このリレーションシップと同じ DataSet に属していません。 - - - テーブルにない行でこの操作を実行することはできません。 - - - このリレーションシップおよび子行は同じ DataSet に属していません。 - - - RowChanging イベント内の各値を変更することはできません。 - - - OnRowChanging イベント内部で EndEdit() を呼び出すことはできません。 - - - RowChanging イベント内部で BeginEdit() を呼び出すことはできません。 - - - OnRowChanging イベント内部で CancelEdit() を呼び出すことはできません。この更新をキャンセルするには exception をスローしてください。 - - - OnRowDeleting イベント内部で Delete を呼び出せません。この Delete をキャンセルするには、exception をスローしてください。 - - - 入力アレイの長さがこのテーブルの列数を超えています。 - - - アクセスする Current データがありません。 - - - アクセスする Original データがありません。 - - - アクセスする Proposed データがありません。 - - - この行はテーブルから削除されており、データは含まれていません。BeginEdit() を使うとこの行に新しいデータを作成することができます。 - - - 削除された行を通して、その行の情報にアクセスすることはできません。 - - - バージョンは、Original、Current、または Proposed でなければなりません。 - - - 位置 {0} に行がありません。 - - - 行の挿入位置 {0} が無効です。 - - - '{1}' という名のテーブルの rowOrder value={0} が 2 度見つかりました。 - - - テーブル '{0}' の rowOrder シーケンスに値がありません。 - - - 指定された DataRow は現在の DataRowCollection ではありません。 - - - この行は既に別のテーブルに属しています。 - - - この行は既にこのテーブルに属しています。 - - - この行は既に削除されているため、さらに削除することはできません。 - - - この行は空です。 - - - 既に削除されている行を削除することはできません。 - - - 子行に複数の親行が含まれています。 - - - 行状態のビット パターンを認識できません。 - - - DataSet の名前を空の文字列に変更できません。 - - - 名前 '{0}' が無効です。DataSet に DataTable と同じ名前を指定することはできません。 - - - スキーマ名前空間が無効です。{0} を使用してください。 - - - CaseSensitive または Locale プロパティを変更できません。この変更によって、関連テーブル間に少なくとも1 つの DataRelation または Constraint の異なる Locale または CaseSensitive 設定が指定されることになります。 - - - 派生した DataSet に SchemaSerializationMode のプロパティがオーバーライドされる場合のみ、このプロパティを設定することができます。 - - - PrimaryKey 列はこのテーブルのものではありません。 - - - 入れ子にされたリレーションシップまたは要素列を、SimpleContent 列を含むテーブルに追加することはできません。 - - - TableName は、DataSet の一部になっている場合には必要です。 - - - DataTable は既に単一のコンテンツ コラムを持っています。 - - - テーブルに主キーがありません。 - - - {0} は有効な Sort 文字列エントリではありません。 - - - DataTable をシリアル化できません。1 つ以上の DataColumn 式中で使われている DataTable が、現在の DataTable の子孫ではありません。 - - - この DataTable は、DataSet の一部としてリモートから操作できません。1 つ以上の複数の式列に他の DataTable への参照が保持されています。 - - - DataSet および DataTable のリモーティング フォーマット プロパティの値は、同じでなくてはなりません。 - - - DataTable をシリアル化できません。DataTable の名前が設定されていません。 - - - '{0}' という DataTable は既にこの DataSet に属しています。 - - - 同じ Namespace '{1}' の '{0}' という DataTable は、既にこの DataSet に属しています。 - - - 入れ子にされたリレーションシップで、テーブル ({0}) をそれ自体の子テーブルにすることはできません。DataSet 名がテーブル名と競合しています。 - - - 名前 '{0}' は無効です。DataTable に DataSet と同じ名前を指定することはできません。 - - - DataTable は既に別の DataSet に属しています。 - - - DataTable は既にこの DataSet に属しています。 - - - テーブル {0} はこの DataSet に属していません。 - - - テーブル {0} が見つかりません。 - - - 既存のリレーションシップを含むテーブルを削除することはできません。最初にリレーションシップを削除してください。 - - - {0} は ForeignKeyConstraint {1} で参照されているため、削除できません。最初に制約を削除してください。 - - - DataTable '{0}' は、ソースの DataTable のどれにも一致しません。 - - - ターゲットの DataSet に {0} の定義が見つかりません。 - - - ターゲットの DataSet に {0} {1} がありません。 - - - <target>.{0} と <source>.{0} は競合するプロパティがあります : DataType プロパティの不一致 - - - <target>.PrimaryKey と <source>.PrimaryKey の Length が異なります。 - - - PrimaryKey 内に列の不一致があります : <target>.{0} に対する <source>.{1} です。 - - - キーに一致しない列があるため、Relation {0} をマージできません。 - - - ターゲット テーブル {0} に列 {1} の定義が見つかりません。 - - - PrimaryKey 列 {0} は、ソースの Table にありません。 - - - RowStates パラメーターには、DataViewRowState 列挙値の有効な組み合わせを設定する必要があります。 - - - Find は、並べ替え順序に基づいて行を検索しますが、並べ替え順序が指定されていません。 - - - インデックスを付加するキーには {0} 値が必要ですが、{1} 値を取得しました。 - - - 集約関数 {0}() および型 : {1} の使用が無効です。 - - - ストレージ型 : {0} が無効です。 - - - DataSet Xml 持続性は、値 '{0}' を Char 値としてサポートしません。Byte ストレージを使用してください。 - - - 値の型が列の型と合いません。 - - - 型 '{0}' には IComparable インターフェイスは実装されていません。比較を実行できません。 - - - {0} を設定できません。 - - - 既に DataSet の既定ビューになっている DataViewManager で DataSet を変更することはできません。 - - - RowStateFilter で、ModifiedOriginals と ModifiedCurrents を同時に表示することはできません。 - - - DataViewManager の DefaultView または DataView で Table プロパティを変更することはできません。 - - - 設定された DataSet プロパティを変更することはできません。 - - - DataViewManager を使う前に DataSet を設定しなければなりません。 - - - 設定された Table プロパティを変更することはできません。 - - - DataView を使う前に DataTable を設定しなければなりません。 - - - 名前のない DataTable にはバインドできません。 - - - オブジェクトをこの一覧に設定できません。 - - - AllowNew が False のときに DataView で AddNew を呼び出すことはできません。 - - - DataView が開かれていません。 - - - リレーションシップがこの DataView がポイントするテーブルの親になっていません。 - - - AllowDelete が false のときに DataSource で削除することはできません。 - - - AllowEdit が false のとき、DataSource を編集することはできません。 - - - インデックス {0} は負の値であるか、行カウントの合計よりも小さい値です。 - - - 外部オブジェクトをこの一覧に追加できません。 - - - この一覧をクリアできません。 - - - 外部オブジェクトをこの一覧に挿入することはできません。 - - - 一覧にないオブジェクトを削除できません。 - - - {0} は、テーブル {1} の DataColumn でも DataRelation でもありません。 - - - Range オブジェクトの Min ({0}) は、 max ({1}) 以下でなければなりません。 - - - 範囲が null です。 - - - MinimumCapacity は負でない値でなければなりません。 - - - 名前 '{0}' のための識別子を生成できません。 - - - '{0}' という名前のテーブルが複数あります (名前空間は異なる場合があります)。 - - - 列 '{0}': 型 '{1}' を Null にすることはできません。 - - - 列 '{0}': 型 '{1}' にはパラメーターなしのコンストラクターがありません。 - - - 列 '{0}': 型 '{1}' には文字列引数を持つコンストラクターがありません。 - - - 型 '{0}' のオブジェクトを型 '{1}' のオブジェクトに変換できません。 - - - Data Exception が発生しました。 - - - Constraint Exception が発生しました。 - - - 削除された行にはアクセスできません。 - - - 重複した名前は許可されていません。 - - - RowChanging イベントではサポートされない操作です。 - - - 制約が無効です。 - - - 主キーがありません。 - - - Null は許可されていません。 - - - 列は読み取り専用としてマークされています。 - - - テーブルに行がありません。 - - - バージョンが見つかりません。 - - - ReadOnly データが変更されています。 - - - DataTableReader は、現在の DataTable '{0}' に対しては無効です。 - - - DataTableReader に保持されている DataTable '{0}' のスキーマは変更されています。DataTableReader は無効です。 - - - DataTableReader を作成できません。DataSet に DataTable がありません。 - - - DataTableReader を作成できません。引数が空です。 - - - DataTableReader を作成できません。引数に null 値が保持されています。 - - - 現在の DataRow の状態は Deleted であるか Detached です。 - - - 現在の DataTable '{0}' は空です。DataTable に DataRow がありません。 - - - DataTable の内部インデックスが破損しています。'{0}' - - - コレクションが修正されました。列挙操作が実行されない可能性があります。 - - - 単純型 '{0}' は既に別の '{1}' を使用して宣言されています。 - - - foliation が無効です。 - - - テーブル名に関連付けられた DataSet が読み込まれた XML ドキュメントに割り当てられた後は、このテーブル名を変更できません。 - - - テーブル名前空間に関連付けられた DataSet が読み込まれた XML ドキュメントに割り当てられた後は、このテーブル名前空間を変更できません。 - - - 列名に関連付けられた DataSet が、読み込まれた XML ドキュメントに割り当てられた後は、この列名を変更できません。 - - - 列名前空間に関連付けられた DataSet が、読み込まれた XML ドキュメントに割り当てられた後は、この列名前空間を変更できません。 - - - 関連付けられた DataSet が、読み込まれた XML ドキュメントに割り当てられた後に ColumnMapping プロパティを変更できません。 - - - DataSet が読み込まれた XML ドキュメントに割り当てられた後に、テーブルの列を追加または削除することはできません。 - - - DataSet が読み込まれた XML ドキュメントに割り当てられた後に、DataSet のテーブルを追加または除することはできません。 - - - DataSet が読み込まれた XML ドキュメントに割り当てられた後に、入れ子にされたデータセットのリレーションシップを追加、削除、または変更することはできません。 - - - DataSet パラメーターが無効です。null にすることはできません。 - - - DataSet が読み込まれた XML ドキュメントに割り当てられた後は DataSet 名を変更できません。 - - - この種類のノードは複製できません : {0} - - - XmlDataDocument が既にデータを含んでいる場合はこの XmlDataDocument を読み込めません。新しい XmlDataDocument を使ってください。 - - - DataSet は 1 つの XmlDataDocument のみに関連付けられます。この DataSet は既にほかの XmlDataDocument に関連付けられているため、現在の XmlDataDocument に DataSet を関連付けることはできません。 - - - XML 操作を使って XmlDataDocument を編集する前に、DataSet.EnforceConstraints == false に設定してください。 - - - GetElementById() は DataDocument でサポートされていません。 - - - DataDocument にエンティティ参照を作成できません。 - - - DateSet および DataTable の Clear 機能は XmlDataDocument でサポートされていません。 - - - 列がこの dataSet に組み込まれているため、削除できません。 - - - リレーションシップがこの dataSet に組み込まれているため、削除できません。 - - - このコントロールのプロパティが変わるときに発生します。 - - - このコレクションのメンバーシップが変わるたびに発生します。 - - - 値が DBNull のため、取得できません。 - - - '{0}' プロパティを使用する場合は、Microsoft WindowsNT または WindowsNT ベースのオペレーティング システムが必要になります。 - - - 要求された .NET Framework データ プロバイダーが見つかりません。これはインストールされていない可能性があります。 - - - 要求された .NET Framework データ プロバイダーの実装には、System.Data.Common.DbProviderFactory の派生型の Instance フィールドがありません。 - - - 登録されている .NET Framework データ プロバイダーが見つからないか、読み込めませんでした。 - - - 見つからない .NET Framework データ プロバイダーのアセンブリ修飾名は必須です。 - - - 指定できるのは要素だけです。 - - - 子ノードは指定できません。 - - - 認識されていない属性 '{0}' です。 - - - 認識されない要素です。 - - - '{0}' セクションは、1 つの構成ファイルで 1 回しか使用できません。 - - - 必要な属性 '{0}' が見つかりません。 - - - 必要な属性 '{0}' を空にすることはできません。 - - - '{0}' パラメーターには空でない配列が必要です。 - - - プロパティ '{0}' に使用できる値は '{1}' のみです。 - - - プロパティ '{0}' に使用できる値は '{1}' または '{2}' です。 - - - 指定された QuotePrefix の値と QuoteSuffix の値が一致しません。 - - - 引数 '{0}' の長さが、上限の '{1}' を超えています。 - - - 型 {1} の引数を予期していましたが、型 {0} が渡されました。 - - - キー '{0}' の値が無効です。 - - - '{0}' キーを使用するには、キーとして '{1}' が指定されている必要があります。 - - - キー '{0}' の値の長さが、制限 '{1}' を超えています。 - - - キーワード '{0}' はサポートされていません。 - - - UDL ファイルを読み込めません。 - - - 無効な UDL ファイルです。 - - - .NET Framework Data Provider の内部エラー {0} です。 - - - QuotePrefix および QuoteSuffix プロパティは、Insert、Update または Delete コマンドが一度生成されると、もはや変更できません。 - - - DataAdapter.SelectCommand プロパティを初期化する必要があります。 - - - DataAdapter.SelectCommand.Connection プロパティは初期化する必要があります。 - - - {0} "{1}"。 - - - {0} "{1}"。引用符の使い方に誤りがあります。 - - - {0} "{1}"。現在の制限値 "{2}" は十分ではありません。 - - - SqlBulkCopy.DestinationTableName は無効なマルチパート名のため、SqlBulkCopy.WriteToServer が失敗しました。 - - - マルチパート名が無効なため、SQL Server からの結果を処理することができませんでした。 - - - SqlParameter.UdtTypeName は、無効なマルチパート名です。 - - - SqlParameter.TypeName が無効なマルチパート名です - - - SqlCommand.CommandText プロパティの値が無効なマルチパート名のため、SqlCommand.DeriveParameters が失敗しました。 - - - OdbcCommand.CommandText プロパティの値が無効なマルチパート名のため、OdbcCommandBuilder.DeriveParameters が失敗しました。 - - - OleDbCommandBuilder.CommandText プロパティの値が無効なマルチパート名のため、OleDbCommandBuilder.DeriveParameters が失敗しました。 - - - ミラー化された SQL Server インスタンスへの MultiSubnetFailover 接続オプションを使用した接続はサポートされていません。 - - - DataColumn '{1}' は計算列であるため、SourceColumn '{0}' からの列マップは失敗しました。 - - - SourceColumn '{1}' の '{0}' と {3} の DataColumn '{2}' の間に変換不可能な型の不一致があります。 - - - SourceColumn '{2}' の DataTable '{1}' に DataColumn '{0}' が見つかりません。 - - - SourceColumn には空でない文字列が必要です。 - - - '{0}' の SourceColumn マップが見つかりません。 - - - {0} 列挙値 {1} は、{2} メソッドではサポートされていません。 - - - {0} の列挙値 {1} は、.NET Framework Odbc データ プロバイダーではサポートされていません。 - - - {0} の列挙値 {1} は、.NET Framework OleDb データ プロバイダーではサポートされていません。 - - - {0} の列挙値 {1} は、.NET Framework SqlClient データ プロバイダーではサポートされていません。 - - - ComputerNameDnsFullyQualified を取得することができません。{0}。 - - - '{1}' SourceTable の '{0}' DataTable が見つかりません。 - - - SourceTable には空でない文字列が必要です。 - - - SourceTable マップ : '{0}' が見つかりません。 - - - {0}: CommandText プロパティは初期化されていません。 - - - {0}: Connection プロパティは初期化されていません。 - - - {0} には Connection が開かれていて使用可能なことが必要です。{1} - - - Fill: SelectCommand.Connection プロパティが初期化されていません。 - - - FillPage: SelectCommand.Connection プロパティが初期化されていません。 - - - FillSchema: SelectCommand.Connection プロパティが初期化されていません。 - - - 更新には、InsertCommand に接続オブジェクトを指定する必要があります。InsertCommand の Connection プロパティは初期化されていません。 - - - 更新には、UpdateCommand に接続オブジェクトを指定する必要があります。UpdateCommand の Connection プロパティは初期化されていません。 - - - 更新には、DeleteCommand に接続オブジェクトを指定する必要があります。DeleteCommand の Connection プロパティは初期化されていません。 - - - 更新には接続オブジェクトが必要ですが、Connection プロパティが初期化されていません。 - - - 更新には、コマンドの複製に接続オブジェクトを指定する必要があります。コマンドの複製の Connection プロパティは初期化されていません。 - - - 更新には接続が必要です。 - - - Update は {0}Command を使用して、開かれた接続オブジェクトを持つ必要があります。{1} - - - Update は {0}Command を使用して、開かれた接続オブジェクトを持つ必要があります。{1} - - - Update は {0}Command を使用して、開かれた接続オブジェクトを持つ必要があります。{1} - - - Update は更新コマンドを使用して、開かれた接続オブジェクトを持つ必要があります。{1} - - - ストアド プロシージャ '{0}' は存在しません。 - - - このコマンドに割り当てられたトランザクションは、最下層で入れ子にされた保留中のローカル トランザクションでなければなりません。 - - - トランザクションが、現在の接続に関連付けられていないか、完了していません。 - - - 現在の接続と関連付けられているトランザクションは完了しましたが、破棄されていません。トランザクションは、接続が SQL ステートメントの実行に使用される前に破棄されなければなりません。 - - - {0} は、コマンドに割り当てられた接続が保留状態であるローカルのトランザクションにあるとき、トランザクション オブジェクトを持つコマンドが必要です。コマンドの Transaction プロパティがまだ初期化されていません。 - - - このコマンドに関連付けられている SqlResultSet が既に開かれています。これを先に閉じる必要があります。 - - - この {0} に関連付けられている DataReader が既に開かれています。これを最初に閉じる必要があります。 - - - {0} DeriveParameters は、CommandType.{1} ではなく、 CommandType.StoredProcedure のみサポートします。 - - - 同じ実行操作に対して、メソッド '{0}' を複数回呼び出すことはできません。 - - - 非同期の結果が正しくありません。 - - - '{0}' を呼び出す前に、SelectCommand プロパティが初期化されませんでした。 - - - StatementType {0} がここに存在するのは誤りです。 - - - FillSchema: SourceTable 名に空の文字列を指定できません。 - - - {0} の MaxRecords 値が無効です。値は 0 以上でなければなりません。 - - - {0} の StartRecord 値が無効です。値は 0 以上でなければなりません。 - - - Fill: SourceTable 名に空の文字列を指定できません。 - - - 階層チャプター コラムは AutoIncrement DataColumn に割り当てしなければなりません。 - - - DataReader.GetFieldType({0}) から null が返されました。 - - - startRecords または maxRecords の値に 0 以外を使用するときは、配列 dataTables には項目を 1 つだけ指定してください。 - - - Update で TableMapping['{0}'] 、または DataTable '{0}' が見つかりませんでした。 - - - Update : 空でない SourceTable 名を指定してください。 - - - TableMapping.DataSetTable='{0}' のときに TableMapping が見つかりません。 - - - Update ではコマンドの複製が有効である必要があります。 - - - Update 中の自動 SQL 生成には、有効な SelectCommand が必要です。 - - - Update には、新しい行を含む DataRow コレクションが渡されたとき、有効な InsertCommand が必要です。 - - - 更新には、変更された行を含む DataRow コレクションが渡されたとき、有効な UpdateCommand が必要です。 - - - Update には、削除された行を含む DataRow コレクションが渡されたとき、有効な DeleteCommand が必要です。 - - - DataRow[{0}] は DataRow[0] とは異なる DataTable からのものです。 - - - RowUpdatedEvent : エラーが発生しました。エラーの詳細情報はありません。 - - - RowUpdatingEvent : エラーが発生しました。エラーの詳細情報はありません。 - - - バッチ処理をする場合、コマンドの UpdateRowSource.FirstReturnedRecord または UpdateRowSource.Both の UpdatedRowSource プロパティの値は無効です。 - - - 同時実行違反 : UpdateCommand によって、処理予定の {1} レコードのうち {0} 件が処理されました。 - - - 同時実行違反 : DeleteCommand によって、処理予定の {1} レコードのうち {0} 件が処理されました。 - - - 同時実行違反 : バッチ処理されたコマンドによって、処理予定の {1} レコードのうち {0} 件が処理されました。 + + 同じ実行操作に対して、メソッド '{0}' を複数回呼び出すことはできません。 CommandTimeout 値 {0} が無効です。値は 0 以上でなければなりません。 - - '{1}[{0}]: Size プロパティのサイズが 0 です。これは無効です。 - - - {0}.Prepare メソッドは、パラメーターはすべて明示的に設定された型を持つことを要求します。 - - - {0}.Prepare メソッドは、可変長のパラメーターはすべて明示的に設定された non-zero Size を持つことを要求します。 - - - {0}.Prepare メソッドは明示的に設定された Precision および Scale を持つ型 '{1}' のパラメーターが必要です。 - - - asyncResult の end メソッド呼び出しが適切ではありません。{0} を呼び出す必要がありますが、{1} が呼び出されました。 - - - 操作が無効です。接続は閉じています。 - - - 接続が無効にされています。 - - - 接続上でローカル トランザクションが実行中のため、トランザクションに参加できません。ローカル トランザクションを終了して、再試行してください。 - - - 接続には現在参加しているトランザクションがあります。現在のトランザクションを終了して、再試行してください。 - - - データベースを Null、空の文字列またはスペースのみを含む文字列にすることはできません。 - - - 引数が長すぎます。 - - - Connect Timeout' の値が無効です。値は、0 以上の整数値でなければなりません。 - - - ソース バッファー (サイズ {0}) オフセットが無効です : {1} - - - ターゲット バッファー (サイズ {0}) オフセット : {1} が無効です。 - - - この数値は 10 進数の 96 ビットに適合させるには大きすぎます。 - - - ストリームが閉じているときに {0} を実行しようとしましたが無効です。 - - - 指定した SeekOrigin の値は無効です。 - - - 複数のベース テーブルに対する動的 SQL の生成はサポートされません。 - - - 動的 SQL 生成は、ベース テーブル情報を返さない SelectCommand に対してサポートされていません。 - - - DeleteCommand の動的 SQL 生成は、キー列情報を返さない SelectCommand に対してはサポートされていません。 - - - UpdateCommand の動的 SQL 生成は、キー列情報を返さない SelectCommand に対してはサポートされていません。 - - - DeleteCommand の動的 SQL 生成は、行バージョンの列を含まない SelectCommand に対してはサポートされていません。 - - - UpdateCommand の動的 SQL 生成は、行バージョンの列を含まない SelectCommand に対してはサポートされていません。 - - - QuotePrefix または QuoteSuffix 文字 '{1}' を含むテーブル名 '{0}' は、動的 SQL 生成のサポートの対象になっていません。 - - - 列番号 '{0}' から読み取ろうとしました。これは無効な処理です。CommandBehavior.SequentialAccess を使う場合、序数値 '{1}' 以上の列からのみ読み取ることができます。 - - - 無効な日付と時刻の数字が指定されているため、データ型 '{0}' をリテラルとしてフォーマットできません。 - - - この値は、要求したデータ型のリテラルとしてフォーマットできません。 - - - 無効な最大スケールがデータ型 '{0}' に含まれているため、これをリテラルとしてフォーマットすることはできません。 - - - 指定されたリテラル値は、データ型 '{0}' のリテラルとしては無効です。 - - - '{0}': リテラル値の長さは偶数である必要があります。 - - - '{0}': リテラル値は 16 進数表記の文字列である必要があります。 - - - 引用符で囲まれたプレフィックスが設定されていない場合、{0} には開かれている接続が必要です。 - - - 正しいブール型のリテラル値を特定できません。Boolean リテラルを作成できません。 - - - データ型 '{0}' に関連付けられたネイティブ データ型のリテラルはサポートされていません。 - - - {0} のパラメーター データ型が無効です。 - - - オブジェクトの型 {0} から既知のマネージ プロバイダーのネイティブ型へのマップがありません。 - - - 型 {1} によって返された不明な TypeCode {0} を処理できません。 - - - DbType {0} から周知の型 {1} へのマッピングは存在しません。 - - - 使用されている SQL Server のバージョンでは、データ型 '{0}' がサポートされていません。 - - - パラメーター値 '{0}' が範囲外です。 - - - 指定したパラメーター名 '{0}' は無効です。 - - - 複数の戻り値パラメーターはサポートされていません。 - - - パラメーター Size の値 '{0}' が無効です。値は 0 に等しいか、またはそれより大きくなければなりません。 - - - 引数 '{0}' は無効な値です。0 以上の値を指定してください。 - - - このメタデータの値は無効です。 - - - メタデータは SqlDbType.Row 型である必要があります。 - - - パラメーター値を {0} から {1} に変換できませんでした。 - - - {0} はパラレル トランザクションをサポートしません。 - - - この {0} は完了しています。再度使用することはできません。 - - - レコードが読み取り専用の場合は、'{0}' を呼び出すことはできません。 - - - DbDataRecord が読み取り専用の場合は、'{0}' を呼び出すことはできません。 - - - オブジェクトの型 '{0}' のデータ型 '{1}' への暗黙的な変換はサポートされていません。 - - - バッファー オフセット '{1}' と使用できるバイト数 '{0}' の合計が、バッファーに渡された長さを超えています。 - - - データの長さ '{0}' が 0 未満です。 - - - 指定された長さ '{0}' は有効範囲外です。 - - - dataIndex '{0}' での操作 {2} は無効です。CommandBehavior.SequentialAccess を使用する読み取りは、dataIndex '{1}' またはそれ以上のみ可能です - - - オフセットは、値内の場所を参照する必要があります。 - - - "ODBC マネージド プロバイダーでは、TABLE_NAME 制約の指定が必要です。GetSchema インデックス コレクションの場合は、この制約は Null でない必要があります。 - - - メソッド '{0}' の引数の値が無効です。 - - - ODBC プロバイダーが SQLGETTYPEINFO から結果を返しませんでした。 - - - 予期しない null DataTable 引数です。 - - - 予期しない null DataSet 引数です。 - - - 接続文字列が長さの最大許容値 {0} を超えています。 - - - {0} - 型をマップできません。 - - - 不明な SQL の種類です - {0}。 - - - 不明な URT の種類です - {0}。 - - - 無効な負の引数です! - - - プロパティは開いている接続には設定できません。 - - - System.Data.IsolationLevel 列挙値への SQL_TRANSACTION '{0}' の有効なマップがありません。 - - - {0} - 接続プールを有効にすることができません... - - - {0} - 環境ハンドルを割り当てることができません。 - - - {0} - 記述子ハンドルを取得できません。 - - - トランザクションにありません。 - - - OdbcType 列挙の value={0} は無効です。 - - - DBNull.Value データが渡される場合は、IsDBNull を使用してください。 - - - {0} [{1}] {2} - - - {0} - エラー情報が使用できません。 - - - 接続が閉じています。 - - - 内部接続の所有者がありません。 - - - .NET Framework Odbc データ プロバイダーには、Microsoft Data Access Components(MDAC) バージョン 2.6 以降が必要です。現在インストールされているバージョンは {0} です。 - - - .NET Framework OleDb データ プロバイダーには Microsoft Data Access Components(MDAC) バージョン 2.6 以降が必要です。現在インストールされているバージョンは {0} です。 - - - '{0}' インターフェイスは、'{1}' プロバイダーではサポートしていません。GetOleDbSchemaTable を現在のプロバイダーで使用することはできません。 - - - '{0}' は失敗しましたが、エラー メッセージはありません。結果コードは {1} です。 - - - エラー メッセージはありません。結果コードは {0} です。 - - - .NET Framework データ プロバイダーには Microsoft Data Access Components(MDAC) が必要です。Microsoft Data Access Components(MDAC) バージョン 2.6 以降をインストールしてください。 - - - .NET Framework Data Provider for OLEDB (Microsoft.Data.OleDb) では、Microsoft OLE DB Provider for ODBC Drivers (MSDASQL) がサポートされていません。.NET Framework Data Provider for ODBC (System.Data.Odbc) を使用してください。 - - - .NET Framework Data Provider for OLEDB では、非インタラクティブな環境にある場合、OLE DB プロバイダーがユーザーにメッセージを表示することを許可していません。 - - - '{0}' プロバイダーはローカルのコンピューターに登録されていません。 - - - ICommandText インターフェイスは '{0}' プロバイダーによってサポートされていません。代わりに、CommandType.TableDirect 使用してください。 - - - ITransactionLocal インターフェイスは、'{0}' プロバイダーではサポートしていません。ローカル トランザクションを現在のプロバイダーで使用することはできません。 - - - 初期化文字列の形式が OLE DB 仕様に適合しません。接続文字列の char[{0}] 付近で始まっています。 - - - 'Asynchronous Processing' は、.NET Framework Data OLE DB Provider(Microsoft.Data.OleDb) でサポートされている機能ではありません。 - - - OLE DB プロバイダーが ConnectionString で指定されませんでした。たとえば 'Provider=SQLOLEDB;' などの場合です。 - - - ConnectionString で指定された OLE DB プロバイダーの文字列が長すぎます。 - - - DbInfoKeywords OleDbSchemaGuid に制限が設定されているのは誤りです。 - - - OleDbSchemaGuid DbInfoLiterals に制限はありません。 - - - スキーマの guid、OleDbSchemaGuid に制限が設定されているのは誤りです。 - - - {0} OleDbSchemaGuid は '{1}' プロバイダーによってサポートされたスキーマではありません。。 - - - 構成設定 '{0}' に指定されている値の数が正しくありません。 - - - 構成設定 '{0}' で指定されている XML ファイルを読み込めません。 - - - コマンド引数 [{0}] '{1}' が無効です。 - - - 署名の不一致またはデータ オーバーフロー以外の理由により、コマンド引数 [{0}] '{1}' データ値を変換できませんでした。 - - - コマンド引数 [{0}] '{1}' の変換に失敗しました。データ値が signed であるのに対し、プロバイダーが使用する型は unsigned です。 - - - コマンド引数 [{0}] '{1}' の変換に失敗しました。データ値がプロバイダーで使われた型をオーバーフローしました。 - - - コマンド引数 [{0}] '{1}' 値を送出中にエラーが発生したため、プロバイダーは処理を中止しました。 - - - パラメーター [{0}] '{1}' に既定値がありません。 - - - パラメーター [{0}] で、次のエラーが発生しました。{1} - - - Microsoft.Data.OleDb.OleDbDataAdapter の内部エラー : パラメーター アクセサーが無効です: {0} {1}。 - - - Parameter [{0}]: OleDbType プロパティが初期化されていません。OleDbType.{1} - - - ICommandWithParameters インターフェイスは '{0}' によってサポートされていません。現在のプロバイダーはコマンド パラメーターをサポートしません。 - - - プロシージャ パラメーター情報の取得は '{0}' プロバイダーによってサポートされていません。 - - - サーバーの桁区切り記号を特定できません。integer 以外の数値リテラルは作成できません。 - - - オブジェクトは ADODB.RecordSet または ADODB.Record ではありません。 - - - ADODB.RecordSet オブジェクトから '{0}' インターフェイスを取得することができません。 + + '{1}[{0}]: Size プロパティのサイズが 0 です。これは無効です。 - - ADODB.Record オブジェクトから IRow インターフェイスを取得することができません。 + + {0}.Prepare メソッドは、パラメーターはすべて明示的に設定された型を持つことを要求します。 - - 型は OLE DB インターフェイス ISourcesRowset をサポートしていません。 + + {0}.Prepare メソッドは、可変長のパラメーターはすべて明示的に設定された non-zero Size を持つことを要求します。 - - プロバイダーが IDBInfo をサポートしていないため、ReservedWords スキーマ コレクションを作成できません。 + + {0}.Prepare メソッドは明示的に設定された Precision および Scale を持つ型 '{1}' のパラメーターが必要です。 - - プロパティの値は設定されませんでした。プロバイダーが '{0}' プロパティをサポートしていなかったか、Initialization プロパティ グループのメンバーではないプロパティの値をコンシューマーが取得または設定しようとしたときにデータ ソース オブジェクトが初期化されていませんでした。 + + asyncResult の end メソッド呼び出しが適切ではありません。{0} を呼び出す必要がありますが、{1} が呼び出されました。 - - 次のいずれかの理由により、'{0}' プロパティを初期化できませんでした。 - 値のデータ型が、プロパティのデータ型ではないか、Null ではありません。たとえば、プロパティが DBPROP_MEMORYUSAGE で、このプロパティのデータ型は Int32 でしたが、値のデータ型は Int64 でした。 - 値が無効な値です。たとえば、プロパティが DBPROP_MEMORYUSAGE でしたが、値が負の値でした。 - 値はプロパティの有効な値で、プロバイダーはプロパティを値の設定可能なプロパティとしてサポートしていますが、プロバイダーが指定された値をサポートしていません。これは、プロバイダーの作成後に OLE DB のこのプロパティに値が追加された場合などが挙げられます。 + + 操作が無効です。接続は閉じています。 - - Options の値が無効な値でした。 + + 接続上でローカル トランザクションが実行中のため、トランザクションに参加できません。ローカル トランザクションを終了して、再試行してください。 - - ColumnID の要素が無効な要素でした。 + + 接続には現在参加しているトランザクションがあります。現在のトランザクションを終了して、再試行してください。 - - '{0}' プロパティはすべての列に適用されるように指定されていましたが、1 つ以上の列に適用できませんでした。 + + データベースを Null、空の文字列またはスペースのみを含む文字列にすることはできません。 - - '{0}' プロパティが読み取り専用であるか、データ ソース オブジェクトが初期化された後に使用者側が Initialization プロパティ グループのプロパティの値を設定しようとしました。使用者は読み取り専用プロパティの値を現在の値に設定することはできます。この状態は、特定の列の値の設定が可能な列プロパティに値が設定できなかった場合も返されます。 + + ソース バッファー (サイズ {0}) オフセットが無効です : {1} - - 任意の '{0}' プロパティの値は指定された値に設定されませんでした。このプロパティに指定された値を設定することはできません。 + + ターゲット バッファー (サイズ {0}) オフセット : {1} が無効です。 - - 既存のプロパティと競合する可能性があったため、'{0}' プロパティの値は設定されていません。 + + ストリームが閉じているときに {0} を実行しようとしましたが無効です。 - - (予約済み)。 + + 指定した SeekOrigin の値は無効です。 - - プロバイダーから不明な DBPROPSTATUS_ の値 '{0}' が返されました。 + + 列番号 '{0}' から読み取ろうとしました。これは無効な処理です。CommandBehavior.SequentialAccess を使う場合、序数値 '{1}' 以上の列からのみ読み取ることができます。 - - アクセサーの妥当性の確認は延期され、メソッドがデータを返す間に実行されました。この列またはパラメーターの連結は無効です。 + + {0} のパラメーター データ型が無効です。 - - OleDbDataAdapter の内部エラー : 行セット アクセサーが無効です。Ordinal={0} Status={1} + + オブジェクトの型 {0} から既知のマネージ プロバイダーのネイティブ型へのマップがありません。 - - 符号の不一致またはデータ オーバーフロー以外の理由により、データ値を変換できませんでした。データ ストア内で破損しているデータが取得可能だった場合などが考えられます。 + + 型 {1} によって返された不明な TypeCode {0} を処理できません。 - - プロバイダーによって、{0} データを返すメモリを割り当てることができませんでした。 + + DbType {0} から周知の型 {1} へのマッピングは存在しません。 - - 変換に失敗しました。{0} データ値が、使用者側のバッファーの {0} 値に指定された型をオーバーフローしました。 + + 使用されている SQL Server のバージョンでは、データ型 '{0}' がサポートされていません。 - - OleDbDataAdapter の内部エラー : [get] 不明な OLE DB データ型です : 0x{0} ({1})。 + + パラメーター値 '{0}' が範囲外です。 - - 変換に失敗しました。{0} データ値が符号付きであるのに対し、コンシューマー バッファーの {0} 値には符号なしが指定されています。 + + 指定したパラメーター名 '{0}' は無効です。 - - OleDbDataAdapter の内部エラー : [set] 不明な OLE DB データ型です。0x{0} ({1}) + + パラメーター Size の値 '{0}' が無効です。値は 0 に等しいか、またはそれより大きくなければなりません。 - - プロバイダーで {0} 値を特定できませんでした。たとえば、行が作成されたときに {0} 列の既定値が使用できない状態で、使用者が新しい {0} 値を設定していなかった場合が考えられます。 + + 引数 '{0}' は無効な値です。0 以上の値を指定してください。 - - OLE DB プロバイダーが、予期しない {0} の状態値を返しました。 + + このメタデータの値は無効です。 - - スレッドの ApartmentState が ApartmentState.MTA 出ない場合、OleDbDataReader.Read を作成されたスレッドと同じスレッドから使用しなければなりません。 + + パラメーター値を {0} から {1} に変換できませんでした。 - - 予期しないエラーです : {0} + + {0} はパラレル トランザクションをサポートしません。 - - IErrorInfo.GetDescription が {0} で 失敗しました。 + + この {0} は完了しています。再度使用することはできません。 - - IErrorInfo.GetSource が {0} で 失敗しました。 + + 指定された長さ '{0}' は有効範囲外です。 - - DBTYPE_VECTOR データは、.NET Framework Data OLE DB Provider(Microsoft.Data.OleDb) によってサポートされていません。 + + dataIndex '{0}' での操作 {2} は無効です。CommandBehavior.SequentialAccess を使用する読み取りは、dataIndex '{1}' またはそれ以上のみ可能です プール サイズの最小値または最大値が無効です。最小プール サイズを最大プール サイズより大きい値に設定することはできません。 - - '{0}' は、古いキーワード です。代わりに '{1}' を使用してください。 - サーバーに対する分散トランザクション コーディネーターのアドレスをサーバーから取得することができません。サーバーで DTC が有効になっているかどうかを確認してください。 - - 接続パラメーター <{0}> の値の長さが使用できる最大値 65535 文字を超えています。 - 'Packet Size' が無効です。値は 512 以上 32768 以下の整数でなければなりません。 トランザクションが無効か、トランザクション内で保存するポイントの名前が無効です。 - - {0} 列挙値 {1} は SQL Server 7.0 または SQL Server 2000 ではサポートされていません。 - User Instance と Failover は、互換性のないオプションです。接続文字列でどちらか一方のみを選択してください。 @@ -2499,33 +540,21 @@ 接続文字列で 'Authentication=Active Directory Integrated' が指定されている場合は、Credential プロパティを設定できません。 - - 証明書認証で指定された認証パラメーターが正しくありません。 - 接続しようとした SQL Server インスタンスでは暗号化が必要とされていますが、このコンピューターは暗号化をサポートしていません。 接続しようとした SQL Server インスタンスは暗号化をサポートしていません。 - - 接続しようとした SQL Server インスタンスは CTAIP をサポートしていません。 - サポートされない SQL Server バージョンです。.NET Framework SqlClient データ プロバイダーは、SQL Server バージョン 7.0 以降でのみ使用できます。 非同期操作の実行中は、{0} を変更することはできません。 - - このコマンドには非同期接続が必要です。接続文字列に "Asynchronous Processing=true" を設定してください。 - 接続試行がタイムアウトしました。 - - タイムアウトに達しました。その結果、接続が切断されました。 - インスタンス エラーです。 @@ -2556,27 +585,18 @@ エラー コード 0x{0} - - ChangePassword を使用するには SQL Server 9.0 以降が必要です。 - 非ローカル ユーザーのインスタンスでは、SSE インスタンスのリダイレクトを行うことはできません。 認証 '{0}' はサポートされていません。 - - SQL 認証方法 '{0}' はサポートされていません。 - '{0}' の種類 '{1}' の認証プロバイダーをインスタンス化できませんでした。 種類 '{0}' の SqlAuthenticationInitializer をインスタンス化できませんでした。 - - 初期化中にプロバイダー '{0}' が例外をスローしました。 - プロバイダー '{0}' では認証 '{1}' はサポートされていません。 @@ -2586,24 +606,15 @@ '{0}' の認証プロバイダーが見つかりません。Active Directory (Entra ID) 認証方法を使用するには、'Microsoft.Data.SqlClient.Extensions.Azure' NuGet パッケージ (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) をインストールしてください。 - - 認証プロバイダーの構成セクションを読み取ることができませんでした。 - パラメーター '{0}' を null や空にすることはできません。 - - この非同期操作は既に完了しています。 - 既に実行中の非同期操作が保留されているため、コマンドの実行を行えません。 無効なコマンドが ExecuteXmlReader に送られました。このコマンドは Xml 結果を返さなければなりません。 - - 通知機能を使用するには SQL Server 9.0 以降が必要です。 - UdtTypeName では 3 パート名のフォーマットは無効です。 @@ -2697,9 +708,6 @@ SSPI パッケージを初期化できません。 - - Timeout に達しました。操作が完了する前にタイムアウト期間が過ぎたか、またはサーバーが応答していません。 - 接続がタイムアウトしました。ログイン前フェーズの開始時にタイムアウト期間が過ぎました。接続タイムアウトに指定されている時間が短すぎる可能性があります。 @@ -2745,12 +753,6 @@ 接続文字列でユーザー インスタンスが要求されましたが、指定されたサーバーではこのオプションをサポートしていません。 - - サーバー証明書がアプリケーションの検証に失敗しました。 - - - 指定されているデータ長 {0} が、許可されている最大長 {1} を超えています。 - データが存在しない場合、データの読み取りは無効です。 @@ -2775,9 +777,6 @@ データの長さ '{0}' が 0 未満です。 - - SqlDataRecord が読み取り専用の場合は、'{0}' を呼び出すことはできません。 - マップされているコレクションは使用中であるため、現在アクセスできません。 @@ -2847,21 +846,6 @@ Microsoft.Data.SqlClient を使用したコンテキスト接続への接続はサポートされていません。 - - TransactionScopes を入れ子にすることはできません。 - - - サーバーの予期しないイベント : {0}。 - - - '{0}' と '{1}' の長さは同じである必要があります。 - - - {1} に対し、SqlDbType '{0}' は無効です。サポートしているのは {2} のみです。 - - - 値が多すぎます。 - Stream は書き込みをサポートしていません。 @@ -2886,192 +870,36 @@ NULL - - メッセージ - - - 演算でオーバーフローしました。 - - - 0 除算エラーが発生しました。 - - - データが Null です。このメソッド、またはプロパティは Null 値で呼び出せません。 - - - 数値演算で切り捨てが起こりました。 - SqlDateTime のオーバーフローです。1/1/1753 12:00:00 AM から 12/31/9999 11:59:59 PM までの間でなければなりません。 - - 連結する 2 つの文字列の照合順序が異なります。 - - - 比較する 2 つの文字列の照合順序が異なります。 - - - フラグ値が無効です。 - - - SqlDecimal から Decimal への変換はオーバーフローします。 - 変換オーバーフローです。 - - SqlDateTime が無効です。 - - - タイム ゾーンが指定されました。SqlDateTime はタイム ゾーンをサポートしていません。 - 配列サイズが無効です。 数値の総桁数/小数点以下桁数が無効です。 - - 入力の形式が正しくありません。 - - - SqlType エラーが発生しました。 - - - このバージョンでは、SqlBytes および SqlChars は 2GB までしかサポートしていません。 - 読み取り中にエラーが発生しました。 - - Stream は閉じられているか後処理されています。 - - - バッファーがありません。読み取りまたは書き込み操作が失敗しました。 - - - 現在の値は Null であるため、0 以外の長さを設定することはできません。 - - - バッファーが十分にありません。読み取りまたは書き込み操作が失敗しました。 - - - 現在の値は Null であるため、0 以外のオフセットに書き込むことはできません。 - - - 現在のサイズを越えるオフセットからは書き込めません。このため、初期化されなかったデータがバッファーに残されます。 - 返されたデータのサイズが 2Gb を超えています。すべてのデータを取得するには、SequentialAccess command behavior を使用してください。 - - GetDayOfWeek の引数は 1 ~ 7 の整数である必要があります。 - - - SQL Type にはデータが読み込まれていません。 - - - SQL Type には既にデータが読み込まれています。 - - - 閉じられている XmlReader にアクセスしようとしましたが無効です。 - - - ストリームが閉じているときに {0} を呼び出そうとしましたが無効です。 - - - ストリームの書き込みができないときに {0} を呼び出そうとしましたが無効です。 - - - ストリームの読み取りができないときに {0} を呼び出そうとしましたが無効です。 - - - ストリームの検索ができないときに {0} を呼び出そうとしましたが無効です。 - - - サブクラスが、必要なメソッドをオーバーライドしませんでした。 - '{0}' のノーマライザーを作成できません。 内部エラー - - コマンド パラメーターには、Null でも空でもないコマンド テキストを使用する必要があります。 - - - MetaData パラメーター配列の長さは、ParameterDirection 配列引数の長さと同じである必要があります。 - - - データ アダプターのマッピング エラーが発生しました。 - - - データ アダプターのエラーが発生しました。 - - - DB の同時実行違反が発生しました。 - 操作は中止されました。 例外が発生したため、操作は中止されました。詳細については、InnerException を確認してください。 - - Fill が DataRow.AcceptChanges を呼び出すかどうかを示します。 - - - Update が DataRow.AcceptChanges を呼び出すかどうかを示します。 - - - Update イベント、RowUpdating および RowUpdated Status が UpdateStatus.ErrorsOccurred のとき、次の DataRow に進むかどうかを指示します。 - - - アダプターが DataReader から DataTable に値を読み込む方法を示します。 - - - TableMappings にテーブルまたは列が見つからない場合に実行する動作です。 - - - DataSet にテーブルまたは列が見つからない場合に実行する動作です。 - - - ソース テーブルを DataSet テーブルにどのように割り当てるかを指定します。 - - - Fill を実行中に修復可能なエラーが発生したときにイベントが発生します。 - - - Fill は、プロバイダー固有の値と共通の CLSCompliant の値のどちらかを返します。 - - - DataColumn.ColumnName - - - ソース列名 - 大文字と小文字を区別します。 - - - コレクション内の項目数です。 - - - 指定された DataColumnMapping オブジェクトです。 - - - このテーブル マップが一致するときのコラム別のマップです。 - - - DataTable.TableName - - - DataTableMapping ソース テーブル名です。この名前は、大文字小文字を区別します。 - - - コレクション内の項目数です。 - - - 指定された DataTableMapping オブジェクトです。 - DataSet の削除された行の更新中に使用されます。 @@ -3090,63 +918,9 @@ 更新中のすべての DataRowRow の後にイベントが発生します。 - - データ ソースに対して実行される前に同時にバッチ処理される行の数です。 - - - Select/Insert/Update/DeleteCommands に接続が指定されていない場合に、使用される接続です。 - - - DataTable 内の削除された行に対する Update の処理中に使用されます。 - - - DataTable 内の新しい行に対する Update の処理中に使用されます。 - - - Fill の処理中に使用されます。 - - - DataTable 内の変更された行に対する Update の処理中に使用されます。 - - - Fill は、プロバイダー固有の値と共通の CLSCompliant の値のどちらかを返します。 - - - ソース テーブルを DataTable にマップする方法を示します。 - - - ユーザーにより設定されていない場合に、Insert/Update/DeleteCommands が生成される方法を示します。 - - - データ ソースに対して実行される前に同時にバッチ処理される行の数です。 - - - Data Source への接続に使用される接続文字列です。 - - - Data Source への接続時に使用される ODBC ドライバーの名前を示します。 - - - Data Source への接続時に使用される DSN を示します。 - - - True の場合は、管理下にある接続プールが使用されることを示します。 - - - Data Source への接続時に使用される UDL ファイルを示します。 - - - この OleDb Provider で有効または無効にされる OLE DB Services を示します。 - - - Data Source への接続時に使用される OLE DB プロバイダーの名前を示します。 - アプリケーションの名前。 - - True の場合は、.NET Framework データ プロバイダーの非同期機能を使用できるようになります。 - 完全なパス名を含む、接続可能なデータベースのプライマリ ファイル名です。 @@ -3156,9 +930,6 @@ True の場合は、プールから削除されるときに接続の状態がリセットされることを示します。 - - True の場合は、接続元が Sql Server のコンテキストであることを示します。Sql Server のプロセスで実行する場合のみ使用できます。 - SQL Server Language レコード名です。 @@ -3252,54 +1023,9 @@ SQL Server での認証方法を指定します。 - - SQL Server で認証するために指定されたクライアント証明書。 - - - データ ソースへの接続に使われる情報です。 - - - ConnectionString 内では設定できない現在の接続タイムアウト値です。 - - - 現在のデータ ソース カタログ値は、接続文字列内の 'Database=X' です。 - - - 接続文字列の現在のデータ ソースは'Server=X' です。 - - - 現在の ODBC ドライバーです。 - - - ODBC ドライバーによってアクセスされる製品のバージョンです。 - - - データ ソースへの接続に使われる情報です。 - - - 現在の接続 timeout 値、ConnectionString 内の 'Connect Timeout=X' です。 - - - 現在のデータ ソース カタログ値、接続文字列の中の 'Initial Catalog=X' です。 - - - 接続文字列の中の現在のデータ ソース 'Data Source=X'。 - - - 接続文字列の中の現在の OLE DB プロバイダー ProgID 'Provider=X'。 - - - OLE DB プロバイダーによってアクセスされる製品のバージョンです。 - 認証に使用するアクセス トークンです。 - - 接続の状態 (同期であるか非同期であるか) です。接続文字列内では、'Asynchronous Processing=x' で表されます。 - - - レプリケーション時の接続に使われる情報です。 - 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI' などのように、DataSource への接続に使われる情報です。 @@ -3324,9 +1050,6 @@ この接続の統計情報を収集します。 - - カスタム列暗号化キー ストア プロバイダーです。 - 物理的な接続を表す GUID。 @@ -3363,99 +1086,9 @@ Microsoft SQL Server が使用する通知に自動的に組み込まれるデータです。 - - ユーザーによって指定されない場合に、Update および Delete コマンドの Where 句をどのように自動生成するかを示します。 - - - テキスト コマンド内の修飾されたテーブル名内のカタログ名の位置を示します。 - - - カタログ名とテキスト コマンド内の識別子の他の部分とを区切る文字です。 - - - スキーマ名とテキスト コマンド内の識別子の他の部分とを区切る文字です。 - - - sql オブジェクトを囲むプレフィックス文字列です。 - - - sql オブジェクトを囲むサフィックス文字列です。 - - - Command を自動的に生成する DataAdapter です。 - - - DataTable のスキーマまたは SelectCommand を使用します。 - - - ユーザーによって設定されていない場合に、Update コマンドの set 句が自動的に生成される方法を示します。 - - - OdbcCommands を自動的に生成する DataAdapter - - - 特殊文字を含む識別子を引用符で囲むために開始引用符としてテキスト コマンドで使用される文字です。 - - - 特別な文字を含む識別子を引用する際の閉じの引用符として、テキスト コマンドで使用される文字です。 - - - 自動的に OleDbCommands を生成する DataAdapter です。 - - - 数値リテラルで使用される桁区切り記号です。 - - - sql オブジェクトを囲むプレフィックス文字列です。 - - - sql オブジェクトを囲むサフィックス文字列です。 - SqlCommands を自動的に生成する DataAdapter です。 - - 数値リテラルで使用される桁区切り記号です。 - - - 特殊文字を含む識別子を引用符で囲むために開始引用符としてテキスト コマンドで使用される文字です。 - - - 特別な文字を含む識別子を引用する際の閉じの引用符として、テキスト コマンドで使用される文字です。 - - - Prepare、FillSchema、および CommandBuilder シナリオで使用するときは、10 進パラメーターと数値パラメーターのみを設定する必要があります。 - - - Prepare、FillSchema、および CommandBuilder シナリオで使用するときは、10 進パラメーターと数値パラメーターのみを設定する必要があります。 - - - ネイティブ型のパラメーターです。 - - - ネイティブ型のパラメーターです。 - - - たとえば '@p1' のような、パラメーターの名前です。 - - - ネイティブ型のパラメーターです。 - - - 型のサーバーの名前です。 - - - 可変長データ型のオフセットです。 - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - パラメーター '{1}' に指定された ParameterDirection '{0}' はサポートされていません。Table-valued parameters では、ParameterDirection.Input だけがサポートされています。 @@ -3468,9 +1101,6 @@ パラメーター '{0}' に TypeName が指定されました。TypeName は Structured パラメーターだけに設定してください。 - - SqlDbType.Structured 型は、複数値型だけでサポートされています。 - スキーマ テーブルのフィールド '{0}' に対する DateType 列が Null です。DataType は、null 以外でなければなりません。 @@ -3483,18 +1113,9 @@ レコード '{0}' のフィールド数が、元のレコードでの数と一致しません。 - - 範囲 : 0-8000 - SqlClient データ プロバイダーに予期しないエラーが発生しました。{0} - - {0},{1} の Type Info を取得できません。 - - - プロバイダーが次のアセンブリを読み込めませんでした。{0} - UDT パラメーターには、UdtTypeName プロパティを設定する必要があります。 @@ -3504,72 +1125,9 @@ 指定された型は、対象サーバー {0} に登録されていません。 - - UDT パラメーターは主キーの一部でない限り where 句では使用できません。 - - - '{0}' は、無効なユーザー定義型です。理由 : {1} - - - インメモリ形式とユーザー定義形式の両方をサポートしています - - - ネイティブ形式はサポートできません。 - - - IBinarySerialize を実装できません - - - シリアル化できません - - - パブリック コンストラクターがありません - - - INullable を実装できません - - - パブリック コンストラクターがありません - UDT 属性がありません - - マップなしのシリアル化は、現時点ではサポートされていません。 - - - 'public static x Parse(System.Data.SqlTypes.SqlString)' メソッドが見つかりません - - - 'public override string ToString()' メソッドが見つかりません - - - 'public static x Null { get; }' メソッドが見つかりません - - - ネイティブ形式では、'{0}' 型のフィールド (直接または別のフィールドから) をサポートしていません。 - - - 型がパブリック型ではありません - - - Native UDT は、'{0}' 型のためシーケンシャル レイアウトをサポートできません - - - Native UDT は最大サイズ (byte) を指定しています。 - - - フィールド '{0}' は、シリアル化されていないとマークされています。 - - - フィールド '{0}' の型は明示的にレイアウトするように設定されていますが、ネイティブ形式では許可されません - - - 複数の値をとるアセンブリ参照には、ゼロ以外のアセンブリ ID が必要です。 - - - 構造化された複数値型は、パラメーターだけで使用でき、他の型の内部で入れ子にすることはできません。 - パス名が無効です。 @@ -3585,9 +1143,6 @@ トランザクションを進めるときにエラーが発生しました。 - - クエリ通知の受信に使用される従属オブジェクトです。 - この依存関係が無効であるかどうかを示すプロパティです。 @@ -3600,9 +1155,6 @@ 既存の従属オブジェクトにコマンドを追加します。 - - Command が既に別の従属オブジェクトに関連付けられています。上書きできません。 - この SqlCommand オブジェクトは、既に別の SqlDependency オブジェクトに関連付けられています。 @@ -3630,12 +1182,6 @@ 指定された Timeout 値が無効です。タイムアウトには 0 より小さい数値は指定できません。 - - 通知エラー。種類={0}、情報={1}、ソース={2} - - - GetMetaData は、この SqlDbType には無効です。 - このコンストラクターに対し、dbType {0} は無効です。 @@ -4104,9 +1650,6 @@ 内部エラー。プレーンテキストの値を Null にすることはできません。 - - 暗号化できません。暗号化の結果、暗号化テキストが {0} バイトになりました。これは、最大許容サイズの {1} バイトを超えています。指定されたプレーンテキストの値が大きすぎる可能性があります (プレーンテキストのサイズは {2} バイトです)。 - 内部エラー。暗号化テキストの値を Null にすることはできません。 @@ -4224,9 +1767,6 @@ 構成証明情報が SQL Server によって返されませんでした。エンクレーブ型は '{0}' で、エンクレーブ構成証明 URL は '{1}' です。 - - セキュリティで保護されたチャネルを確立できませんでした。エラー メッセージ: {0} - 内部エラーです。型 '{1}' のオブジェクトを構築するときに null の引数 '{0}' が指定されました。'{0}' を null にすることはできません。 @@ -4242,15 +1782,6 @@ エンクレーブ構成証明サービスのエンドポイントを指定します。このエンドポイントは、Always Encrypted を使用して暗号化されたデータベース列に対する計算のために SQL Server インスタンス内で構成されたエンクレーブが有効であり、セキュリティで保護されているかどうかを確認するために使用されます。 - - エンクレーブ プロバイダーの構成セクションを読み取れませんでした。アプリケーション構成ファイルに含まれるそのセクションの形式が正しいことを確認してください。エラー メッセージ: {0} - - - 型 '{1}'、名前 '{0}' のエンクレーブ プロバイダーをインスタンス化できませんでした。エラー メッセージ: {2} - - - 内部エラーです。SqlColumnEncryptionEnclaveProviderName を null または空にすることはできません。 - '{0}' 結果セットの読み取り中にエラーが発生しました。接続文字列の中では構成証明の URL が指定されていませんが、クエリではエンクレーブ計算が必要です。エンクレーブの種類は '{1}' です。 @@ -4260,9 +1791,6 @@ 内部エラーです。エンクレーブ計算が必要なクエリを実行するときに SQL Server から受信したエンクレーブ型が null または空です。 - - クエリを実行するためにエンクレーブ計算が必要ですが、アプリケーション構成にエンクレーブ プロバイダー セクションがありません。 - エンクレーブの種類 '{0}' および構成証明プロトコル '{1}' に対して、エンクレーブ プロバイダーが見つかりませんでした。接続文字列に正しい構成証明プロトコルを指定してください。 @@ -4320,9 +1848,6 @@ 接続上のすべてのコマンドの既定の列暗号化設定です。 - - 機密性の高いデータをサーバーに送信する前にパラメーターを強制的に暗号化する。 - SQL Server 名を格納しているディクショナリ オブジェクトと信頼された列マスター キーのパス。 @@ -4332,9 +1857,6 @@ ApplicationIntent ReadOnly 接続オプションを使用して SQL Server のミラー化されたインスタンスに接続することはできません。 - - 予期しないルーティング情報を受信しました。 - 無効なルーティング情報を受信しました。 @@ -4485,9 +2007,6 @@ このコンテキストでサポートされていない認証が指定されています: {0} - - Active Directory 対話型認証がタイムアウトになりました。ユーザーが認証要求に応答するのに時間がかかりすぎました。 - Credential プロパティが設定されている場合は、'Authentication=Active Directory Interactive' を使用できません。 @@ -4497,9 +2016,6 @@ アクティブな接続のサーバー プロセス ID (SPID)。 - - Active Directory デバイス コード フロー認証がタイムアウトになりました。ユーザーが認証要求に応答するのに時間がかかりすぎました。 - 接続文字列で 'Authentication=Active Directory Device Code Flow' が指定されている場合は、Credential プロパティを設定できません。 @@ -4533,9 +2049,6 @@ 要求されたアクションに対しては、1 次元配列のみがサポートされます。 - - 指定された項目は、指定されたコレクションに見つからなかったため、削除できません。 - 負でない数値が必要です。 @@ -4563,87 +2076,36 @@ 接続文字列に 'Authentication=Active Directory Default' が指定されている場合、AccessTokenCallback プロパティを設定できません。 - - {0} が {1} を返しました。 - - - プロトコル エラー: 受信したメッセージは有効な署名を含んでいますが、有効な保護レベルで必要な暗号化がされていません。 - - - エラー {1} により {0} に失敗しました。 - このメソッドは、このクラスで実装されていません。 - - 要求されたセキュリティ パッケージは、サポートされていません。 - physicalConnection の種類が正しくありません。 AppContext スイッチ '{0}'={1} の設定中に例外が発生しました。 - - 型 {0} はこのプラットフォームでサポートされていません。 - グローバリゼーション インバリアント モードはサポートされていません。 - - Kerberos を使用して認証できません。クライアントで 'kinit' を使用して Kerberos が初期化されており、Kerberos 認証を許可するためのサービス プリンシパル名が SQL Server に登録されていることを確認してください。 - キーワード 'Network Library' はこのプラットフォームでサポートされていません。代わりに 'Data Source' の前に必要なプロトコルを付けてください (TCP 接続の場合は 'tcp:'、名前付きパイプ接続の場合は 'np:')。 ソケットは予期された '{0}' をスローしませんでした。エラー コード '{1}'。 - - サーバーは、このプラットフォームでサポートされていない機能を使用しようとしています。 - キーワード '{0}' はこのプラットフォームでサポートされていません。 現在読み込まれている System.Transactions.dll では、グローバル トランザクションはサポートされていません。 - - サーバーからデータを読み取り中に、サポートされていないトークン '{0}' を受信しました。 - - - '{0}' はサポートされているハンドル型ではありません。 - SqlFileStream はこのプラットフォームでサポートされていません。 LocalDB はこのプラットフォームでサポートされていません。 - - バッファー領域が不足しています。必須: {0}、実際: {1}。 - - - GSSAPI 操作が失敗しました。状態: {0} (マイナー状態: {1})。 - - - エラー - {0} ({1}) により、GSSAPI 操作が失敗しました。 - - - Windows 以外のオペレーティング システムでのチャネル バインドはサポートされていません。 - - - 既定の資格情報が渡される場合、ターゲット名を空にすることはできません。 - - - 要求された保護レベルは、現在インストールされている GSSAPI 実装ではサポートされていません。 - - - サーバーの実装はサポートされていません。 - - - このプラットフォームでは、既定の資格情報では NTLM 認証を実行できません。 - DataAdapter.Update が使用する場合、パラメーター値は DBNull.Value から (Int32)1 または (Int32)0 (非 Null の場合) に変更されます。 @@ -4668,9 +2130,6 @@ バッファーの最大サイズを超えました: {0} - - SourceColumn '{2}' の DataColumn '{0}' が見つかりません。 - SqlBatchCommand リストが初期化されていません。 diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx index 7f1832c415..a3a6bc51e4 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 오름차순 - Count={2}인 이 {1}에 대한 {0} 인덱스가 잘못되었습니다. @@ -132,18 +129,12 @@ {0}이(가) 이미 다른 {1}에 들어 있습니다. - - {0}이(가) 이미 해당 {1}에 들어 있습니다. - {0}은(는) Null이 아닌 {1} 형식 개체만 허용합니다. 이 {1}에 들어 있지 않은 {0}을(를) 제거하려 했습니다. - - {0}.{1}은(는) 고유해야 하는데 '{2}'이(가) 컬렉션에 이미 있습니다. - 연결이 닫혀 있지 않습니다. {0} @@ -171,12 +162,6 @@ 판독기가 닫혀 있으면 {0}을(를) 호출할 수 없습니다. - - 연결이 위임된 트랜잭션이나 확장된 트랜잭션에 대한 기본 연결이므로 트랜잭션에 참여할 수 없습니다. - - - 내림차순 - '{0}' 매개 변수에 대한 문자열은 비어 있지 않아야 합니다. @@ -204,9 +189,6 @@ ConnectionString 속성이 초기화되지 않았습니다. - - 비CLS Exception이 Catch되었습니다. - 지정된 보안 요소가 사용 권한 요소가 아닙니다. @@ -252,9 +234,6 @@ 데이터 - - StateChange - 업데이트 @@ -264,48 +243,12 @@ 연결 여부를 나타내는 ConnectionState입니다. - - 연결 상태가 변경될 때 이벤트가 트리거됩니다. - - - 매개 변수의 일반적인 형식입니다. - - - 입력, 출력 또는 양방향 매개 변수입니다. - - - 강력한 형식의 코드 생성에 사용되는 디자인 타임 속성입니다. - - - 가변 길이 데이터 형식의 오프셋입니다. - - - 매개 변수의 이름입니다. - - - 가변 길이 데이터 형식(문자열 및 배열)의 크기입니다. - - - DataAdapter.Update에서 사용하는 경우 ColumnMappings에서 DataSetColumn 이름을 찾는 데 사용되는 원본 열 이름입니다. 매개 변수와 데이터 행 간의 값을 복사하는 데 사용됩니다. - - - DataAdapter.Update(UpdateCommand 전용)에서 사용하는 경우 데이터 원본을 업데이트하는 데 사용되는 DataRow 값의 버전입니다. - - - DataAdapter.Update에서 사용하는 경우 매개 변수 값이 Null이 아니면 DBNull.Value에서 (Int32)1 또는 (Int32)0으로 변경됩니다. - - - 매개 변수 값입니다. - SQL 쿼리를 실행하지 못했으므로 '{0}' 컬렉션을 작성할 수 없습니다. 자세한 내용은 내부 예외를 참조하십시오. 요청한 스키마('{0}')가 지원하는 것보다 더 많은 제한이 지정되었습니다. - - '{2}'은(는) '{0}' 스키마 컬렉션의 '{1}' 제한에 올바른 값이 아닙니다. - 요청한 컬렉션({0})이 정의되지 않았습니다. @@ -315,15 +258,6 @@ 요청한 컬렉션({0})은 이 버전의 공급자에서 지원되지 않습니다. - - 필요한 DataSourceInformation 테이블 열 중 하나가 없습니다. - - - DataSourceInformation 테이블에는 행이 하나만 있어야 합니다. - - - 제한 컬렉션의 필수 열이 하나 이상 없습니다. - 제한 컬렉션에 일치하는 행이 없는 제한이 있습니다. @@ -342,42 +276,21 @@ 컬렉션 '{0}'이(가) 메타데이터 XML에 없습니다. - - 메타데이터 XML이 잘못되었습니다. - - - 메타데이터 XML이 잘못되었습니다. {0} 컬렉션에는 {1} 열이 있어야 하며 문자열 열이어야 합니다. - 메타데이터 XML이 잘못되었습니다. {0} 컬렉션의 {1} 열에는 비어 있지 않은 문자열이 있어야 합니다. - - 동작 - - - 동작 - 채우기 InfoMessage - - 매핑 - StatementCompleted - - UDT - 알림 - - 스키마 - XML @@ -393,9 +306,6 @@ 풀링 - - 명명된 ConnectionString - 보안 @@ -408,2073 +318,204 @@ 연결 복구 - - 사용자 지정 사용자 정보를 보유하는 컬렉션입니다. - - - DataSet 내의 문자열을 비교하는 경우 대/소문자를 구분하는지 여부를 나타냅니다. - - - 이 DataSet의 이름입니다. - - - DataSet에 포함된 데이터의 사용자 지정 "뷰"를 나타냅니다. 이 뷰를 사용하면 사용자 지정 데이터 뷰를 통해 필터링, 검색, 탐색 등을 수행할 수 있습니다. - - - 제약 조건 규칙이 적용되는지 여부를 나타냅니다. - - - DataSet에 오류가 있음을 나타냅니다. - - - DataSet 내의 문자열을 비교할 로케일을 나타냅니다. - - - 이 DataSet에서 가리키는 루트 요소의 XML URI 네임스페이스를 나타냅니다. - - - 이 DataSet에 사용되는 네임스페이스의 접두사를 나타냅니다. - - - 이 DataSet에 대한 관계를 보유하는 컬렉션입니다. - - - 이 DataSet에 대한 테이블을 보유하는 컬렉션입니다. - - - 이름이 같은 두 테이블의 스키마를 병합할 수 없는 경우 발생합니다. - - - Initialization이 완료된 후 발생합니다. - - - 데이터의 인-메모리 캐시를 나타냅니다. - - - 테이블 내의 문자열을 비교하는 경우 대/소문자를 구분하는지 여부를 나타냅니다. - - - 이 테이블에 대한 자식 관계를 반환합니다. - - - 이 테이블에 대한 열을 보유하는 컬렉션입니다. - - - 이 테이블에 대한 제약 조건을 보유하는 컬렉션입니다. - - - 이 테이블이 속해 있는 DataSet을 나타냅니다. - - - 테이블에 대한 기본 DataView입니다. - - - 이 행의 데이터 바인딩된 값을 계산하는 데 사용되는 식입니다. - - - 테이블에 오류가 있는지 여부를 반환합니다. - - - 테이블 내의 문자열을 비교할 로케일을 나타냅니다. - - - 이 테이블의 초기 시작 크기를 나타냅니다. - - - 이 테이블에 포함된 요소에 대한 XML URI 네임스페이스를 나타냅니다. - - - 이 테이블에 사용된 네임스페이스의 접두사를 XML 표현으로 나타냅니다. - - - 이 테이블에 대한 부모 관계를 반환합니다. - - - 이 테이블의 기본 키를 표현하는 열을 나타냅니다. - - - 이 테이블에 대한 데이터 행을 보유하는 컬렉션을 나타냅니다. - - - DataSet의 Tables 컬렉션에서 이 테이블을 찾는 데 사용되는 이름을 나타냅니다. - - - 테이블에서 행을 편집한 후에 발생합니다. - - - 이벤트 처리기에서 변경 내용을 수정하거나 취소할 수 있도록 해당 행을 변경 중일 때 발생합니다. 사용자는 행의 값을 수정할 수 있으며 예외를 Throw하여 편집을 취소해야 합니다. - - - 테이블에서 행을 삭제한 후에 발생합니다. - - - 테이블의 행을 삭제하도록 표시한 경우 발생합니다. 예외를 Throw하여 삭제를 취소할 수 있습니다. - - - 이 열의 값이 제출될 때 발생합니다. 사용자는 제안된 값을 수정할 수 있으며 편집을 취소하려면 예외를 throw해야 합니다. - - - 이 열의 값이 변경될 때 발생합니다. - - - 테이블에서 모든 행을 지우기 전에 발생합니다. - - - 테이블에서 모든 행을 지운 후에 발생합니다. - - - 새 DataRow가 인스턴스화된 후에 발생합니다. - - - DataSet의 Relations 컬렉션에서 이 관계를 찾는 데 사용되는 이름입니다. - - - 이 관계의 자식 열을 나타냅니다. - - - 이 관계의 부모 열을 나타냅니다. - - - 관계가 중첩되었는지 여부를 나타냅니다. - - - 삭제할 경우 이 관계에 수행할 관련 항목 적용(Cascade) 유형을 나타냅니다. - - - 업데이트할 경우 이 관계에 수행할 관련 항목 적용(Cascade) 유형을 나타냅니다. - - - 변경 내용을 적용 및 취소하는 경우 이 관계에 수행할 관련 항목 적용(CASCADE) 유형을 나타냅니다. - - - 이 제약 조건의 자식 열을 나타냅니다. - - - 이 제약 조건의 부모 열을 나타냅니다. - - - 이 제약 조건의 자식 테이블을 나타냅니다. - - - 이 제약 조건의 열을 나타냅니다. - - - 이 제약 조건이 기본 키인지 여부를 나타냅니다. - - - 이 제약 조건의 이름을 나타냅니다. - - - 이 제약 조건의 테이블을 나타냅니다. - - - 이 열에 null 값을 사용할 수 있는지 여부를 나타냅니다. - - - 테이블에 새 행이 추가되는 경우 해당 열이 자동으로 증가하는지 여부를 나타냅니다. 열 형식은 Int16, Int32 또는 Int64여야 합니다. - - - AutoIncrement 열의 시작 값을 나타냅니다. - - - AutoIncrement 열에서 사용하는 증분을 나타냅니다. - - - 이 열의 기본 사용자 인터페이스 캡션을 나타냅니다. - - - DataTable의 Columns 컬렉션에서 이 열을 찾는 데 사용되는 이름을 나타냅니다. - - - 이 열이 속해 있는 DataTable을 반환합니다. - - - 이 열에 저장된 데이터 형식을 나타냅니다. - - - 테이블에 새 행을 추가할 때 사용되는 기본 열 값을 나타냅니다. - - - 사용자가 직접 입력하는 대신 다른 열의 각 행에 대해 이 열에서 계산하는 값을 나타냅니다. - - - 이 열이 XML에서 특성, 요소, 단순 콘텐츠 노드로 지속되는지 또는 아무 것도 아닌지 나타냅니다. - - - 이 열에 저장된 요소 또는 특성에 대한 XML URI를 나타냅니다. - - - 이 DataColumn에 사용된 Prefix를 xml 표현으로 나타냅니다. - - - Columns 컬렉션에 있는 이 열의 인덱스를 나타냅니다. - - - 테이블에 행이 추가된 후 이 열에서 변경할 수 있는지 여부를 나타냅니다. - - - 이 열에서 테이블 행의 값을 고유한 값으로 제한할지 여부를 나타냅니다. - - - 이 열에 허용되는 값의 최대 길이를 나타냅니다. - - - 이 DataColumn의 DateTimeMode를 나타냅니다. - - - 이 DataView 및 관련된 사용자 인터페이스에서 삭제할 수 있는지 여부를 나타냅니다. - - - 이 DataView 및 관련된 사용자 인터페이스에서 편집할 수 있는지 여부를 나타냅니다. - - - 이 DataView 및 관련된 사용자 인터페이스에서 새 행을 추가할 수 있는지 여부를 나타냅니다. - - - 현재 이 뷰에 있는 항목 수를 반환합니다. - - - 이 DataSet이 있을 경우 이를 소유하는 DataViewManager에 포인터를 반환합니다. - - - 뷰가 열려 있는지 여부를 나타냅니다. - - - 이 DataView에서 반환된 데이터를 필터링하는 데 사용되는 식을 나타냅니다. - - - 이 DataView에서 반환된 데이터의 버전을 나타냅니다. - - - 이 DataView에서 반환하는 데이터의 순서와 열의 이름을 나타냅니다. - - - Sort 속성이 설정되어 있지 않은 경우 기본 정렬을 사용할지 여부를 나타냅니다. - - - 이 DataView에서 데이터를 가져오기 위해 사용하는 테이블을 나타냅니다. - - - 이 DataView에서 반환된 데이터가 변경되었음을 나타냅니다. - - - 이 DataViewManager의 데이터 원본을 나타냅니다. - - - 해당하는 DataSet의 테이블에 대한 정렬/필터링/상태 설정을 나타냅니다. - - - DataSet은 simpleType으로 'union' 또는 'list'를 지원하지 않습니다. - - - 잘못된 {0} 구문: 필요한 '{1}' 특성이 없습니다. - - - '{1}' 값은 '{0}' 특성에 사용할 수 없습니다. - - - '{0}' 특성의 값은 '{1}' 또는 '{2}'(으)로 설정해야 합니다. - - - ElementType name='{0}'을(를) 찾을 수 없습니다. - - - '{0}' 관계에 부모 테이블 이름이 없습니다. - - - '{0}' 관계에 자식 테이블 이름이 없습니다. - - - '{0}' 관계에 부모 테이블 키가 없습니다. - - - '{0}' 관계에 자식 테이블 키가 없습니다. - - - 정의되지 않은 데이터 형식입니다. '{0}' - - - 데이터 형식이 정의되지 않았습니다. - - - 필드 노드 내의 XPath 선택이 잘못되었습니다. {0}을(를) 찾을 수 없습니다. - - - 선택기 노드 내부에 잘못된 XPath 선택이 있습니다. {0}. - - - 이름이 {0}인 제약 조건 내의 'Key' 노드가 잘못되었습니다. - - - 제약 조건 이름 {0}이(가) 스키마에서 이미 사용 중입니다. - - - '{0}'을(를) 유형 '{1}'(으)로 변환할 수 없습니다. - - - '{2}'(이)라는 '{1}' 제약 조건에 '{0}' 부분이 없습니다. - - - 잘못된 Relation 정의: 키 길이가 다릅니다. - - - DataSet에서는 이름이 '{0}'인 ComplexType의 순환 참조를 허용하지 않습니다 . - - - DataSet은 노드 {0}에 대해 추상 ComplexType을 인스턴스화할 수 없습니다. - - - Xml Schema를 작성하는 동안 여러 대상 변환기에서 오류가 발생했습니다. 자세한 내용은 내부 예외를 참조하십시오. - - - Xml Schema를 작성하는 동안 여러 대상 변환기에서 오류가 발생했습니다. null 또는 빈 문자열이 반환되었습니다. - - - '{0}' 선언이 중복되었습니다. - - - diffGram을 로드할 수 없습니다. 대상 데이터 세트에 테이블 '{0}'이(가) 없습니다. - - - diffGram을 로드할 수 없습니다. 'sql' 노드가 없습니다. - - - 열 이름 '{0}'이(가) 여러 매핑 형식에 대해 정의되어 있습니다. - - - 접두사 '{0}'은(는) 유효하지 않습니다. 접두사에 특수 문자가 들어 있기 때문입니다. - - - 자기 중첩 테이블 '{0}'의 순환 참조입니다. - - - DataSet의 엔터티를 확장할 수 없습니다. XmlValidatingReader를 사용하여 EntityHandling 속성을 적절하게 설정하십시오. - - - 유형 '{0}'은(는) IXmlSerializable 인터페이스를 구현하지 않으므로 serialization을 계속할 수 없습니다. - - - 역직렬화를 계속할 수 없습니다. 데이터가 IXMLSerializable을 구현하지 않으므로 다형성이 지원되지 않습니다. - - - DataTable은 Xml에서 스키마 유추를 지원하지 않습니다. - - - DataTable '{0}' serialization을 계속할 수 없습니다. 같은 외래 키에 여러 개의 부모 행이 있는 DataRow가 포함되어 있습니다. - - - IsDataSet 특성이 입력 Schema에 없습니다. - - - DataSet 요소를 확인할 수 없습니다. IsDataSet 속성이 두 개 이상 있습니다. - - - 데이터 세트은 IDynamicMetaObjectProvider를 구현하면서 IXmlSerializable은 구현하지 않는 형식을 직렬화하지 않습니다. - - - 기능이 구현되지 않았습니다. {0} - - - 구문 오류: '{0}' 연산자 뒤에 피연산자가 없습니다. - - - '{0}' 식의 형식이 일치하지 않습니다. - - - 식이 너무 복잡합니다. - - - 열 [{0}]을(를) 찾을 수 없습니다. - - - 식에 잘못된 문자열 상수가 있습니다. {0} - - - 식에 정의되지 않은 함수 호출 {0}()이(가) 있습니다. - - - 식에 구문 오류가 있습니다. - - - 인수 개수가 잘못되었습니다. 함수 {0}() - - - 식에 닫는 괄호가 없습니다. - - - {1} 위치의 '{0}' 토큰을 해석할 수 없습니다. - - - {0}이(가) 필요한데 {2} 위치의 실제 토큰은 {1}입니다. - - - {0}에서 {1}(으)로 변환할 수 없습니다. - - - '{0}' 값을 '{1}' 형식으로 변환할 수 없습니다. - - - 열 이름[{0}]이 잘못되었습니다. - - - 식에 잘못된 날짜 상수 '{0}'이(가) 있습니다. - - - IN 연산자의 식 목록에는 상수 식만 사용할 수 있습니다. - - - Like 연산자에 오류가 있습니다. 문자열 패턴 '{0}'이(가) 잘못되었습니다. - - - 구문 오류: IN 키워드 다음에 오는 항목은 쉼표로 구분해야 하고 괄호로 묶어야 합니다. - - - 함수 인수의 형식이 일치하지 않습니다. 인수 {1}의 {0}()에는 {2}이(가) 필요합니다. - - - 함수 인수의 형식이 일치하지 않습니다. 인수 {1}의 {0}()에는 정수 형식이 필요합니다. - - - {1} 및 {2}에서 '{0}' 작업을 수행할 수 없습니다. - - - '{0}' 연산자는 '{1}' 및 '{2}' 형식의 피연산자에서 모호합니다. 부호 있는 형식과 부호 없는 형식을 함께 사용할 수 없습니다. 명시적 Convert() 함수를 사용하십시오. - - - 구문 오류: IN 키워드 다음에는 쉼표로 구분된 비어 있지 않은 식 목록이 와야 하고 이를 괄호로 묶어야 합니다. - - - 식에 지원되지 않는 연산자 '{0}'이(가) 있습니다. - - - 식에 잘못된 이름이 있습니다. '{0}' - - - 구문 오류: '{0}' 연산자 앞에 피연산자가 없습니다. - - - 식에 닫는 괄호가 너무 많이 있습니다. - - - 테이블[{0}]이 둘 이상의 관계에 관련되어 있습니다. '{1}' 식의 관계 이름을 명시적으로 기술해야 합니다. - - - aggregate 인수에 구문 오류가 있습니다. 'Child' 한정자를 사용하는 단일 열 인수가 필요합니다. - - - 집계식 '{0}'에 바인딩되지 않은 참조가 있습니다. - - - 현재 행이 없는 비상수 식을 계산할 수 없습니다. - - - '{0}' 식에 바인딩되지 않은 참조가 있습니다. - - - 계산할 수 없습니다. '{0}' 식은 집계식이 아닙니다. - - - '{0}' 필터 식은 부울 형식으로 계산되지 않습니다. - - - 형식 이름 '{0}'이(가) 잘못되었습니다. - - - 조회식에 구문 오류가 있습니다. 'Parent' 키워드 다음에는 관계 한정자를 사용하는 단일 열 인수가 필요합니다. Parent[(<relation_name>)].<column_name>. - - - DataFilter를 호출할 행 또는 테이블이 필요합니다. - - - {0}() 인수가 범위를 벗어났습니다. - - - 구문 오류: 'Is' 연산자 사용이 잘못되었습니다. 올바른 구문: <expression> Is [Not] Null + + 오프셋 및 길이가 배열의 범위를 벗어났거나 카운트가 인덱스부터 소스 컬렉션 끝까지의 요소 수보다 큽니다. - - 값이 '{0}' 형식에 비해 너무 크거나 작습니다. + + '{0}' 유형의 개체를 '{1}' 유형의 개체로 변환할 수 없습니다. - - 0으로 나눔 오류가 발생했습니다. + + 속성 '{0}'에 대해 허용되는 유일한 값은 '{1}'입니다. - - 부모 관계 '{0}'을(를) 찾을 수 없습니다. + + 속성 '{0}'에 허용되는 값은 '{1}' 또는 '{2}'입니다. - - 'hours' 인수가 범위를 벗어났습니다. -14에서 +14 사이의 값이어야 합니다. + + 지정한 QuotePrefix 및 QuoteSuffix 값이 일치하지 않습니다. - - 'minutes' 인수가 범위를 벗어났습니다. -59에서 +59 사이의 값이어야 합니다. + + 인수 '{0}'의 길이가 한계인 '{1}'을(를) 초과합니다. - - 제공된 시간 범위가 총 14시간을 초과합니다. + + 키 '{0}'에 대한 값이 잘못되었습니다. - - 제공된 DateTime 인수의 Kind 속성이 'hours' 및 'minutes' 인수와 일치하지 않습니다. + + 키 '{0}'을(를) 사용하려면 키 '{1}'이(가) 있어야 합니다. - - 형식이 '{0}'인 DataColumn은 식을 지원하지 않습니다. + + 키 '{0}'의 값 길이가 제한인 ‘{1}’을(를) 초과합니다. - - 제약 조건을 활성화하지 못했습니다. 하나 이상의 행에 NOT NULL, UNIQUE 또는 외래 키 제약 조건을 위반하는 값이 있습니다. + + 지원되지 않는 키워드입니다: '{0}'. - - 컬렉션 자체를 수정할 수 없습니다. + + 내부 .NET Framework Data Provider 오류 {0}입니다. - - 주어진 이름 '{0}'이(가) 대/소문자가 다른 컬렉션 개체의 이름과 적어도 두 개 이상 일치하지만 대/소문자가 일치하는 이름은 없습니다. + + {0} "{1}". - - 지정된 이름 '{0}'이(가) 네임스페이스가 다른 컬렉션 개체에 있는 두 개 이상의 이름과 일치합니다. + + {0} "{1}", 따옴표 사용이 잘못되었습니다. - - 오프셋 및 길이가 배열의 범위를 벗어났거나 카운트가 인덱스부터 소스 컬렉션 끝까지의 요소 수보다 큽니다. + + {0} "{1}", 현재 한도 "{2}"이(가) 부족합니다. - - '{0}' 인수가 범위를 벗어났습니다. + + SqlBulkCopy.DestinationTableName이 잘못된 multipart 이름이어서 SqlBulkCopy.WriteToServer에 실패했습니다. - - '{0}' 인수는 Null일 수 없습니다. + + multipart 이름이 잘못되어 SQL Server 결과를 처리하지 못했습니다. - - '{0}' 인수에 Null 값이 있습니다. + + SqlParameter.UdtTypeName은 잘못된 multipart 이름입니다. - - {0} 열을 찾을 수 없습니다. + + SqlParameter.TypeName은 잘못된 multipart 이름입니다. - - '{0}' 열은 이미 현재 DataTable에 속해 있습니다. + + SqlCommand.CommandText 속성 값이 잘못된 multipart 이름이어서 SqlCommand.DeriveParameters에 실패했습니다. - - '{0}' 열은 이미 다른 DataTable에 속해 있습니다. + + MultiSubnetFailover 연결 옵션을 사용하여 미러된 SQL Server 인스턴스에 연결이 지원되지 않습니다. - - DataTable에 SimpleContent 열이 둘 이상 포함될 수 없습니다. + + {0} 열거형 값 {1}은(는) {2} 메서드에서 지원되지 않습니다. - - 요소 열 또는 중첩 관계를 포함하는 테이블에 SimpleContent 열을 추가할 수 없습니다. + + {0} 열거형 값 {1}은(는) .NET Framework SqlClient Data Provider에서 지원되지 않습니다. - - 이름이 '{0}'인 열이 이 DataTable에 이미 속해 있습니다. + + {0}: CommandText 속성이 초기화되지 않았습니다. - - 이름이 '{0}'인 열을 추가할 수 없습니다. 같은 이름의 중첩 테이블이 이미 이 DataTable에 속해 있습니다. + + {0}: Connection 속성이 초기화되지 않았습니다. - - 이름이 '{0}'인 열이 이미 이 DataTable에 속해 있습니다. 중첩 테이블 이름을 같은 이름으로 설정할 수 없습니다. + + {0}을(를) 사용하려면 Connection이 열려 있고 사용 가능해야 합니다. {1} - - 이 테이블에 속하지 않는 열은 제거할 수 없습니다. + + '{0}' 저장 프로시저가 없습니다. - - 이 열은 기본 키의 일부이므로 제거할 수 없습니다. + + 트랜잭션이 현재 연결과 연결되어 있지 않거나 완료되었습니다. - - 이 열은 {0} 관계의 부모 키 일부이므로 제거할 수 없습니다. + + 현재 연결에 연결된 트랜잭션은 완료되었지만 삭제되지 않았습니다. 이 연결이 SQL 문을 실행하는 데 사용될 수 있으려면 먼저 트랜잭션이 삭제되어야 합니다. - - 이 열은 {1} 테이블에서 {0} 제약 조건의 일부이므로 제거할 수 없습니다. + + 명령에 할당된 연결이 보류 중인 로컬 트랜잭션에 연결되어 있는 경우 {0}을(를) 사용하려면 트랜잭션이 있어야 합니다. 명령의 Transaction 속성이 초기화되지 않았습니다. - - 이 열은 식의 일부이므로 제거할 수 없습니다. {0} = {1}. + + 이 {0}과(와) 연결된 DataReader가 이미 열려 있습니다. 먼저 닫아야 합니다. - - 계산 열에 AutoIncrement 속성을 설정할 수 없습니다. + + {0} DeriveParameters는 CommandType.StoredProcedure만 지원하며 CommandType.{1}은(는) 지원하지 않습니다. - - DefaultValue가 설정된 열에 AutoIncrement 속성을 설정할 수 없습니다. - - - AutoIncrement 열에 DefaultValue를 설정할 수 없습니다. - - - AutoIncrementStep 값은 0이 아니어야 합니다. - - - DataTable의 일부인 경우 ColumnName이 필요합니다. - - - 열에 데이터가 있으면 해당 열의 DataType을 변경할 수 없습니다. - - - 열에는 올바른 DataType이 필요합니다. - - - {0} 열의 DefaultValue 형식은 {1}이며 {2}(으)로 변환할 수 없습니다. - - - 열의 DefaultValue 형식은 {0}이며 {1}(으)로 변환할 수 없습니다. - - - {0} 열의 DefaultValue 형식은 {1}이지만 열 형식은 {2}입니다. - - - Expression 열의 ReadOnly 속성을 변경할 수 없습니다. - - - Expression 열의 Unique 속성을 변경할 수 없습니다. - - - AutoIncrement 또는 Unique가 지정된 열에 식을 만들 수 없습니다. - - - 열을 ReadOnly로 지정할 수 없으므로 식을 설정할 수 없습니다. - - - {0} 열은 제약 조건의 일부이므로 이 열에 Expression 속성을 설정할 수 없습니다. - - - Expression 열 {0}을(를) 기반으로 제약 조건을 만들 수 없습니다. - - - 식에 순환 참조가 있으므로 Expression 속성을 설정할 수 없습니다. - - - '{0}' 열에 null 값이 있습니다. - - - '{0}' 열에 nulls를 사용할 수 없습니다. - - - '{0}' 열은 읽기 전용입니다. - - - 열 '{0}'에 고유하지 않은 값이 있습니다. - - - '{0}' 열은 {1} 테이블에 속하지 않습니다. - - - 열은 테이블에 속해야 합니다. - - - <{0}>을(를) {1} 열에 저장할 수 없습니다. {2} 형식이 필요합니다. - - - Column '{0}'을(를) null로 설정할 수 없습니다. 대신 DBNull을 사용하십시오. - - - 열 '{0}'을(를) 설정할 수 없습니다. 값이 이 열의 MaxLength 제한을 위반합니다. - - - MaxLength는 문자열 데이터 형식에만 적용됩니다. Column '{0}'의 MaxLength 속성을 음수가 아닌 숫자로 설정할 수 없습니다. - - - Column '{0}'의 MaxLength 속성을 '{1}'(으)로 설정할 수 없습니다. 테이블에 새 제한값보다 긴 문자열이 최소한 하나 이상 있습니다. - - - Column '{0}'의 MaxLength 속성을 설정할 수 없습니다. 해당 열이 SimpleContent입니다. - - - Column '{0}'의 DataType 속성을 {1}(으)로 설정할 수 없습니다. 해당 열이 SimpleContent입니다. - - - Column '{0}'의 MappingType 속성을 SimpleContent로 설정할 수 없습니다. 해당 열의 DataType은 {1}입니다. - - - Column '{0}'이(가) MaxLength 제한값을 초과합니다. - - - 열 '{0}'에는 DBNull.Value를 사용할 수 없습니다. - - - Column '{0}'의 Namespace 속성을 변경할 수 없습니다. 해당 열이 SimpleContent입니다. - - - DataColumn에 데이터가 있으면 유형이 '{0}'인 DataColumn의 AutoIncrement를 변경할 수 없습니다. - - - 열 '{0}'은(는) 기본 테이블 '{1}'에 속하지 않습니다. - - - 형식이 '{0}'인 DataColumn은 complexType입니다. 복합 형식의 값을 Attribute로 serialize할 수 없습니다. - - - DateTimeMode는 DateTime 유형의 DataColumns에만 설정할 수 있습니다. - - - '{0}'은(는) 잘못된 DataSetDateTime 값입니다. - - - 테이블에 데이터가 있으면 DateTimeMode를 '{0}'에서 '{1}'(으)로 변경할 수 없습니다. - - - Type '{0}'에 static Null 속성 또는 필드가 없습니다. - - - 유형 '{0}'이(가) IRevertibleChangeTracking을 구현하지 않으므로 RejectChanges()를 계속할 수 없습니다. - - - SetAdded 및 SetModified는 DataRowState가 Unchanged인 DataRows에서만 호출할 수 있습니다. - - - 서수 '{0}'이(가) 최대 수를 초과합니다. - - - DataSet에서 System.Nullable<>을 지원하지 않습니다. - - - ConstraintCollection에 포함되어 있는 경우 해당 제약 조건의 이름을 빈 문자열로 변경할 수 없습니다. - - - {0} 제약 조건에 제약 조건을 적용할 수 없습니다. - - - '{0}' 열은 고유 값을 가지도록 제약되어 있습니다. 값 '{1}'은(는) 이미 있습니다. - - - 제약 조건 '{0}'이(가) 이 DataTable에 속하지 않습니다. - - - {0} 제약 조건을 찾을 수 없습니다. - - - 이름이 {0}인 제약 조건과 일치하는 제약 조건이 컬렉션에 이미 있습니다. - - - 이름이 '{0}'인 Constraint가 이 DataTable에 이미 속해 있습니다. - - - 이 열에는 현재 고유 값이 없습니다. - - - 해당 열이 이 테이블을 가리키지 않습니다. - - - 모든 값이 각각 대응하는 부모 값을 갖지 않으므로 이 제약 조건을 사용할 수 없습니다. - - - ForeignKey가 {0} 테이블에 속하지 않으므로 이 제약 조건을 추가할 수 없습니다. - - - 이 테이블에 속하지 않는 제약 조건은 제거할 수 없습니다. - - - UNIQUE 제약 조건 '{0}'을(를) 제거할 수 없습니다. 먼저 외래 키 제약 조건 '{1}'을(를) 제거하십시오. - - - {0} 관계에 제약 조건이 적용되어 있으므로 이 행을 삭제할 수 없습니다. 삭제하면 자식 행이 고립됩니다. - - - {0} 관계에 제약 조건이 적용되어 있으므로 변경할 수 없습니다. 이 값을 변경하면 자식 행이 고립됩니다. - - - ForeignKeyConstraint {1}(으)로 제약 조건을 적용하고 {2}에 자식 행이 있으므로 {0} 테이블을 지울 수 없습니다. - - - ForeignKeyConstraint {0}을(를) 사용하려면 자식 키 값({1})이 부모 테이블에 있어야 합니다. - - - '{0}' 때문에 속성에 액세스할 수 없습니다. - - - 이 행은 자식 행을 포함하고 {0} 관계에 제약 조건이 적용되므로 제거할 수 없습니다. - - - 이 테이블에 기본 키가 이미 설정되어 있으므로 기본 키 제약 조건을 추가할 수 없습니다. - - - 두 개의 중첩 관계에서 자식 테이블인 DataTable '{0}'에 제약 조건을 추가할 수 없습니다. - - - 다른 테이블에 속해 있는 열에서 키를 만들 수 없습니다. - - - 0개의 열을 가질 수 없습니다. - - - {0}개가 넘는 열을 가질 수 없습니다. - - - 동일한 열이 두 번 이상 나열된 경우 키를 만들 수 없습니다. '{0}' - - - UNIQUE 제약 조건은 테이블의 기본 키이므로 제거할 수 없습니다. - - - UNIQUE 제약 조건은 {0} 테이블의 기본 키이므로 제거할 수 없습니다. - - - 부모 열과 자식 열에 형식이 일치하는 열이 없습니다. - - - ParentKey와 ChildKey가 동일합니다. - - - ParentColumns와 ChildColumns의 길이는 동일해야 합니다. - - - ParentColumns과 ChildColumns의 길이는 0이 아니어야 합니다. - - - 행이 이 관계와 동일한 DataSet에 속하지 않습니다. - - - DataSet의 일부인 경우 RelationName이 필요합니다. - - - GetChildRows에는 Table이 {0}인 행이 필요한데 Table이 {1}인 행을 지정했습니다. - - - 이 DataSet에 관계를 추가하려면 해당 DataSet의 두 테이블을 연결해야 합니다. - - - GetParentRow에는 테이블이 {0}인 행이 필요한데 테이블이 {1}인 행을 지정했습니다. - - - SetParentRow에는 테이블이 {0}인 자식 행이 필요하지만 지정된 행의 테이블은 {1}입니다. - - - 서로 다른 DataSet의 테이블 간에 관계를 가질 수 없습니다. - - - 서로 다른 DataSets의 테이블 간에 관계를 만들 수 없습니다. - - - 해당 자식 열에 대한 관계가 이미 있습니다. - - - 이 관계는 현재 관계 컬렉션에 속하지 않습니다. - - - 이 관계는 이미 다른 DataSet에 속해 있습니다. - - - 이 관계는 이미 현재 DataSet에 속해 있습니다. - - - 이름이 '{0}'인 Relation이 이 DataSet에 이미 속해 있습니다. - - - Relation {0}이(가) 이 DataSet에 속하지 않습니다. - - - {0} 관계를 찾을 수 없습니다. - - - null 테이블에 컬렉션을 만들 수 없습니다. - - - 이 컬렉션이 관계를 표시하는 테이블은 DataSet에서 제거되었습니다. - - - 이 테이블이 자식 테이블이 아닌 경우 테이블의 ParentRelation 컬렉션에 관계를 추가할 수 없습니다. - - - 이 테이블이 부모 테이블이 아닌 경우 테이블의 ChildRelation 컬렉션에 관계를 추가할 수 없습니다. - - - 이 관계에 대해 'Nested' 속성을 false로 설정할 수 없습니다. - - - 동일한 테이블 '{0}'은(는) 두 개의 중첩 관계에서 자식 테이블이 될 수 없습니다. - - - 테이블({0})은 중첩 관계에서 자신의 자식 테이블이 될 수 없습니다. - - - 부모 테이블과 자식 테이블 간에 Locale 또는 CaseSensitive 설정이 다른 DataRelation 또는 Constraint를 추가할 수 없습니다. - - - Parent 또는 Child 열이 DataSet에 없는 경우 DataRelation을 만들 수 없습니다. - - - 네임스페이스를 상속하는 중첩 테이블 '{0}'에는 네임스페이스가 다른 여러 부모 테이블을 사용할 수 없습니다. - - - 빈 네임스페이스가 있는 중첩 테이블 '{0}'에는 네임스페이스가 다른 여러 개의 부모 테이블을 사용할 수 없습니다. - - - 행이 이 관계와 동일한 DataSet에 속하지 않습니다. - - - 테이블에 없는 행에서는 이 작업을 수행할 수 없습니다. - - - 이 관계 및 자식 행이 동일한 DataSet에 속하지 않습니다. - - - RowChanging 이벤트에서 제안한 값을 변경할 수 없습니다. - - - OnRowChanging 이벤트 내에서 EndEdit()을 호출할 수 없습니다. - - - RowChanging 이벤트 내에서 BeginEdit()을 호출할 수 없습니다. - - - OnRowChanging 이벤트 내에서 CancelEdit()을 호출할 수 없습니다. exception을 Throw하여 이 업데이트를 취소하십시오. - - - OnRowDeleting 이벤트 내에서 Delete를 호출할 수 없습니다. exception을 Throw하여 삭제를 취소하십시오. - - - 입력 배열이 이 테이블의 열 개수보다 깁니다. - - - 액세스할 Current 데이터가 없습니다. - - - 액세스할 Original 데이터가 없습니다. - - - 액세스할 Proposed 데이터가 없습니다. - - - 이 행은 테이블에서 제거되었으며 어떤 데이터도 포함하고 있지 않습니다. BeginEdit()을 사용하여 이 행에 새 데이터를 만들 수 있습니다. - - - 해당 행을 통해 삭제된 행 정보에 액세스할 수 없습니다. - - - 버전은 Original, Current 또는 Proposed여야 합니다. - - - 위치 {0}에 행이 없습니다. - - - 행 삽입 위치 {0}이(가) 잘못되었습니다. - - - 이름이 '{1}'인 테이블에 rowOrder 값={0}이(가) 두 번 사용되었습니다. - - - '{0}' 테이블의 rowOrder 시퀀스에 값이 없습니다. - - - 지정된 DataRow가 현재 DataRowCollection에 없습니다. - - - 이 행은 이미 다른 테이블에 속해 있습니다. - - - 이 행은 이미 현재 테이블에 속해 있습니다. - - - 이 행은 이미 삭제되었으므로 다시 삭제할 수 없습니다. - - - 이 행이 비어 있습니다. - - - 이미 제거된 행이므로 다시 제거할 수 없습니다. - - - 자식 행에 부모가 여러 개 있습니다. - - - 인식할 수 없는 행 상태 비트 패턴입니다. - - - DataSet 이름을 빈 문자열로 변경할 수 없습니다. - - - 이름 '{0}'이(가) 잘못되었습니다. DataSet 이름에는 DataTable과 같은 이름을 사용할 수 없습니다. - - - 스키마 네임스페이스가 잘못되었습니다. {0}을(를) 대신 사용하십시오. - - - CaseSensitive 또는 Locale 속성을 변경할 수 없습니다. 이 속성을 변경하면 최소한 하나의 DataRelation 또는 Constraint가 관련 테이블 간에 서로 다른 Locale 또는 CaseSensitive 설정을 포함하게 됩니다. - - - SchemaSerializationMode 속성은 파생 DataSet에서 재정의한 경우에만 설정할 수 있습니다. - - - PrimaryKey 열은 이 테이블에 속하지 않습니다. - - - SimpleContent 열을 포함하는 테이블에 중첩 관계 또는 요소 열을 추가할 수 없습니다. - - - DataSet의 일부인 경우 TableName이 필요합니다. - - - DataTable에 단순 콘텐츠 열이 이미 있습니다. - - - 테이블에 기본 키가 없습니다. - - - {0}은(는) 올바른 Sort 문자열 엔트리가 아닙니다. - - - DataTable을 serialize할 수 없습니다. 하나 이상의 DataColumn 식에서 사용되는 DataTable이 현재 DataTable의 하위 항목이 아닙니다. - - - 이 DataTable은 DataSet의 일부로만 원격이 될 수 있습니다. 하나 이상의 Expression 열에 다른 DataTable에 대한 참조가 있습니다. - - - DataSet 및 DataTable에 대해 다른 원격 형식 속성 값을 설정할 수 없습니다. - - - DataTable을 serialize할 수 없습니다. DataTable 이름이 설정되지 않았습니다. - - - 이름이 '{0}'인 DataTable이 이 DataSet에 이미 속해 있습니다. - - - 같은 Namespace '{1}'이(가) 있는 '{0}'(이)라는 DataTable이 이미 이 DataSet에 속해 있습니다. - - - DataSet 이름과 테이블 이름이 충돌하므로, 중첩 관계에서 ({0}) 테이블은 자신의 자식 테이블이 될 수 없습니다. - - - 이름 '{0}'이(가) 잘못되었습니다. DataTable 이름에는 DataSet과 같은 이름을 사용할 수 없습니다. - - - DataTable이 이미 다른 DataSet에 속해 있습니다. - - - DataTable이 이미 현재 DataSet에 속해 있습니다. - - - {0} 테이블은 이 DataSet에 속하지 않습니다. - - - {0} 테이블을 찾을 수 없습니다. - - - 기존 관계를 포함하는 테이블을 제거할 수 없습니다. 먼저 관계를 제거하십시오. - - - {0} 테이블은 ForeignKeyConstraint {1}에서 참조했으므로 제거할 수 없습니다. 먼저 제약 조건을 제거하십시오. - - - DataTable '{0}'이(가) 원본의 모든 DataTable과 일치하지 않습니다. - - - 대상 DataSet에 {0}의 정의가 없습니다. - - - 대상 DataSet에 {0} {1}이(가) 없습니다. - - - <target>.{0}과(와) <source>.{0}의 속성이 충돌합니다. DataType 속성이 일치하지 않습니다. - - - <target>.PrimaryKey와 <source>.PrimaryKey의 Length가 다릅니다. - - - PrimaryKey의 열이 일치하지 않습니다 : <target>.{0} 및 <source>.{1}. - - - 해당 키에 일치하지 않는 열이 있으므로 관계 {0}을(를) 병합할 수 없습니다. - - - {0} 대상 테이블에 {1} 열의 정의가 없습니다. - - - PrimaryKey 열 {0}이(가) 원본 테이블에 없습니다. - - - RowStates 매개 변수는 DataViewRowState 열거형의 유효한 값 조합으로 설정해야 합니다. - - - Find는 정렬 순서에 따라 행을 찾지만 정렬 순서가 지정되어 있지 않습니다. - - - 인덱싱되는 키에 대한 {0} 값이 필요한데 {1} 값을 받았습니다. - - - 집계 함수 {0}() 및 형식 {1} 사용이 잘못되었습니다. - - - 스토리지 형식이 잘못되었습니다. {0} - - - DataSet Xml 지속성은 '{0}' 값을 Char 값으로 지원하지 않습니다. Byte 스토리지를 대신 사용하세요. - - - 값의 유형이 열 유형과 일치하지 않습니다. - - - 유형 '{0}'이(가) IComparable 인터페이스를 구현하지 않습니다. 비교를 완료할 수 없습니다. - - - {0}을(를) 설정할 수 없습니다. - - - 이미 DataSet의 기본 뷰인 DataViewManager에서는 DataSet을 변경할 수 없습니다. - - - RowStateFilter는 ModifiedOriginals와 ModifiedCurrents를 동시에 표시할 수 없습니다. - - - DataViewManager에서 파생되는 DefaultView 또는 DataView에서 Table 속성을 변경할 수 없습니다. - - - DataSet 속성을 설정한 후에는 다시 변경할 수 없습니다. - - - DataViewManager를 사용하기 전에 DataSet을 설정해야 합니다. - - - Table 속성을 설정한 후에는 다시 변경할 수 없습니다. - - - DataView를 사용하기 전에 DataTable을 설정해야 합니다. - - - 이름이 없는 DataTable에 바인딩할 수 없습니다. - - - 이 목록에 개체를 설정할 수 없습니다. - - - AllowNew가 false인 경우 DataView에서 AddNew를 호출할 수 없습니다. - - - DataView가 열려 있지 않습니다. - - - 이 관계는 DataView가 가리키는 테이블로부터 생성된 것이 아닙니다. - - - AllowDelete가 false인 경우 DataSource에서 삭제할 수 없습니다. - - - AllowEdit이 false인 경우 DataSource에서 편집할 수 없습니다. - - - 인덱스 {0}이(가) 음수이거나 행 개수보다 큽니다. - - - 이 목록에 외부 개체를 추가할 수 없습니다. - - - 이 목록을 지울 수 없습니다. - - - 이 목록에 외부 개체를 삽입할 수 없습니다. - - - 목록에 없는 개체를 제거할 수 없습니다. - - - {0}은(는) {1} 테이블의 DataColumn 또는 DataRelation이 아닙니다. - - - Range 개체에서 Min({0})은 max({1})보다 작거나 같아야 합니다. - - - 범위가 null입니다. - - - MinimumCapacity는 음수가 아니어야 합니다. - - - 이름 '{0}'에 대한 식별자를 생성할 수 없습니다. - - - 네임스페이스는 다르지만 이름이 '{0}'인 테이블이 둘 이상 있습니다. - - - 열 '{0}': 형식 '{1}'은(는) null일 수 없습니다. - - - 열 '{0}': 형식 '{1}'에 매개 변수 없는 생성자가 없습니다. - - - 열 '{0}': 형식 '{1}'에 문자열 인수를 사용하는 생성자가 없습니다. - - - '{0}' 유형의 개체를 '{1}' 유형의 개체로 변환할 수 없습니다. - - - Data Exception. - - - Constraint Exception. - - - 삭제된 행에 액세스할 수 없습니다. - - - 중복 이름을 사용할 수 없습니다. - - - RowChanging 이벤트에서는 작업이 지원되지 않습니다. - - - 제약 조건이 잘못되었습니다. - - - 기본 키가 없습니다. - - - Null이 허용되지 않습니다. - - - 열이 읽기 전용으로 표시되어 있습니다. - - - 테이블에 행이 없습니다. - - - 버전을 찾을 수 없습니다. - - - ReadOnly 데이터가 수정됩니다. - - - DataTableReader가 현재 DataTable '{0}'에 사용할 수 없습니다. - - - DataTableReader에 있는 현재 DataTable '{0}'의 스키마가 변경되었으며 DataTableReader가 잘못되었습니다. - - - DataTableReader를 만들 수 없습니다. DataSet에 DataTable이 없습니다. - - - DataTableReader를 만들 수 없습니다. 인수가 비어 있습니다. - - - DataTableReader를 만들 수 없습니다. 인수에 null 값이 있습니다. - - - 현재 DataRow가 Deleted 상태이거나 Detached 상태입니다. - - - 현재 DataTable '{0}'이(가) 비어 있습니다. DataTable에 DataRow가 없습니다. - - - DataTable 내부 인덱스가 손상되었습니다: '{0}'. - - - 컬렉션이 수정되었습니다. 열거 작업이 실행되지 않았을 수 있습니다. - - - 단순 형식 '{0}'이(가) 다른 '{1}'(으)로 이미 선언되었습니다. - - - foliation이 잘못되었습니다. - - - 연결된 DataSet이 로드된 XML 문서에 매핑된 후에는 테이블 이름을 변경할 수 없습니다. - - - 연결된 DataSet이 로드된 XML 문서에 매핑된 후에는 테이블 네임스페이스를 변경할 수 없습니다. - - - 연결된 DataSet이 로드된 XML 문서에 매핑된 후에는 열 이름을 변경할 수 없습니다. - - - 연결된 DataSet이 로드된 XML 문서에 매핑된 후에는 열 네임스페이스를 변경할 수 없습니다. - - - 연결된 DataSet이 로드된 XML 문서에 매핑된 후에는 ColumnMapping 속성을 변경할 수 없습니다. - - - DataSet이 로드된 XML 문서에 매핑된 후에는 테이블에서 열을 추가하거나 제거할 수 없습니다. - - - DataSet이 로드된 XML 문서에 매핑된 후에는 DataSet에서 테이블을 추가하거나 제거할 수 없습니다. - - - DataSet이 로드된 XML 문서에 매핑된 후에는 DataSet에서 중첩 관계를 추가, 제거 또는 변경할 수 없습니다. - - - DataSet 매개 변수가 잘못되었습니다. 이 매개 변수는 null일 수 없습니다. - - - DataSet이 로드된 XML 문서에 매핑된 후에는 DataSet 이름을 변경할 수 없습니다. - - - 이 형식의 노드를 복제할 수 없습니다. {0} - - - XmlDataDocument에 이미 데이터가 포함되어 있는 경우 로드할 수 없습니다. 새 XmlDataDocument를 사용하십시오. - - - DataSet은 최대 하나의 XmlDataDocument에 연결될 수 있습니다. DataSet이 다른 XmlDataDocument에 이미 연결되어 있으므로 현재 XmlDataDocument에 연결할 수 없습니다. - - - XML 작업을 사용하여 XmlDataDocument를 편집하기 전에 DataSet.EnforceConstraints == false를 설정하십시오. - - - DataDocument에서는 GetElementById()를 지원하지 않습니다. - - - DataDocument에 엔터티 참조를 만들 수 없습니다. - - - DateSet과 DataTable의 Clear 기능은 XmlDataDocument에서 지원되지 않습니다. - - - 이 열은 현재 dataSet에 내장되어 있으므로 제거할 수 없습니다. - - - 이 관계는 현재 dataSet에 내장되어 있으므로 제거할 수 없습니다. - - - 이 컨트롤의 속성이 변경될 때마다 발생합니다. - - - 이 컬렉션의 멤버가 변경될 때마다 발생합니다. - - - 값이 DBNull이므로 가져올 수 없습니다. - - - '{0}' 속성을 사용하려면 Microsoft WindowsNT 또는 WindowsNT 기반 운영 체제가 필요합니다. - - - 요청한 .NET Framework 데이터 공급자를 찾을 수 없습니다. 설치되지 않았을 수 있습니다. - - - 요청한 .NET Framework 데이터 공급자 구현에 System.Data.Common.DbProviderFactory 파생 유형의 인스턴스 필드가 없습니다. - - - 등록된 .NET Framework 데이터 공급자를 찾거나 로드하지 못했습니다. - - - 누락된 .NET Framework 데이터 공급자의 정규화된 어셈블리 이름이 필요합니다. - - - 요소만 사용할 수 있습니다. - - - 자식 노드는 사용할 수 없습니다. - - - 인식할 수 없는 특성 '{0}'입니다. - - - 인식할 수 없는 요소입니다. - - - '{0}' 섹션은 config 파일별로 한 번만 사용할 수 있습니다. - - - 필수 특성 '{0}'을(를) 찾을 수 없습니다. - - - 필요한 '{0}' 특성은 비워 둘 수 없습니다. - - - '{0}' 매개 변수에 대한 배열은 비어 있지 않아야 합니다. - - - 속성 '{0}'에 대해 허용되는 유일한 값은 '{1}'입니다. - - - 속성 '{0}'에 허용되는 값은 '{1}' 또는 '{2}'입니다. - - - 지정한 QuotePrefix 및 QuoteSuffix 값이 일치하지 않습니다. - - - 인수 '{0}'의 길이가 한계인 '{1}'을(를) 초과합니다. - - - {1} 유형의 인수가 필요한데 {0} 유형을 수신했습니다. - - - 키 '{0}'에 대한 값이 잘못되었습니다. - - - 키 '{0}'을(를) 사용하려면 키 '{1}'이(가) 있어야 합니다. - - - 키 '{0}'의 값 길이가 제한인 ‘{1}’을(를) 초과합니다. - - - 지원되지 않는 키워드입니다: '{0}'. - - - UDL 파일을 로드할 수 없습니다. - - - 잘못된 UDL 파일입니다. - - - 내부 .NET Framework Data Provider 오류 {0}입니다. - - - Insert, Update 또는 Delete 명령이 생성된 후에는 QuotePrefix 및 QuoteSuffix 속성을 변경할 수 없습니다. - - - DataAdapter.SelectCommand 속성을 초기화해야 합니다. - - - DataAdapter.SelectCommand.Connection 속성을 초기화해야 합니다. - - - {0} "{1}". - - - {0} "{1}", 따옴표 사용이 잘못되었습니다. - - - {0} "{1}", 현재 한도 "{2}"이(가) 부족합니다. - - - SqlBulkCopy.DestinationTableName이 잘못된 multipart 이름이어서 SqlBulkCopy.WriteToServer에 실패했습니다. - - - multipart 이름이 잘못되어 SQL Server 결과를 처리하지 못했습니다. - - - SqlParameter.UdtTypeName은 잘못된 multipart 이름입니다. - - - SqlParameter.TypeName은 잘못된 multipart 이름입니다. - - - SqlCommand.CommandText 속성 값이 잘못된 multipart 이름이어서 SqlCommand.DeriveParameters에 실패했습니다. - - - OdbcCommand.CommandText 속성 값이 잘못된 multipart 이름이어서 OdbcCommandBuilder.DeriveParameters에 실패했습니다. - - - OleDbCommandBuilder.CommandText 속성 값이 잘못된 multipart 이름이어서 OleDbCommandBuilder.DeriveParameters에 실패했습니다. - - - MultiSubnetFailover 연결 옵션을 사용하여 미러된 SQL Server 인스턴스에 연결이 지원되지 않습니다. - - - DataColumn '{1}'이(가) 계산 열이므로 SourceColumn '{0}'에서의 열 매핑에 실패했습니다. - - - {1}의 SourceColumn '{0}'과(와) {3}의 DataColumn '{2}'의 유형이 일치하지 않습니다. - - - SourceColumn '{2}'의 DataTable '{1}'에 DataColumn '{0}'이(가) 없습니다. - - - SourceColumn에는 비어 있지 않은 문자열이 필요합니다. - - - '{0}'에 대한 SourceColumn 매핑이 없습니다. - - - {0} 열거형 값 {1}은(는) {2} 메서드에서 지원되지 않습니다. - - - {0} 열거형 값 {1}은(는) .NET Framework Odbc Data Provider에서 지원되지 않습니다. - - - {0} 열거형 값 {1}은(는) .NET Framework OleDb Data Provider에서 지원되지 않습니다. - - - {0} 열거형 값 {1}은(는) .NET Framework SqlClient Data Provider에서 지원되지 않습니다. - - - ComputerNameDnsFullyQualified, {0}을(를) 검색할 수 없습니다. - - - '{1}' SourceTable에 대한 '{0}' DataTable이 없습니다. - - - SourceTable에는 비어 있지 않은 문자열이 필요합니다. - - - SourceTable 매핑이 없습니다. '{0}' - - - {0}: CommandText 속성이 초기화되지 않았습니다. - - - {0}: Connection 속성이 초기화되지 않았습니다. - - - {0}을(를) 사용하려면 Connection이 열려 있고 사용 가능해야 합니다. {1} - - - Fill: SelectCommand.Connection 속성이 초기화되지 않았습니다. - - - FillPage: SelectCommand.Connection 속성이 초기화되지 않았습니다. - - - FillSchema: SelectCommand.Connection 속성이 초기화되지 않았습니다. - - - 업데이트하려면 InsertCommand에 연결 개체가 있어야 합니다. InsertCommand의 Connection 속성이 초기화되지 않았습니다. - - - 업데이트하려면 UpdateCommand에서 연결 개체를 사용해야 합니다. UpdateCommand의 Connection 속성이 초기화되지 않았습니다. - - - 업데이트하려면 DeleteCommand에서 연결 개체를 사용해야 합니다. DeleteCommand의 Connection 속성이 초기화되지 않았습니다. - - - 업데이트하려면 연결 개체가 필요합니다. Connection 속성이 초기화되지 않았습니다. - - - 업데이트하려면 명령 복제에서 연결 개체를 사용해야 합니다. 명령 복제의 Connection 속성이 초기화되지 않았습니다. - - - 업데이트하려면 연결이 필요합니다. - - - Update하려면 {0}Command에서 열린 연결 개체를 사용해야 합니다. {1} - - - Update하려면 {0}Command에서 열린 연결 개체를 사용해야 합니다. {1} - - - Update하려면 {0}Command에서 열린 연결 개체를 사용해야 합니다. {1} - - - Update하려면 업데이트 명령에서 열린 연결 개체를 사용해야 합니다. {1} - - - '{0}' 저장 프로시저가 없습니다. - - - 이 명령에 할당된 트랜잭션은 최대 중첩된 보류 중인 로컬 트랜잭션이어야 합니다. - - - 트랜잭션이 현재 연결과 연결되어 있지 않거나 완료되었습니다. - - - 현재 연결에 연결된 트랜잭션은 완료되었지만 삭제되지 않았습니다. 이 연결이 SQL 문을 실행하는 데 사용될 수 있으려면 먼저 트랜잭션이 삭제되어야 합니다. - - - 명령에 할당된 연결이 보류 중인 로컬 트랜잭션에 연결되어 있는 경우 {0}을(를) 사용하려면 트랜잭션이 있어야 합니다. 명령의 Transaction 속성이 초기화되지 않았습니다. - - - 이 명령과 연결된 SqlResultSet이 이미 열려 있습니다. 먼저 닫아야 합니다. - - - 이 {0}과(와) 연결된 DataReader가 이미 열려 있습니다. 먼저 닫아야 합니다. - - - {0} DeriveParameters는 CommandType.StoredProcedure만 지원하며 CommandType.{1}은(는) 지원하지 않습니다. - - - 메서드 '{0}'은(는) 같은 실행에 대해 두 번 이상 호출할 수 없습니다. - - - 비동기 결과가 잘못되었습니다. - - - '{0}'을(를) 호출하기 전에 SelectCommand 속성이 초기화되지 않았습니다. - - - StatementType {0}은(는) 여기에 사용할 수 없습니다. - - - FillSchema: SourceTable 이름에는 비어 있지 않은 문자열이 필요합니다. - - - MaxRecords 값 {0}이(가) 잘못되었습니다. 값은 0보다 크거나 같아야 합니다. - - - StartRecord 값 {0}이(가) 잘못되었습니다. 값은 0보다 크거나 같아야 합니다. - - - Fill: SourceTable 이름에는 비어 있지 않은 문자열이 필요합니다. - - - 계층적 장의 열은 AutoIncrement DataColumn에 매핑되어야 합니다. - - - DataReader.GetFieldType({0})에서 null을 반환했습니다. - - - startRecords 또는 maxRecords에 0이 아닌 값을 사용하는 경우 dataTables 배열에서 한 항목만 지정하십시오. - - - Update로 TableMapping['{0}'] 또는 DataTable '{0}'을(를) 찾을 수 없습니다. - - - Update: 비어 있지 않은 SourceTable 이름이 필요합니다. - - - TableMapping.DataSetTable='{0}'인 TableMapping이 없습니다. - - - Update하려면 올바른 명령 복제가 필요합니다. - - - Update하는 동안 자동 SQL 생성을 수행하려면 올바른 SelectCommand가 필요합니다. - - - 새 행을 포함하여 DataRow 컬렉션을 전달하는 경우 Update하려면 올바른 InsertCommand가 필요합니다. - - - 수정된 행을 포함하여 DataRow 컬렉션을 전달하는 경우 업데이트하려면 올바른 UpdateCommand가 필요합니다. - - - 삭제된 행을 포함하여 DataRow 컬렉션을 전달하는 경우 Update하려면 올바른 DeleteCommand가 필요합니다. - - - DataRow[{0}]는 DataRow[0]와 DataTable이 다릅니다. - - - RowUpdatedEvent: 오류가 발생했습니다. 추가 정보가 없습니다. - - - RowUpdatingEvent: 오류가 발생했습니다. 추가 정보가 없습니다. - - - 일괄 처리할 때 UpdateRowSource.FirstReturnedRecord 또는 UpdateRowSource.Both에 대한 명령의 UpdatedRowSource 속성 값이 잘못되었습니다. - - - 동시성 위반: 필요한 {1} 레코드 중 UpdateCommand의 영향을 받는 {0}이(가) 없습니다. - - - 동시성 위반: 필요한 {1} 레코드 중 DeleteCommand의 영향을 받는 {0}이(가) 없습니다. - - - 동시성 위반: 필요한 {1} 레코드 중 일괄 처리된 명령의 영향을 받는 {0}이(가) 없습니다. + + 메서드 '{0}'은(는) 같은 실행에 대해 두 번 이상 호출할 수 없습니다. CommandTimeout 값 {0}이(가) 잘못되었습니다. 값은 0보다 크거나 같아야 합니다. - - {1}[{0}]: Size 속성의 크기 0은 잘못되었습니다. - - - {0}.Prepare 메서드에는 모든 매개 변수의 형식을 명시적으로 설정해야 합니다. - - - {0}.Prepare 메서드에는 모든 가변 길이 매개 변수의 크기를 0이 아닌 Size로 명시적으로 설정해야 합니다. - - - {0}.Prepare 메서드에는 Precision 및 Scale이 명시적으로 설정된 '{1}' 형식의 매개 변수가 필요합니다. - - - asyncResult에 대한 end 메서드 호출이 일치하지 않습니다. {0}을(를) 호출해야 하는데 {1}이(가) 호출되었습니다. - - - 잘못된 작업입니다. 연결이 닫혔습니다. - - - 연결이 비활성화되었습니다. - - - 로컬 트랜잭션이 연결 중이기 때문에 트랜잭션에 참여할 수 없습니다. 로컬 트랜잭션을 끝낸 후 다시 시도하십시오. - - - 현재 연결이 트랜잭션에 참여했습니다. 현재 트랜잭션을 끝낸 후 다시 시도하십시오. - - - 데이터베이스는 Null, 빈 문자열 또는 공백으로만 구성된 문자열일 수 없습니다. - - - 인수가 너무 깁니다. - - - 'Connect Timeout' 값이 잘못되었습니다. 이 값은 0보다 크거나 같은 정수여야 합니다. - - - 소스 버퍼(크기: {0}) 오프셋이 잘못되었습니다. {1} - - - 대상 버퍼(크기: {0}) 오프셋이 잘못되었습니다. {1} - - - 숫자 값이 너무 커서 96비트 10진수에 맞지 않습니다. - - - 스트림이 닫혀 있으면 {0}을(를) 수행할 수 없습니다. - - - 지정한 SeekOrigin 값이 잘못되었습니다. - - - 여러 기본 테이블에 대해서는 동적 SQL 생성이 지원되지 않습니다. - - - 기본 테이블 정보를 반환하지 않는 SelectCommand에 대해서는 동적 SQL 생성이 지원되지 않습니다. - - - 키 열 정보를 반환하지 않는 SelectCommand에 대해서는 DeleteCommand에 대한 동적 SQL 생성이 지원되지 않습니다. - - - 키 열 정보를 반환하지 않는 SelectCommand에 대해서는 UpdateCommand에 대한 동적 SQL 생성이 지원되지 않습니다. - - - 행 버전 정보를 표시한 열을 포함하지 않는 SelectCommand에 대해서는 DeleteCommand에 대한 동적 SQL 생성이 지원되지 않습니다. - - - 행 버전 정보를 표시한 열을 포함하지 않는 SelectCommand에 대해서는 UpdateCommand에 대한 동적 SQL 생성이 지원되지 않습니다. - - - QuotePrefix 또는 QuoteSuffix 문자 '{1}'을(를) 포함하는 테이블 이름 '{0}'에 대해서는 동적 SQL 생성이 지원되지 않습니다. - - - 열 서수 '{0}'에서 읽으려고 했습니다. CommandBehavior.SequentialAccess를 사용하는 경우 열 서수 '{1}' 이상에서만 읽을 수 있습니다. - - - 데이터 형식 '{0}'은(는) 날짜/시간 자릿수가 잘못되었기 때문에 리터럴 형식으로 지정할 수 없습니다. - - - 요청한 형식의 리터럴로 값의 형식을 지정할 수 없습니다. - - - 데이터 형식 '{0}'은(는) 최대 소수 자릿수가 잘못되었기 때문에 리터럴로 형식을 지정할 수 없습니다. - - - 지정한 리터럴 값은 데이터 유형 '{0}'에 적합하지 않은 리터럴입니다. - - - '{0}': 리터럴 값의 길이는 짝수여야 합니다. - - - '{0}': 리터럴 값은 16진수의 문자열이어야 합니다. - - - 따옴표 접두사를 설정하지 않은 경우 {0}을(를) 사용하려면 연결이 열려 있어야 합니다. - - - 올바른 부울 리터럴 값을 확인할 수 없습니다. Boolean 리터럴을 만들 수 없습니다. - - - 데이터 유형 '{0}'에 연결된 기본 데이터 유형의 리터럴은 지원되지 않습니다. - - - 매개 변수 데이터 형식 {0}이(가) 잘못되었습니다. - - - 개체 유형 {0}에서 알려진 관리되는 공급자 기본 유형으로의 매핑이 없습니다. - - - Type {1}에서 반환한 알 수 없는 TypeCode {0}을(를) 처리할 수 없습니다. - - - DbType {0}에서 {1}(으)로의 매핑이 없습니다. - - - 사용 중인 SQL Server 버전에서 데이터 유형 '{0}'을(를) 지원하지 않습니다. - - - 매개 변수 값 '{0}'이(가) 범위를 벗어났습니다. - - - 지정한 매개 변수 이름 '{0}'이(가) 잘못되었습니다. - - - 반환 값이 여러 개인 매개 변수는 지원되지 않습니다. - - - 매개 변수 Size 값 '{0}'이(가) 잘못되었습니다. 값은 0보다 크거나 같아야 합니다. - - - 인수 '{0}'에 대한 값이 잘못되었습니다. 값은 0보다 크거나 같아야 합니다. - - - 이 메타데이터에 대한 값이 잘못되었습니다. - - - 메타데이터는 SqlDbType.Row여야 합니다. - - - 매개 변수 값을 {0}에서 {1}(으)로 변환하지 못했습니다. - - - {0}에서 병렬 트랜잭션을 지원하지 않습니다. - - - 이 {0}이(가) 완료되었습니다. 더 이상 사용할 수 없습니다. - - - 레코드가 읽기 전용이면 '{0}'을(를) 호출할 수 없습니다. - - - DbDataRecord가 읽기 전용이면 '{0}'을(를) 호출할 수 없습니다. - - - 개체 유형 '{0}'을(를) 데이터 형식 '{1}'(으)로 암시적으로 변환할 수 없습니다. - - - 버퍼 오프셋 '{1}'과(와) 사용 가능한 바이트 '{0}'을(를) 더한 값이 전달된 버퍼의 길이보다 큽니다. - - - 데이터 길이 '{0}'은(는) 0보다 작습니다. - - - 지정한 길이 '{0}'이(가) 범위를 벗어났습니다. - - - '{0}' dataIndex에서 {2} 시도가 잘못되었습니다. CommandBehavior.SequentialAccess를 사용하는 경우 '{1}' dataIndex 이상에서만 읽을 수 있습니다. - - - 오프셋은 값 내의 위치를 참조해야 합니다. - - - ODBC 관리되는 공급자를 사용하려면 TABLE_NAME 제한을 지정해야 하며 GetSchema 인덱스 컬렉션에 대해 Null이 아닌 값을 지정해야 합니다. - - - 메서드 '{0}'에 대한 인수 값이 잘못되었습니다. - - - ODBC 공급자가 SQLGETTYPEINFO에서 결과를 반환하지 않았습니다. - - - 예기치 않은 null DataTable 인수입니다. - - - 예기치 않은 null DataSet 인수입니다. - - - 연결 문자열이 허용되는 최대 길이인 {0}을(를) 초과합니다. - - - {0} - 형식에 매핑할 수 없습니다. - - - 알 수 없는 SQL 형식입니다. {0} - - - 알 수 없는 URT 형식입니다. {0} - - - 음의 인수가 잘못되었습니다. - - - 열려 있는 연결에는 속성을 설정할 수 없습니다. - - - SQL_TRANSACTION '{0}'에서 System.Data.IsolationLevel 열거형 값으로의 올바른 매핑이 없습니다. - - - {0} - 연결 풀링을 사용할 수 없습니다. - - - {0} - 환경 핸들을 할당할 수 없습니다. - - - {0} - 설명자 핸들을 가져올 수 없습니다. - - - 트랜잭션에 없습니다. - - - OdbcType 열거형 값={0}이(가) 잘못되었습니다. - - - DBNull.Value 데이터가 필요한 경우 IsDBNull을 사용합니다. - - - {0}[{1}] {2} - - - {0} - 오류 정보가 없습니다. - - - 연결이 닫혀 있습니다. - - - 내부 연결에 소유자가 없습니다. - - - .NET Framework Odbc Data Provider를 사용하려면 MDAC(Microsoft Data Access Components) 버전 2.6 이상이 필요합니다. 현재 버전 {0}이(가) 설치되어 있습니다. - - - .NET Framework OleDb Data Provider를 사용하려면 MDAC(Microsoft Data Access Components) 버전 2.6 이상이 필요합니다. 현재 버전 {0}이(가) 설치되어 있습니다. - - - '{0}' 인터페이스는 '{1}' 공급자에서 지원되지 않습니다. GetOleDbSchemaTable은 현재 공급자에서 사용할 수 없습니다. - - - 오류 메시지 없이 '{0}'이(가) 실패했습니다(결과 코드: {1}). - - - 오류 정보가 없습니다. 결과 코드: {0} - - - .NET Framework 데이터 공급자를 사용하려면 MDAC(Microsoft Data Access Components)가 필요합니다. MDAC(Microsoft Data Access Components) 버전 2.6 이상을 설치하세요. - - - .NET Framework Data Provider for OLEDB(Microsoft.Data.OleDb)는 MSDASQL(Microsoft OLE DB Provider for ODBC Drivers)을 지원하지 않습니다. .NET Framework Data Provider for ODBC(System.Data.Odbc)를 사용하세요. - - - .NET Framework Data Provider for OLEDB는 대화형이 아닌 환경에서 OLE DB 공급자가 사용자에게 확인하는 작업을 허용하지 않습니다. - - - '{0}' 공급자는 로컬 컴퓨터에 등록할 수 없습니다. - - - ICommandText 인터페이스는 '{0}' 공급자에서 지원되지 않습니다. 대신 CommandType.TableDirect를 사용하십시오. - - - ITransactionLocal 인터페이스는 '{0}' 공급자에서 지원되지 않습니다. 로컬 트랜잭션은 현재 공급자에서 사용할 수 없습니다. - - - 초기화 문자열 형식이 OLE DB 사양에 맞지 않습니다. '연결 문자열의 char[{0}] 주위에서 시작합니다. - - - '비동기 처리'는 .NET Framework Data OLE DB Provider(Microsoft.Data.OleDb)에서 지원하지 않는 기능입니다. - - - ConnectionString에 OLE DB 공급자를 지정하지 않았습니다(예: 'Provider=SQLOLEDB;'). - - - ConnectionString에 지정된 OLE DB 공급자가 너무 깁니다. - - - DbInfoKeywords OleDbSchemaGuid에 대한 제한이 없어야 합니다. - - - DbInfoLiterals OleDbSchemaGuid에 제한 사항이 없어야 합니다. - - - 스키마 guid OleDbSchemaGuid에 대한 제한이 없어야 합니다. - - - {0} OleDbSchemaGuid는 '{1}' 공급자에서 지원되는 스키마가 아닙니다. - - - '{0}' 구성 설정의 값 수가 잘못되었습니다. - - - 구성 설정 '{0}'에 지정된 XML 파일을 로드할 수 없습니다. - - - 명령 매개 변수[{0}] '{1}'이(가) 잘못되었습니다. - - - 부호 불일치 또는 데이터 오버플로 이외의 다른 이유로 인해 명령 매개 변수[{0}] '{1}' 데이터 값을 변환할 수 없습니다. - - - 데이터 값은 부호가 있고 공급자에서 사용한 형식은 부호가 없기 때문에 명령 매개 변수[{0}] '{1}'을(를) 변환하지 못했습니다. - - - 공급자에서 사용한 형식에 대해 데이터 값 오버플로가 발생했기 때문에 명령 매개 변수[{0}] '{1}'을(를) 변환하지 못했습니다. - - - 명령 매개 변수[{0}] '{1}' 값을 보내는 동안 공급자에서 오류가 발생하여 처리가 중지되었습니다. - - - 매개 변수[{0}] '{1}'에 기본값이 없습니다. - - - 매개 변수[{0}]: {1}에서 오류가 발생했습니다. - - - Microsoft.Data.OleDb.OleDbDataAdapter 내부 오류: 매개 변수 접근자가 잘못되었습니다. {0} {1}. - - - Parameter[{0}]: OleDbType 속성이 초기화되지 않았습니다. OleDbType.{1}. - - - ICommandWithParameters 인터페이스는 '{0}' 공급자에서 지원되지 않습니다. 현재 공급자는 명령 매개 변수를 지원하지 않습니다. - - - 프로시저 매개 변수 정보 검색은 '{0}' 공급자에서 지원되지 않습니다. - - - 서버의 소수 구분 기호를 확인할 수 없습니다. 정수가 아닌 숫자 리터럴을 만들 수 없습니다. - - - ADODB.RecordSet 또는 ADODB.Record 개체가 아닙니다. - - - ADODB.RecordSet 개체에서 '{0}' 인터페이스를 검색할 수 없습니다. + + {1}[{0}]: Size 속성의 크기 0은 잘못되었습니다. - - ADODB.Record 개체에서 IRow 인터페이스를 검색할 수 없습니다. + + {0}.Prepare 메서드에는 모든 매개 변수의 형식을 명시적으로 설정해야 합니다. - - 유형이 OLE DB 인터페이스 ISourcesRowset을 지원하지 않습니다. + + {0}.Prepare 메서드에는 모든 가변 길이 매개 변수의 크기를 0이 아닌 Size로 명시적으로 설정해야 합니다. - - 공급자가 IDBInfo를 지원하지 않기 때문에 ReservedWords 스키마 컬렉션을 생성할 수 없습니다. + + {0}.Prepare 메서드에는 Precision 및 Scale이 명시적으로 설정된 '{1}' 형식의 매개 변수가 필요합니다. - - 공급자가 '{0}' 속성을 지원하지 않거나, 소비자가 Initialization 속성 그룹에 없는 속성의 값을 가져오거나 설정하려고 했으며 데이터 원본 개체가 초기화되지 않았기 때문에 속성의 값이 설정되지 않았습니다. + + asyncResult에 대한 end 메서드 호출이 일치하지 않습니다. {0}을(를) 호출해야 하는데 {1}이(가) 호출되었습니다. - - 다음과 같은 이유로 인해 '{0}' 속성을 초기화하지 못했습니다. - 값 데이터 유형이 속성의 데이터 유형이 아니거나 Null이 아닙니다. 예를 들어 속성은 데이터 유형이 Int32인 DBPROP_MEMORYUSAGE이고 데이터 유형은 Int64인 경우입니다. - 값이 잘못되었습니다. 예를 들어 속성은 DBPROP_MEMORYUSAGE이고 값은 음수인 경우입니다. - 값은 속성에 유효한 값이고 공급자는 설정 가능한 속성으로 해당 속성을 지원하지만 공급자는 지정된 값을 지원하지 않습니다. 여기에는 공급자가 기록된 후 OLE DB의 속성에 값이 추가된 경우가 포함됩니다. + + 잘못된 작업입니다. 연결이 닫혔습니다. - - Options의 값이 잘못되었습니다. + + 로컬 트랜잭션이 연결 중이기 때문에 트랜잭션에 참여할 수 없습니다. 로컬 트랜잭션을 끝낸 후 다시 시도하십시오. - - ColumnID 요소가 잘못되었습니다. + + 현재 연결이 트랜잭션에 참여했습니다. 현재 트랜잭션을 끝낸 후 다시 시도하십시오. - - '{0}' 속성은 모든 열에 적용되도록 지정되었지만 하나 이상의 열에 적용할 수 없습니다. + + 데이터베이스는 Null, 빈 문자열 또는 공백으로만 구성된 문자열일 수 없습니다. - - '{0}' 속성은 읽기 전용이거나 소비자가 데이터 원본 개체를 초기화한 후 Initialization 속성 그룹에서 속성의 값을 설정하려고 했습니다. 소비자는 읽기 전용 속성의 값을 현재 값으로 설정할 수 있습니다. 이 상태는 설정 가능한 열 속성을 특정 열에 대해 설정할 수 없는 경우에도 반환됩니다. + + 소스 버퍼(크기: {0}) 오프셋이 잘못되었습니다. {1} - - 선택적인 '{0}' 속성의 값이 지정된 값으로 설정되지 않았으며 지정된 값으로 설정할 수 없습니다. + + 대상 버퍼(크기: {0}) 오프셋이 잘못되었습니다. {1} - - '{0}' 속성의 값을 설정하면 기존 속성과 충돌하기 때문에 설정되지 않았습니다. + + 스트림이 닫혀 있으면 {0}을(를) 수행할 수 없습니다. - - (예약됨) + + 지정한 SeekOrigin 값이 잘못되었습니다. - - 공급자가 알 수 없는 DBPROPSTATUS_ 값 '{0}'을(를) 반환했습니다. + + 열 서수 '{0}'에서 읽으려고 했습니다. CommandBehavior.SequentialAccess를 사용하는 경우 열 서수 '{1}' 이상에서만 읽을 수 있습니다. - - 접근자 유효성 검사가 지연되었으며 메서드가 데이터를 반환하는 동안 수행되었습니다. 바인딩이 이 열 또는 매개 변수에 적합하지 않습니다. + + 매개 변수 데이터 형식 {0}이(가) 잘못되었습니다. - - OleDbDataAdapter 내부 오류: 행 집합 접근자가 잘못되었습니다. Ordinal={0} Status={1}. + + 개체 유형 {0}에서 알려진 관리되는 공급자 기본 유형으로의 매핑이 없습니다. - - 부호가 일치하지 않거나 데이터 오버플로 이외의 이유 때문에 데이터 값을 변환할 수 없습니다. 예를 들어, 데이터 저장소의 데이터는 손상되었지만 행을 계속 검색할 수 있는 경우입니다. + + Type {1}에서 반환한 알 수 없는 TypeCode {0}을(를) 처리할 수 없습니다. - - 공급자가 {0} 데이터를 반환할 메모리를 할당할 수 없습니다. + + DbType {0}에서 {1}(으)로의 매핑이 없습니다. - - {0} 데이터 값이 소비자 버퍼의 {0} 값에 지정된 형식에 대해 오버플로되었기 때문에 변환하지 못했습니다. + + 사용 중인 SQL Server 버전에서 데이터 유형 '{0}'을(를) 지원하지 않습니다. - - OleDbDataAdapter 내부 오류: [get] 알 수 없는 OLE DB 데이터 형식입니다. 0x{0} ({1}) + + 매개 변수 값 '{0}'이(가) 범위를 벗어났습니다. - - {0} 데이터 값은 부호가 있고 소비자 버퍼의 {0} 값에 지정한 형식은 부호가 없기 때문에 변환하지 못했습니다. + + 지정한 매개 변수 이름 '{0}'이(가) 잘못되었습니다. - - OleDbDataAdapter 내부 오류: [set] 알 수 없는 OLE DB 데이터 형식입니다: 0x{0}({1}). + + 매개 변수 Size 값 '{0}'이(가) 잘못되었습니다. 값은 0보다 크거나 같아야 합니다. - - 공급자가 {0} 값을 결정하지 못했습니다. 예를 들어 행을 새로 만들었거나, {0} 열의 기본값을 사용할 수 없거나, 소비자가 새 {0} 값을 아직 설정하지 않은 경우입니다. + + 인수 '{0}'에 대한 값이 잘못되었습니다. 값은 0보다 크거나 같아야 합니다. - - OLE DB 공급자에서 예기치 않은 상태 코드 {0}을(를) 반환했습니다. + + 이 메타데이터에 대한 값이 잘못되었습니다. - - 해당 스레드의 ApartmentState가 ApartmentState.MTA가 아닌 경우 OleDbDataReader.Read는 이것을 만든 동일한 스레드에서 사용되어야 합니다. + + 매개 변수 값을 {0}에서 {1}(으)로 변환하지 못했습니다. - - 알 수 없는 오류입니다. {0} + + {0}에서 병렬 트랜잭션을 지원하지 않습니다. - - {0} 때문에 IErrorInfo.GetDescription이 실패했습니다. + + 이 {0}이(가) 완료되었습니다. 더 이상 사용할 수 없습니다. - - {0} 때문에 IErrorInfo.GetSource가 실패했습니다. + + 지정한 길이 '{0}'이(가) 범위를 벗어났습니다. - - DBTYPE_VECTOR 데이터는 .NET Framework Data OLE DB Provider(Microsoft.Data.OleDb)에서 지원되지 않습니다. + + '{0}' dataIndex에서 {2} 시도가 잘못되었습니다. CommandBehavior.SequentialAccess를 사용하는 경우 '{1}' dataIndex 이상에서만 읽을 수 있습니다. 풀 크기의 최소값 또는 최대값이 잘못되었습니다. 최소 풀 크기는 최대 풀 크기보다 클 수 없습니다. - - '{0}' 키워드는 사용되지 않습니다. 대신 '{1}'을(를) 사용하십시오. - 서버에 대한 DTC(Distributed Transaction Coordinator) 주소를 서버로부터 가져올 수 없습니다. 서버에서 DTC를 활성화하시겠습니까? - - 연결 매개 변수 <{0}> 값의 길이가 허용되는 최대 길이인 65535자를 초과합니다. - 'Packet Size'가 잘못되었습니다. 이 값은 512보다 크거나 같고 32768보다 작거나 같은 정수여야 합니다. 트랜잭션이 잘못되었거나 트랜잭션에 저장할 지점 이름이 잘못되었습니다. - - {0} 열거형 값 {1}은(는) SQL Server 7.0 또는 SQL Server 2000에서 지원되지 않습니다. - User Instance 및 Failover는 호환되지 않는 옵션입니다. 연결 문자열에서 둘 중 하나만 선택해야 합니다. @@ -2499,33 +540,21 @@ 'Authentication=Active Directory Integrated'가 연결 문자열에 지정된 경우 Credential 속성을 설정할 수 없습니다. - - 인증서 인증으로 지정된 인증 매개 변수가 잘못되었습니다. - 연결하려는 SQL Server 인스턴스에 암호화가 필요하지만 이 시스템에서 암호화를 지원하지 않습니다. 연결하려는 SQL Server 인스턴스에서 암호화를 지원하지 않습니다. - - 연결하려는 SQL Server 인스턴스에서 CTAIP를 지원하지 않습니다. - 지원되지 않는 SQL Server 버전입니다. .NET Framework SqlClient Data Provider는 SQL Server 버전 7.0 이상에서만 사용할 수 있습니다. 비동기 작업이 진행 중인 동안에는 {0}을(를) 변경할 수 없습니다. - - 이 명령을 사용하려면 비동기 연결이 필요합니다. 연결 문자열에서 "Asynchronous Processing=true"로 설정하십시오. - 연결 시도 시간이 초과되었습니다. - - 제한 시간이 만료되었으므로 연결이 끊겼습니다. - 인스턴스 오류입니다. @@ -2556,27 +585,18 @@ 오류 코드 0x{0} - - ChangePassword를 사용하려면 SQL Server 9.0 이상이 필요합니다. - SSE 인스턴스 리디렉션은 로컬이 아닌 사용자 인스턴스에는 지원되지 않습니다. '{0}' 인증을 사용할 수 없습니다. - - SQL 인증 방법 '{0}'을(를) 사용할 수 없습니다. - '{0}'에 대해 '{1}' 형식을 사용하여 인증 공급자를 인스턴스화하지 못했습니다. '{0}' 형식을 사용하여 SqlAuthenticationInitializer를 인스턴스화하지 못했습니다. - - 초기화하는 동안 공급자 '{0}'에서 예외를 throw했습니다. - '{0}' 공급자에서 '{1}' 인증을 지원하지 않습니다. @@ -2586,24 +606,15 @@ '{0}'에 대한 인증 공급자를 찾을 수 없습니다. Active Directory(Entra ID) 인증 방법을 사용하려면 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 패키지(https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure)를 설치하세요. - - 인증 공급자에 대한 구성 섹션을 읽지 못했습니다. - '{0}' 매개 변수는 null이거나 비워 둘 수 없습니다. - - 비동기 작업이 이미 완료되었습니다. - 보류 중인 비동기 작업이 이미 진행 중이기 때문에 명령 실행을 계속할 수 없습니다. ExecuteXmlReader로 보낸 명령이 잘못되었습니다. 이 명령은 Xml 결과를 반환해야 합니다. - - 알림을 사용하려면 SQL Server 9.0 이상이 필요합니다. - UdtTypeName의 세 부분으로 된 이름 형식이 잘못되었습니다. @@ -2697,9 +708,6 @@ SSPI 패키지를 초기화할 수 없습니다. - - Timeout이 만료되었습니다. 작업을 완료하기 전에 제한 시간이 경과되었거나 서버가 응답하지 않았습니다. - 연결 제한 시간이 만료되었습니다. 사전 로그인 단계를 시작할 때 제한 시간이 경과되었습니다. 이 문제는 연결 제한 시간에 제공된 시간이 부족하기 때문일 수 있습니다. @@ -2745,12 +753,6 @@ 연결 문자열에서 사용자 인스턴스를 요청했지만 지정한 서버에서 이 옵션을 지원하지 않습니다. - - 서버 인증서가 애플리케이션 유효성 검사에 실패했습니다. - - - 지정한 데이터 길이 {0}이(가) 허용되는 최대 길이 {1}을(를) 초과합니다. - 데이터가 없어서 읽을 수 없습니다. @@ -2775,9 +777,6 @@ 데이터 길이 '{0}'은(는) 0보다 작습니다. - - SqlDataRecord가 읽기 전용이면 '{0}'을(를) 호출할 수 없습니다. - 매핑된 컬렉션이 사용 중이며 현재 액세스할 수 없습니다. @@ -2847,21 +846,6 @@ Microsoft.Data.SqlClient를 사용하여 컨텍스트 연결에 연결할 수 없습니다. - - 중첩 TransactionScopes는 지원되지 않습니다. - - - 예기치 않은 서버 이벤트가 발생했습니다. {0} - - - '{0}'의 길이는 '{1}'의 길이와 일치해야 합니다. - - - SqlDbType '{0}'은(는) {1}에 사용할 수 없습니다. {2}만 지원됩니다. - - - 값이 너무 많습니다. - Stream이 쓰기를 지원하지 않습니다. @@ -2886,192 +870,36 @@ Null - - 메시지 - - - 산술 오버플로가 발생했습니다. - - - 0으로 나눔 오류가 발생했습니다. - - - Data가 Null입니다. 이 메서드 또는 속성은 Null 값에 호출될 수 없습니다. - - - 수치 연산을 수행하면 잘라내기가 발생합니다. - SqlDateTime 오버플로가 발생했습니다. 1753년 1월 1일 오전 12:00:00에서 9999년 12월 31일 오후 11:59:59 사이에 있어야 합니다. - - 연결할 두 문자열의 데이터가 다르게 정렬되어 있습니다. - - - 비교할 두 문자열의 데이터가 다르게 정렬되어 있습니다. - - - 플래그 값이 잘못되었습니다. - - - SqlDecimal에서 Decimal로 변환하는 동안 오버플로가 발생했습니다. - 변환 오버플로가 발생했습니다. - - SqlDateTime이 잘못되었습니다. - - - 표준 시간대를 지정했습니다. SqlDateTime은 표준 시간대를 지원하지 않습니다. - 배열 크기가 잘못되었습니다. 전체 자릿수/소수 자릿수가 잘못되었습니다. - - 입력 형식이 잘못되었습니다. - - - SqlType 오류입니다. - - - 이 버전에서는 SqlBytes와 SqlChars가 2GB 이상의 길이를 지원하지 않습니다. - 읽는 동안 오류가 발생했습니다. - - Stream을 닫거나 삭제했습니다. - - - 버퍼가 없습니다. 읽기 또는 쓰기 작업이 실패했습니다. - - - 현재 값이 Null이므로 0이 아닌 길이로 설정할 수 없습니다. - - - 버퍼가 부족합니다. 읽기 또는 쓰기 작업이 실패했습니다. - - - 현재 값이 Null이므로 0이 아닌 오프셋에 쓸 수 없습니다. - - - 현재 길이보다 큰 오프셋에서 쓸 수 없습니다. 따라서 버퍼에 초기화되지 않은 데이터가 남게 됩니다. - 반환된 데이터의 크기가 2Gb보다 큽니다. 모든 데이터를 가져오려면 SequentialAccess command behavior를 사용하십시오. - - GetDayOfWeek에 대한 인수는 1에서 7 사이의 정수여야 합니다. - - - SQL Type이 데이터와 함께 로드되지 않았습니다. - - - SQL Type이 데이터와 함께 로드되었습니다. - - - 닫힌 XmlReader에 액세스할 수 없습니다. - - - 스트림이 닫혀 있으면 {0}을(를) 호출할 수 없습니다. - - - 스트림을 쓸 수 없으면 {0}을(를) 호출할 수 없습니다. - - - 스트림을 읽을 수 없으면 {0}을(를) 호출할 수 없습니다. - - - 스트림을 검색할 수 없으면 {0}을(를) 호출할 수 없습니다. - - - 서브클래스에서 필요한 메서드를 재정의하지 않았습니다. - '{0}'에 대한 노멀라이저를 만들 수 없습니다. 내부 오류 - - Command 매개 변수에는 Null이 아니며 비어 있지 않은 명령 텍스트가 있어야 합니다. - - - MetaData 매개 변수 배열의 길이는 ParameterDirection 배열 인수와 같아야 합니다. - - - 제공된 시간 범위가 총 14시간을 초과합니다. - - - 데이터 어댑터 오류입니다. - - - DB 동시성 위반입니다. - 작업이 중단되었습니다. 예외가 발생하여 작업이 중단되었습니다. 자세한 내용은 InnerException을 참조하십시오. - - Fill에서 DataRow.AcceptChanges를 호출할지 여부를 지정합니다. - - - Update에서 DataRow.AcceptChanges를 호출할지 여부입니다. - - - Update 이벤트, RowUpdating과 RowUpdated, 그리고 Status가 UpdateStatus.ErrorsOccurred인 경우에 다음 DataRow로 이동할지 여부를 결정합니다. - - - 어댑터가 DataReader에서 DataTable을 채우는 방법입니다. - - - TableMappings의 테이블 또는 열이 없는 경우 수행되는 작업입니다. - - - DataSet의 테이블 또는 열이 없는 경우 수행되는 작업입니다. - - - DataSet 테이블에 소스 테이블을 매핑하는 방법을 지정합니다. - - - Fill을 사용하는 동안 복구할 수 있는 오류가 발생할 때 이벤트가 트리거됩니다. - - - Fill에서 공급자 관련 값 또는 공용 CLSCompliant 값을 반환해야 합니다. - - - DataColumn.ColumnName - - - 원본 열 이름 - 대/소문자 구분 - - - 컬렉션의 항목 수입니다. - - - 지정된 DataColumnMapping 개체입니다. - - - 이 테이블 매핑이 일치할 때의 개별 열 매핑입니다. - - - DataTable.TableName - - - DataTableMapping 소스 테이블 이름이며 대/소문자를 구분합니다. - - - 컬렉션의 항목 수입니다. - - - 지정된 DataTableMapping 개체입니다. - DataSet의 삭제된 행을 업데이트하는 동안 사용됩니다. @@ -3090,63 +918,9 @@ 업데이트하는 동안 각 DataRow 다음에 이벤트가 트리거됩니다. - - 데이터 원본에 대해 실행하기 전에 함께 일괄 처리할 행 수입니다. - - - Select/Insert/Update/DeleteCommands에 연결이 없는 경우 사용되는 연결입니다. - - - DataTable의 삭제된 행에 대한 Update 시 사용됩니다. - - - DataTable의 새 행에 대한 Update 시 사용됩니다. - - - Fill 시 사용됩니다. - - - DataTable의 수정된 행에 대한 Update 시 사용됩니다. - - - Fill에서 공급자 관련 값 또는 공용 CLSCompliant 값을 반환해야 합니다. - - - 원본 테이블을 DataTable에 매핑하는 방법입니다. - - - 사용자가 설정하지 않은 경우 Insert/Update/DeleteCommands가 생성되는 방법입니다. - - - 데이터 원본에 대해 실행하기 전에 함께 일괄 처리할 행 수입니다. - - - Data Source에 연결하는 데 사용되는 연결 문자열입니다. - - - Data Source에 연결할 때 사용할 ODBC 드라이버의 이름입니다. - - - Data Source에 연결할 때 사용할 DSN입니다. - - - true인 경우 관리되는 연결 풀링이 사용됨을 나타냅니다. - - - Data Source에 연결할 때 사용할 UDL 파일입니다. - - - OleDb Provider에서 OLE DB Services를 활성화거나 비활성화할지 여부를 지정합니다. - - - Data Source에 연결할 때 사용할 OLE DB 공급자의 이름입니다. - 애플리케이션의 이름입니다. - - true인 경우 .NET Framework 데이터 공급자에서 비동기 기능의 사용을 활성화합니다. - 연결할 수 있는 데이터베이스의 기본 파일 이름(전체 경로 이름 포함)입니다. @@ -3156,9 +930,6 @@ true인 경우 풀에서 제거될 때 연결 상태가 다시 설정됨을 나타냅니다. - - true인 경우 연결이 Sql Server 컨텍스트에서 시작되어야 함을 나타냅니다. Sql Server 프로세스에서 실행 중인 경우에만 사용할 수 있습니다. - SQL Server 언어 레코드 이름입니다. @@ -3252,54 +1023,9 @@ SQL Server로 인증하는 방법을 지정합니다. - - SQL Server 인증을 위해 지정된 클라이언트 인증서입니다. - - - 데이터 원본에 연결하는 데 사용되는 정보입니다. - - - ConnectionString에서 설정할 수 없는 현재 연결 시간 제한 값입니다. - - - 연결 문자열에서 현재 데이터 원본 카탈로그 값입니다('Database=X'). - - - 연결 문자열에서 현재 데이터 원본입니다('Server=X'). - - - 현재 ODBC 드라이버입니다. - - - ODBC 드라이버에서 액세스하는 제품의 버전입니다. - - - 데이터 원본에 연결하는 데 사용되는 정보입니다. - - - ConnectionString에서 현재 연결 시간 제한 값입니다('Connect Timeout=X'). - - - 연결 문자열에서 현재 데이터 원본 카탈로그 값입니다('Initial Catalog=X'). - - - 연결 문자열에서 현재 데이터 원본입니다('Data Source=X'). - - - 연결 문자열에서 현재 OLE DB 공급자 ProgID입니다('Provider=X'). - - - OLE DB 공급자에서 액세스하는 제품의 버전입니다. - 인증에 사용할 액세스 토큰입니다. - - 연결, 동기 또는 비동기 작업의 상태입니다. 연결 문자열에서 'Asynchronous Processing=x'입니다. - - - 연결하는 데 사용되는 복제에 대한 정보입니다. - 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'와 같이 DataSource에 연결하는 데 사용되는 정보입니다. @@ -3324,9 +1050,6 @@ 이 연결에 대한 통계를 수집합니다. - - 사용자 지정 암호화 키 저장소 공급자입니다. - 실제 연결을 나타내는 GUID입니다. @@ -3363,99 +1086,9 @@ Microsoft SQL Server에서 사용하는 알림의 자동 인리스트먼트입니다. - - 사용자가 지정하지 않은 경우 Update 및 Delete 명령에 대해 where 절이 자동 생성되는 방법입니다. - - - 텍스트 명령의 정규화된 테이블 이름에서 카탈로그 이름의 위치를 나타냅니다. - - - 텍스트 명령의 나머지 식별자와 카탈로그 이름을 구분하는 문자입니다. - - - 텍스트 명령의 나머지 식별자와 스키마 이름을 구분하는 문자입니다. - - - 접두 문자열이 sql 개체 이름에 연결되었습니다. - - - 접미 문자열이 sql 개체 이름에 연결되었습니다. - - - 자동으로 명령을 생성할 DataAdapter입니다. - - - DataTable 또는 SelectCommand에서 스키마를 사용합니다. - - - 사용자가 지정하지 않은 경우 Update 명령에 대해 set 절이 자동 생성되는 방법입니다. - - - OdbcCommands를 자동으로 생성하는 DataAdapter입니다. - - - 텍스트 명령에서 특수 문자가 포함된 식별자를 따옴표로 묶을 때, 여는 따옴표로 사용되는 문자입니다. - - - 텍스트 명령에서 특수 문자가 포함된 식별자를 따옴표로 묶을 때, 닫는 따옴표로 사용되는 문자입니다. - - - OleDbCommands를 자동으로 생성하는 DataAdapter입니다. - - - 숫자 리터럴에서 사용되는 소수 구분 기호입니다. - - - 접두 문자열이 sql 개체 이름에 연결되었습니다. - - - 접미 문자열이 sql 개체 이름에 연결되었습니다. - SqlCommands를 자동으로 생성하는 DataAdapter입니다. - - 숫자 리터럴에서 사용되는 소수 구분 기호입니다. - - - 텍스트 명령에서 특수 문자가 포함된 식별자를 따옴표로 묶을 때, 여는 따옴표로 사용되는 문자입니다. - - - 텍스트 명령에서 특수 문자가 포함된 식별자를 따옴표로 묶을 때, 닫는 따옴표로 사용되는 문자입니다. - - - Prepare, FillSchema 및 CommandBuilder 시나리오를 사용할 때는 decimal 및 numeric 매개 변수에 대해서만 설정해야 합니다. - - - Prepare, FillSchema 및 CommandBuilder 시나리오를 사용할 때는 decimal 및 numeric 매개 변수에 대해서만 설정해야 합니다. - - - 기본 형식 매개 변수입니다. - - - 기본 형식 매개 변수입니다. - - - 매개 변수 이름입니다(예: '@p1'). - - - 기본 형식 매개 변수입니다. - - - 해당 형식의 서버 이름입니다. - - - 가변 길이 데이터 형식의 오프셋입니다. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - 지정된 ParameterDirection '{0}'은(는) 매개 변수 '{1}'에 대해 지원되지 않습니다. Table-valued parameters는 ParameterDirection.Input만 지원합니다. @@ -3468,9 +1101,6 @@ 매개 변수 '{0}'에 TypeName이 지정되었습니다. TypeName은 Structured 매개 변수에만 설정해야 합니다. - - SqlDbType.Structured 형식은 다중 값 형식에만 지원됩니다. - 스키마 테이블의 '{0}' 필드의 DateType 열이 Null입니다. DataType은 null이 아니어야 합니다. @@ -3483,18 +1113,9 @@ '{0}' 레코드의 필드 수가 원본 레코드의 필드 수와 일치하지 않습니다. - - 범위: 0-8000 - SqlClient 데이터 공급자에서 오류가 발생했습니다. {0} - - {0},{1}에 대한 Type Info를 가져올 수 없습니다. - - - 공급자가 다음 어셈블리를 로드하지 못했습니다: {0} - UdtTypeName 속성은 UDT 매개 변수에 대해 설정해야 합니다. @@ -3504,72 +1125,9 @@ 지정한 유형이 대상 서버에 등록되어 있지 않습니다.{0}. - - 기본 키의 일부가 아닌 경우 UDT 매개 변수는 where 절에서 허용되지 않습니다. - - - '{0}'은(는) 잘못된 사용자 정의 유형입니다. 이유: {1}. - - - 인 메모리 및 사용자 정의 형식 모두를 지원합니다. - - - 네이티브 형식을 지원할 수 없습니다. - - - IBinarySerialize를 구현하지 않습니다. - - - serialize할 수 없음 - - - public 생성자 없음 - - - INullable을 구현하지 않습니다. - - - public 생성자가 없습니다. - UDT 특성 없음 - - 매핑 없는 serialization은 아직 지원되지 않습니다. - - - 'public static x Parse(System.Data.SqlTypes.SqlString)' 메서드가 없습니다. - - - 'public override string ToString()' 메서드가 없습니다. - - - 'public static x Null { get; }' 메서드가 없습니다. - - - 네이티브 형식은 유형 '{0}'의 필드를 직접적으로나 다른 필드를 통해 지원하지 않습니다. - - - 유형이 public이 아닙니다. - - - Native UDT는 유형 '{0}'(으)로 인해 순차적 레이아웃이 아닙니다. - - - Native UDT는 최대 byte 크기를 지정합니다. - - - 필드 '{0}'이(가) serialize되지 않은 것으로 표시되어 있습니다. - - - 필드 {0}의 유형이 네이티브 형식에서 허용되지 않는 명시적 레이아웃으로 표시되어 있습니다. - - - 값이 여러 개인 어셈블리 참조에는 0이 아닌 어셈블리 ID가 있어야 합니다. - - - Structured 다중 값 형식은 매개 변수에만 사용할 수 있으며 다른 형식 내에 중첩할 수 없습니다. - 경로 이름이 잘못되었습니다. @@ -3585,9 +1143,6 @@ 트랜잭션을 확장하는 동안 오류가 발생했습니다. - - 쿼리 알림을 수신하는 데 사용되는 종속 개체입니다. - 이 종속 관계가 잘못되었는지 여부를 나타내는 속성입니다. @@ -3600,9 +1155,6 @@ 기존 종속 개체에 명령을 추가합니다. - - Command가 이미 다른 종속 개체에 연결되어 있습니다. 덮어쓸 수 없습니다. - 이 SqlCommand 개체는 이미 다른 SqlDependency 개체에 연결되어 있습니다. @@ -3630,12 +1182,6 @@ 지정한 Timeout이 잘못되었습니다. Timeout은 0보다 작을 수 없습니다. - - 알림 오류입니다. 유형={0}, 정보={1}, 원본={2}. - - - GetMetaData는 이 SqlDbType에 적합하지 않습니다. - dbType {0}은(는) 이 생성자에 사용할 수 없습니다. @@ -4104,9 +1650,6 @@ 내부 오류입니다. 일반 텍스트 값은 null일 수 없습니다. - - 암호화할 수 없습니다. 암호화로 허용되는 제한인 {1}바이트를 초과하는 {0}바이트의 암호 텍스트가 생성되었습니다. 지정된 일반 텍스트 값은 너무 큰 것 같습니다(일반 텍스트 크기: {2}바이트). - 내부 오류입니다. 암호 텍스트 값은 null일 수 없습니다. @@ -4224,9 +1767,6 @@ SQL Server에서 증명 정보를 반환하지 않았습니다. enclave 형식은 '{0}'이고 enclave 증명 URL은 '{1}'입니다. - - 보안 채널을 설정할 수 없습니다. 오류 메시지: {0} - 내부 오류가 발생했습니다. '{1}' 형식의 개체를 생성할 때 빈 null 인수 '{0}'이(가) 지정되었습니다. '{0}'은(는) null일 수 없습니다. @@ -4242,15 +1782,6 @@ Always Encrypted를 사용하여 암호화된 데이터베이스 열의 계산에 대해 SQL Server 인스턴스에 구성된 elclave가 유효하고 안전한지를 확인하는 데 사용할 enclave 증명 서비스의 엔드포인트을 지정합니다. - - enclave 공급자의 구성 섹션을 읽지 못했습니다. 섹션이 애플리케이션 구성 파일에 올바르게 형식이 지정되어 있는지 확인하세요. 오류 메시지: {0} - - - 이름 '{0}'에 대해 '{1}' 형식을 사용하여 enclave 공급자를 인스턴스화하지 못했습니다. 오류 메시지: {2} - - - 내부 오류입니다. SqlColumnEncryptionEnclaveProviderName은 null이거나 비워둘 수 없습니다. - '{0}' 결과 집합을 읽는 동안 오류가 발생했습니다. 연결 문자열에 증명 URL이 지정되지 않았지만 쿼리에 enclave 계산이 필요합니다. enclave 형식은 '{1}'입니다. @@ -4260,9 +1791,6 @@ 내부 오류가 발생했습니다. enclave 계산이 필요한 쿼리를 실행할 때 SQL Server에서 수신된 enclave 형식이 null이거나 비어 있습니다. - - 쿼리를 실행하려면 enclave 계산이 필요하지만 애플리케이션 구성에 enclave 공급자 섹션이 없습니다. - enclave 형식 '{0}' 및 증명 프로토콜 '{1}'에 대한 enclave 공급자를 찾을 수 없습니다. 연결 문자열에 올바른 증명 프로토콜을 지정하세요. @@ -4320,9 +1848,6 @@ 연결에서 모든 명령에 대한 기본 열 암호화 설정입니다. - - 중요한 데이터를 서버에 보내기 전에 매개 변수를 강제로 암호화합니다. - SQL Server 이름과 해당하는 신뢰할 수 있는 열 마스터 키 경로가 포함된 사전 개체입니다. @@ -4332,9 +1857,6 @@ ApplicationIntent ReadOnly 연결 옵션을 사용하여 미러된 SQL Server 인스턴스에 연결할 수 없습니다. - - 예기치 않은 라우팅 정보가 수신되었습니다. - 잘못된 라우팅 정보가 수신되었습니다. @@ -4485,9 +2007,6 @@ 이 컨텍스트에 지원되지 않는 인증이 지정됨: {0} - - Active Directory Interactive 인증 시간이 초과되었습니다. 사용자가 인증 요청에 응답하는 데 시간이 너무 오래 걸렸습니다. - Credential 속성이 설정된 경우 'Authentication=Active Directory Interactive'를 사용할 수 없습니다. @@ -4497,9 +2016,6 @@ 활성 연결의 SPID(서버 프로세스 ID)입니다. - - Active Directory Device Code Flow 인증 시간이 초과되었습니다. 사용자가 인증 요청에 응답하는 데 시간이 너무 오래 걸렸습니다. - 'Authentication=Active Directory Device Code Flow'가 연결 문자열에 지정된 경우 Credential 속성을 설정할 수 없습니다. @@ -4533,9 +2049,6 @@ 요청한 동작에 대해 1차원 배열만 지원됩니다. - - 해당 항목은 지정된 컬렉션에 없으므로 제거할 수 없습니다. - 음수가 아닌 수가 필요합니다. @@ -4563,87 +2076,36 @@ 연결 문자열에 'Authentication=Active Directory Default'가 지정된 경우 AccessTokenCallback 속성을 설정할 수 없습니다. - - {0}이(가) {1}을(를) 반환했습니다. - - - 프로토콜 오류: 수신된 메시지에 유효한 서명이 포함되어 있지만 유효한 보호 수준에서 요구하는 대로 암호화되지 않았습니다. - - - {0}이(가) 오류 {1}(으)로 인해 실패했습니다. - 이 메서드는 이 클래스에서 구현되지 않습니다. - - 요청한 보안 패키지는 지원되지 않습니다. - 잘못된 physicalConnection 유형입니다. AppContext 스위치 '{0}'={1}을(를) 설정하는 동안 예외가 발생했습니다. - - {0} 유형은 이 플랫폼에서 지원되지 않습니다. - 전역화 고정 모드는 지원되지 않습니다. - - Kerberos를 사용하여 인증할 수 없습니다. Kerberos가 'kinit'를 사용하여 클라이언트에서 초기화되었고 Kerberos 인증을 허용하도록 SQL Server에 대한 서비스 사용자 이름이 등록되었는지 확인하세요. - '네트워크 라이브러리' 키워드는 이 플랫폼에서 지원되지 않습니다. 대신 원하는 프로토콜을 '데이터 원본' 앞에 붙입니다(TCP 연결의 경우 'tcp:' 또는 명명된 파이프 연결의 경우 'np:'). 소켓이 오류 코드 '{1}'과(와) 예상되는 '{0}'을(를) throw하지 않았습니다. - - 서버가 이 플랫폼에서 지원되지 않는 기능을 사용하려고 합니다. - '{0}' 키워드는 이 플랫폼에서 지원되지 않습니다. 현재 로드된 System.Transactions.dll은 글로벌 트랜잭션을 지원하지 않습니다. - - 서버에서 데이터를 읽는 동안 지원되지 않는 토큰 '{0}'을(를) 받았습니다. - - - '{0}'은(는) 지원되는 핸들 유형이 아닙니다. - SqlFileStream은 이 플랫폼에서 지원되지 않습니다. LocalDB는 이 플랫폼에서 지원되지 않습니다. - - 버퍼 공간이 부족합니다. 필수: {0} 실제: {1}. - - - 상태가 {0}(사소한 상태: {1})이므로 GSSAPI 작업이 실패했습니다. - - - 오류로 인해 GSSAPI 작업이 실패했습니다 - {0}({1}). - - - Windows 이외의 운영 체제에서는 채널 바인딩을 지원하지 않습니다. - - - 기본 자격 증명이 전달되는 경우 대상 이름은 비어 있지 않아야 합니다. - - - 현재 설치된 GSSAPI 구현에서는 요청한 보호 수준이 지원되지 않습니다. - - - 서버 구현은 지원되지 않습니다. - - - 이 플랫폼의 기본 자격 증명으로는 NTLM 인증을 사용할 수 없습니다. - DataAdapter.Update에서 사용하는 경우 매개 변수 값은 DBNull.Value에서 (Int32)1로 변경되거나 null이 아닌 경우 (Int32)0으로 변경됩니다. @@ -4668,9 +2130,6 @@ 버퍼 최대 크기 초과: {0} - - SourceColumn '{2}'에 DataColumn '{0}'이(가) 없습니다. - SqlBatchCommand 목록이 초기화되지 않았습니다. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx index f852b5ba98..eeb6d69665 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Rosnąco - Nieprawidłowy indeks {0} dla tego elementu {1}: Count={2}. @@ -132,18 +129,12 @@ Element {0} jest już zawarty w innym elemencie {1}. - - Element {0} jest już zawarty w tym elemencie {1}. - Element {0} akceptuje tylko obiekty typu {1} o wartości innej niż null. Próbowano usunąć element {0}, który nie jest zawarty w tym elemencie {1}. - - Element {0}.{1} musi być unikatowy, element „{2}” już istnieje w kolekcji. - Połączenie nie zostało zamknięte. {0} @@ -171,12 +162,6 @@ Nieprawidłowa próba wywołania {0}, gdy czytnik jest zamknięty. - - Nie można zarejestrować transakcji, ponieważ połączenie jest połączeniem podstawowym dla transakcji delegowanej lub z podwyższonym poziomem. - - - Malejąco - Oczekiwany jest niepusty ciąg dla parametru {0}. @@ -204,9 +189,6 @@ Nie zainicjowano właściwości ConnectionString. - - Przechwycono wyjątek Exception niezgodny ze specyfikacją CLS. - Dany element zabezpieczeń nie jest elementem uprawnień. @@ -252,9 +234,6 @@ Dane - - StateChange - Aktualizuj @@ -264,48 +243,12 @@ Element ConnectionState wskazujący, czy połączenie jest otwarte, czy zamknięte. - - Zdarzenie wyzwalane po zmianie stanu połączenia. - - - Ogólny typ parametru. - - - Parametr wejściowy, wyjściowy lub dwukierunkowy. - - - właściwość czasu projektowania używana do generowania kodu o jednoznacznie określonych typach. - - - Przesunięcie w typach danych o zmiennej długości. - - - Nazwa parametru. - - - Rozmiar typów danych o zmiennej długości (ciąg i tablice). - - - Gdy nazwa kolumny źródłowej jest używana przez metodę DataAdapter.Update, służy do znajdowania nazwy DataSetColumn we właściwości ColumnMappings. Ma to na celu kopiowanie wartości między parametrem a wierszem danych. - - - Gdy wersja wartości DataRow jest używana przez metodę DataAdapter.Update (tylko właściwość UpdateCommand), służy do aktualizowania źródła danych. - - - Gdy jest używana przez element DataAdapter.Update, wartość parametru jest zmieniana z DBNull.Value na (Int32)1 lub (Int32)0, jeśli jest niezerowa. - - - Wartość parametru. - Nie można skompilować kolekcji {0}, ponieważ wykonanie zapytania SQL nie powiodło się. Zobacz wewnętrzny wyjątek, aby poznać szczegóły. Określono więcej ograniczeń, niż żądany schemat ({0}) może obsłużyć. - - Wartość {2} nie jest prawidłową wartością ograniczenia {1} kolekcji schematów {0}. - Żądana kolekcja ({0}) nie została zdefiniowana. @@ -315,15 +258,6 @@ Żądana kolekcja ({0}) nie jest obsługiwana przez tę wersję dostawcy. - - Brak jednej z wymaganych kolumn tabel DataSourceInformation. - - - Tabela DataSourceInformation musi zawierać dokładnie jeden wiersz. - - - Brak co najmniej jednej z wymaganych kolumn kolekcji ograniczeń. - Istnieje ograniczenie, dla którego nie ma pasującego wiersza w kolekcji ograniczeń. @@ -342,42 +276,21 @@ Brak kolekcji {0} w elemencie XML metadanych. - - Nieprawidłowy element XML metadanych. - - - Nieprawidłowy element XML metadanych. Kolekcja {0} musi zawierać kolumnę {1} i musi być kolumną ciągu. - Nieprawidłowy element XML metadanych. Kolumna {1} kolekcji {0} musi zawierać niepusty ciąg. - - Akcja - - - Zachowanie - Wypełnij InfoMessage - - Mapowanie - StatementCompleted - - UDT - Powiadomienie - - Schemat - XML @@ -393,9 +306,6 @@ Buforowanie - - Nazwane parametry połączenia - Zabezpieczenia @@ -408,2073 +318,204 @@ Elastyczność połączenia - - Kolekcja, która przechowuje niestandardowe informacje użytkownika. - - - Wskazuje, czy porównywanie ciągów wewnątrz elementu DataSet odbywa się z uwzględnieniem wielkości liter. - - - Nazwa tego elementu DataSet. - - - Wskazuje na niestandardowy „widok” danych zawartych w zestawie danych. Ten widok umożliwia filtrowanie, wyszukiwanie i nawigowanie po niestandardowym widoku danych. - - - Wskazuje, czy należy stosować reguły ograniczeń. - - - Wskazuje, że element DataSet zawiera błędy. - - - Wskazuje ustawienia regionalne, przy użyciu których należy porównywać ciągi wewnątrz elementu DataSet. - - - Wskazuje przestrzeń nazw identyfikatora XML URI dla elementu głównego wskazywanego przez ten element DataSet. - - - Wskazuje prefiks przestrzeni nazw używany dla tego elementu DataSet. - - - Kolekcja, która przechowuje relacje dla tego elementu DataSet. - - - Kolekcja, która przechowuje tabele dla tego elementu DataSet. - - - Występuje, gdy nie można scalić schematów dla dwóch tabel z tą samą nazwą. - - - Występuje po zakończeniu inicjalizacji Initialization. - - - Reprezentuje pamięć podręczną danych. - - - Wskazuje, czy porównywanie ciągów wewnątrz tabeli odbywa się z uwzględnieniem wielkości liter. - - - Zwraca relacje podrzędne dla tej tabeli. - - - Kolekcja, która przechowuje kolumny dla tej tabeli. - - - Kolekcja, która przechowuje ograniczenia dla tej tabeli. - - - Wskazuje element DataSet, do którego należy ta tabela. - - - To jest domyślny element DataView dla tabeli. - - - Wyrażenie używane do obliczenia wartości związanej z danymi w tym wierszu. - - - Zwraca informację, czy tablica zawiera błędy. - - - Wskazuje ustawienia regionalne, przy użyciu których należy porównywać ciągi wewnątrz tabeli. - - - Wskazuje początkowy rozmiar tej tabeli. - - - Wskazuje przestrzeń nazw identyfikatora XML URI dla elementów zawartych w tej tabeli. - - - Wskazuje element Prefix przestrzeni nazw używany dla tej tabeli w reprezentacji XML. - - - Zwraca relacje nadrzędne dla tej tabeli. - - - Wskazuje kolumny reprezentujące klucz podstawowy dla tej tabeli. - - - Wskazuje kolekcję, która przechowuje wiersze danych dla tej tabeli. - - - Wskazuje nazwę używaną do odszukania tej tabeli w kolekcji Tables elementu DataSet. - - - Występuje po pomyślnej edycji wiersza w tabeli. - - - Występuje, gdy wiersz jest zmieniany, aby program obsługi zdarzeń mógł zmodyfikować lub anulować zmianę. Użytkownik może modyfikować wartości w wierszu i powinien zgłosić wyjątek, aby anulować edytowanie. - - - Występuje po pomyślnym usunięciu wiersza z tabeli. - - - Występuje, gdy wiersz w tabeli jest oznaczony do usunięcia. Zgłoś wyjątek, aby anulować usunięcie. - - - Występuje, gdy wartość dla tej kolumny zostanie przesłana. Użytkownik może zmodyfikować proponowaną wartość i zgłosić wyjątek, aby anulować edytowanie. - - - Występuje, gdy wartość dla tej kolumny zostanie zmieniona. - - - Występuje przed wyczyszczeniem wszystkich wierszy z tabeli. - - - Występuje po pomyślnym wyczyszczeniu wszystkich wierszy z tabeli. - - - Występuje po utworzeniu nowego wystąpienia elementu DataRow. - - - Nazwa używana do odszukania tej relacji w kolekcji Relations elementu DataSet. - - - Wskazuje kolumny podrzędne tej relacji. - - - Wskazuje kolumny nadrzędne tej relacji. - - - Wskazuje, czy relacje są zagnieżdżone. - - - Wskazuje, jaki rodzaj operacji kaskadowej ma zostać wykonany dla tej relacji przy usuwaniu. - - - Wskazuje, jaki rodzaj operacji kaskadowej ma zostać wykonany dla tej relacji przy aktualizacji. - - - Wskazuje, jaki rodzaj operacji kaskadowej ma zostać wykonany dla tej relacji przy akceptowaniu i odrzucaniu zmian. - - - Wskazuje kolumny podrzędne tego ograniczenia. - - - Wskazuje kolumny nadrzędne tego ograniczenia. - - - Wskazuje tabelę podrzędną tego ograniczenia. - - - Wskazuje kolumny tego ograniczenia. - - - Wskazuje, czy to ograniczenie jest kluczem podstawowym. - - - Wskazuje nazwę tego ograniczenia. - - - Wskazuje tabelę tego ograniczenia. - - - Wskazuje, czy w tej kolumnie są dozwolone wartości null. - - - Wskazuje, czy kolumna automatycznie się zwiększa dla nowych wierszy dodawanych do tabeli. Kolumna musi być typu Int16, Int32 lub Int64. - - - Wskazuje początkową wartość dla kolumny typu AutoIncrement. - - - Wskazuje przyrost używany w kolumnie typu AutoIncrement. - - - Wskazuje domyślny podpis interfejsu użytkownika dla tej kolumny. - - - Wskazuje nazwę używaną do odszukania tej kolumny w kolekcji Columns elementu DataTable. - - - Zwraca element DataTable, do którego należy ta kolumna. - - - Wskazuje typ danych przechowywanych w tej kolumnie. - - - Wskazuje domyślną wartość kolumny używaną przy dodawaniu nowych wierszy do tabeli. - - - Wskazuje wartość, która jest obliczana dla każdego wiersza w tej kolumnie na podstawie wartości znajdujących się w innych kolumnach, zamiast przyjmowania danych od użytkownika. - - - Wskazuje, w jaki sposób ta kolumna jest utrwalona w kodzie XML: jako atrybut, element, prosty węzeł zawartości czy wcale. - - - Wskazuje identyfikator XML URI dla elementów lub atrybutów przechowywanych w tej kolumnie. - - - Wskazuje element Prefix używany dla tego elementu DataColumn w reprezentacji xml. - - - Wskazuje indeks tej kolumny w kolekcji Columns. - - - Wskazuje, czy ta kolumna zezwala na zmiany po tym, jak wiersz zostanie dodany do tabeli. - - - Wskazuje, czy ta kolumna powinna nałożyć ograniczenie na swoje wartości w wierszach tabeli tak, aby były one unikatowe. - - - Wskazuje maksymalną długość wartości dozwoloną w tej kolumnie. - - - Wskazuje element DateTimeMode tego elementu DataColumn. - - - Wskazuje, czy ten element DataView oraz skojarzony z nim interfejs użytkownika zezwalają na usuwanie. - - - Wskazuje, czy ten element DataView oraz skojarzony z nim interfejs użytkownika zezwalają na edycję. - - - Wskazuje, czy ten element DataView oraz skojarzony z nim interfejs użytkownika zezwalają na dodawanie nowych wierszy. - - - Zwraca liczbę elementów aktualnie znajdujących się w tym widoku. - - - Zwraca wskaźnik powrotny do elementu DataViewManager, który jest właścicielem tego elementu DataSet (jeśli taki istnieje). - - - Wskazuje, czy widok jest otwarty. - - - Wskazuje wyrażenie używane do filtrowania danych zwróconych przez ten element DataView. - - - Wskazuje wersje danych zwróconych przez ten element DataView. - - - Wskazuje nazwy kolumn i kolejność, w której dane są zwracane przez ten element DataView. - - - Wskazuje, czy używać sortowania domyślnego, jeśli właściwość Sort nie została ustawiona. - - - Wskazuje tabelę, która jest używana przez ten element DataView do pobierania danych. - - - Wskazuje, że dane zwrócone przez ten element DataView zostały zmienione w dowolny sposób. - - - Wskazuje źródło danych dla tego elementu DataViewManager. - - - Wskazuje ustawienia sortowania/filtrowania/stanu dla dowolnej tabeli w odpowiadającym elemencie DataSet. - - - Element DataSet nie obsługuje elementu „union” lub „list” jako elementu simpleType. - - - Nieprawidłowa składnia {0}: brak wymaganego atrybutu „{1}”. - - - Wartość „{1}” jest nieprawidłowa dla atrybutu „{0}”. - - - Atrybut „{0}” powinien mieć wartość „{1}” lub „{2}”. - - - Nie można odnaleźć elementu ElementType name=„{0}”. - - - Brak nazwy tabeli nadrzędnej w relacji „{0}”. - - - Brak nazwy tabeli podrzędnej w relacji „{0}”. - - - Brak klucza tabeli nadrzędnej w relacji „{0}”. - - - Brak klucza tabeli podrzędnej w relacji „{0}”. - - - Niezdefiniowany typ danych: „{0}”. - - - Typ danych nie jest zdefiniowany. - - - Nieprawidłowy wybór elementu XPath wewnątrz węzła pola. Nie można odnaleźć: {0}. - - - Nieprawidłowy składnik elementu XPath wewnątrz węzła selektora: {0}. - - - Nieprawidłowy węzeł 'Key' wewnątrz ograniczenia o nazwie: {0}. - - - Nazwa ograniczenia {0} jest już używana w schemacie. - - - Nie można przekonwertować elementu „{0}” na typ „{1}”. - - - Brak elementu „{0}” w ograniczeniu „{1}” o nazwie „{2}”. - - - Nieprawidłowa definicja relacji (Relation): różne długości klucza. - - - Element DataSet nie zezwala na odwołania cykliczne w elemencie ComplexType o nazwie „{0}”. - - - Obiekt DataSet nie może utworzyć wystąpienia abstrakcyjnego obiektu ComplexType dla węzła {0}. - - - Wystąpił błąd z konwerterem wielu elementów docelowych podczas zapisywania schematu XML. Zobacz wewnętrzny wyjątek, aby poznać szczegóły. - - - Wystąpił błąd z konwerterem wielu elementów docelowych podczas zapisywania schematu XML. Zwrócono ciąg o wartości null lub pusty. - - - Zduplikowana deklaracja „{0}”. - - - Nie można załadować elementu diffGram. Brak tabeli „{0}” w docelowym zestawie danych. - - - Nie można załadować elementu diffGram. Brak węzła „sql”. - - - Nazwa kolumny „{0}” jest zdefiniowana dla różnych typów mapowania. - - - Prefiks „{0}” jest nieprawidłowy, ponieważ zawiera znaki specjalne. - - - Cykliczne odwołanie w tabeli „{0}”, która jest zagnieżdżona w samej sobie. - - - Element DataSet nie może rozszerzać jednostek. Użyj elementu XmlValidatingReader i odpowiednio ustaw właściwość EntityHandling. - - - Typ „{0}” nie implementuje interfejsu IXmlSerializable, dlatego nie można kontynuować serializacji. - - - Nie można kontynuować deserializacji. Dane nie implementują interfejsu IXMLSerializable, dlatego polimorfizm nie jest obsługiwany. - - - Element DataTable nie obsługuje wnioskowania schematu na podstawie danych Xml. - - - Nie można kontynuować serializowania DataTable „{0}”. Zawiera element DataRow, który ma wiele wierszy nadrzędnych w tym samym kluczu obcym. - - - Brak atrybutu IsDataSet w schemacie wejściowym (Schema). - - - Nie można określić elementu DataSet. Atrybut IsDataSet istnieje więcej niż raz. - - - Obiekt DataSet nie będzie serializował typów, które implementują interfejs IDynamicMetaObjectProvider, ale nie implementują interfejsu IXmlSerializable. - - - Funkcja nie jest zaimplementowana. {0}. - - - Błąd składni: brak operandu po operatorze {0}. - - - Niezgodność typów w wyrażeniu {0}. - - - Wyrażenie jest zbyt złożone. - - - Nie można odnaleźć kolumny [{0}]. - - - Wyrażenie zawiera nieprawidłową stałą typu string: {0}. - - - Wyrażenie zawiera niezdefiniowane wywołanie funkcji {0}(). - - - Błąd składni w wyrażeniu. - - - Niewłaściwa liczba argumentów: funkcja {0}(). - - - W wyrażeniu brak nawiasu zamykającego. - - - Nie można zinterpretować tokenu {0}' w pozycji {1}. - - - Oczekiwano {0}, ale rzeczywistym tokenem w pozycji {2} jest {1}. - - - Nie można przekonwertować elementu {0} na {1}. - - - Nie można przekonwertować wartości „{0}” na typ {1}. - - - Nieprawidłowa nazwa kolumny: [{0}]. - - - Wyrażenie zawiera nieprawidłową stałą daty {0}. - - - Na liście wyrażeń dla operatora IN mogą występować tylko wyrażenia stałe. - - - Błąd w operatorze Like: wzorzec ciągu {0} jest nieprawidłowy. - - - Błąd składni: elementy występujące po słowie kluczowym IN muszą być oddzielone przecinkami i ujęte w zwykłe nawiasy. - - - Niezgodność typów w argumencie funkcji: {0}(), argument {1}, oczekiwano {2}. - - - Niezgodność typów w argumencie funkcji: {0}(), argument {1}, oczekiwano jednego z typów całkowitych. - - - Nie można wykonać operacji {0} na {1} i {2}. - - - Operator {0} jest niejednoznaczny dla operandów typu {1} i {2}. Nie można mieszać typów podpisanych i niepodpisanych. Użyj jawnej funkcji Convert(). - - - Błąd składni: po słowie kluczowym IN musi nastąpić ujęta w nawiasy niepusta lista wyrażeń oddzielonych przecinkami. - - - Wyrażenie zawiera nieobsługiwany operator {0}. - - - Wyrażenie zawiera nieprawidłową nazwę: {0}. - - - Błąd składni: brak operandu przed operatorem {0}. - - - Wyrażenie ma za dużo nawiasów zamykających. - - - Tabela [{0}] jest związana z więcej niż jedną relacją. Musisz wyraźnie wskazać nazwę relacji w wyrażeniu {1}. - - - Błąd składni w argumencie funkcji agregującej: oczekiwano argumentu oznaczającego pojedynczą kolumnę z ewentualnym kwalifikatorem „Child”. - - - Niepowiązane odwołanie w wyrażeniu agregującym {0}. - - - Nie można obliczyć wartości wyrażenia niestałego bez bieżącego wiersza. - - - Niepowiązane odwołania w wyrażeniu {0}. - - - Nie można obliczyć. Wyrażenie {0} nie jest wartością agregującą. - - - Wyrażenie filtru „{0}” nie zwraca terminu wartości logicznej Boolean. - - - Nieprawidłowa nazwa typu {0}. - - - Błąd składni w wyrażeniu wyszukiwania: oczekiwano słowa kluczowego „Parent”, a następnie argumentu w postaci pojedynczej kolumny z ewentualnym kwalifikatorem relacji: Parent[(<nazwa_relacji>)].<nazwa_kolumny>. - - - Do wywołania elementu DataFilter niezbędny jest wiersz lub tabela. - - - Argument {0}() jest poza zakresem. + + Wartości przesunięcia i długości są spoza zakresu tablicy lub liczba przekracza liczbę elementów znajdujących się między indeksem a końcem kolekcji źródłowej. - - Błąd składni: nieprawidłowe użycie operatora „Is”. Poprawna składnia: <wyrażenia> [Nie] ma wartość null. + + Nie można przekonwertować obiektu typu „{0}” na obiekt typu „{1}”. - - Wartość jest za mała albo za duża dla typu {0}. + + Jedyną dopuszczalną wartością właściwości {0} jest {1}. - - Wystąpił błąd dzielenia przez zero. + + Dopuszczalne wartości właściwości {0} to {1} lub {2}. - - Nie można odnaleźć relacji nadrzędnej {0}. + + Określone wartości QuotePrefix i QuoteSuffix nie są zgodne. - - Argument hours znajduje się poza zakresem. Wartość musi należeć do zakresu od 14 do 14. + + Długość argumentu {0} przekracza limit wynoszący {1}. - - Argument minutes znajduje się poza zakresem. Wartość musi należeć do zakresu od 59 do 59. + + Nieprawidłowa wartość klucza „{0}”. - - Dany zakres pierwszego czasu przekracza 14 godzin. + + Użycie klucza {0} wymaga obecności klucza {1}. - - Właściwość Kind danego argumentu DateTime jest niezgodna z argumentami „hours” i „minutes”. + + Długość wartości dla klucza {0} przekracza limit wynoszący {1}. - - Element DataColumn typu {0} nie obsługuje wyrażeń. + + Słowo kluczowe jest nieobsługiwane: „{0}”. - - Nie można włączyć ograniczeń. Co najmniej jeden wiersz zawiera wartości naruszające ograniczenia inne niż null, unikatowe lub obce. + + Błąd wewnętrzny dostawcy danych programu .NET Framework: {0}. - - Nie można modyfikować kolekcji jako takiej. + + {0} „{1}”. - - Podana nazwa {0} pasuje do co najmniej dwóch nazw w obiekcie kolekcji, jeśli wielkość liter nie jest uwzględniana; jeśli wielkość liter jest uwzględniana, nazwa nie pasuje do żadnej z nich. + + {0} „{1}” – nieprawidłowe użycie cudzysłowu. - - Podana nazwa {0} pasuje do co najmniej dwóch nazw w obiekcie kolekcji z różnymi przestrzeniami nazw. + + {0} „{1}” – bieżący limit, „{2}” jest niewystarczający. - - Wartości przesunięcia i długości są spoza zakresu tablicy lub liczba przekracza liczbę elementów znajdujących się między indeksem a końcem kolekcji źródłowej. + + Operacja SqlBulkCopy.WriteToServer nie powiodła się, ponieważ element SqlBulkCopy.DestinationTableName jest nieprawidłową nazwą wieloczęściową - - Argument {0} jest poza zakresem. + + Przetwarzanie wyników z programu SQL Server nie powiodło się z powodu nieprawidłowej nazwy wieloczęściowej - - Argument {0} nie może mieć wartości null. + + SqlParameter.UdtTypeName jest nieprawidłową nazwą wieloczęściową - - Argument {0} zawiera wartość zerową. + + SqlParameter.TypeName jest nieprawidłową nazwą wieloczęściową - - Nie można odnaleźć kolumny {0}. + + Operacja SqlCommand.DeriveParameters nie powiodła się, ponieważ właściwość SqlCommand.CommandText jest nieprawidłową nazwą wieloczęściową - - Kolumna {0} już należy do tego elementu DataTable. + + Nawiązywanie połączenia z dublowanym wystąpieniem programu SQL Server z użyciem opcji połączenia MultiSubnetFailover jest nieobsługiwane. - - Kolumna {0} już należy do innego elementu DataTable. + + Wartość wyliczenia elementu {0}, wynosząca {1}, nie jest obsługiwana przez metodę {2}. - - W elemencie DataTable nie może być więcej niż jedna kolumna typu SimpleContent. + + Wartość wyliczenia elementu{0} , wynosząca {1}, nie jest obsługiwana przez dostawcę danych SqlClient programu .NET Framework. - - Nie można dodać kolumny typu SimpleContent do tabeli zawierającej kolumny typu Element lub relacje zagnieżdżone. + + {0}: właściwość CommandText nie została zainicjowana - - Kolumna o nazwie {0} należy już do tego elementu DataTable. + + {0}: właściwość połączenia nie została zainicjowana. - - Nie można dodać kolumny o nazwie {0}: tabela zagnieżdżona o takiej samej nazwie należy już do tego elementu DataTable. + + Element {0} wymaga otwartego i dostępnego połączenia (Connection). {1} - - Kolumna o nazwie {0} należy już do tego elementu DataTable: nazwa ta nie może zostać użyta jako nazwa tabeli zagnieżdżonej. + + Przechowywana procedura {0} nie istnieje. - - Nie można usunąć kolumny, która nie należy do tej tabeli. + + Transakcja nie jest skojarzona z bieżącym połączeniem lub została ukończona. - - Nie można usunąć tej kolumny, ponieważ jest ona częścią klucza podstawowego. + + Transakcja skojarzona z bieżącym połączeniem została ukończona, ale nie została usunięta. Transakcja musi zostać usunięta, aby można było użyć połączenia do wykonywania instrukcji SQL. - - Nie można usunąć tej kolumny, ponieważ jest ona częścią klucza nadrzędnego dla relacji {0}. + + Element {0} wymaga, aby polecenie miało transakcję, gdy połączenie przypisane do polecenia jest w fazie oczekującej transakcji lokalnej. Właściwość Transaction polecenia nie została zainicjowana. - - Nie można usunąć tej kolumny, ponieważ jest ona częścią ograniczenia {0} na tabeli {1}. + + Istnieje już skojarzony z tym poleceniem {0} otwarty element DataReader, który należy najpierw zamknąć. - - Nie można usunąć tej kolumny, ponieważ jest ona częścią wyrażenia: {0} = {1}. + + Element {0} DeriveParameters obsługuje tylko element CommandType.StoredProcedure, a nie element CommandType.{1}. - - Nie można ustawić właściwości AutoIncrement dla kolumny obliczanej. - - - Nie można ustawić właściwości AutoIncrement dla kolumny z ustawionym elementem DefaultValue. - - - Nie można ustawić elementu DefaultValue dla kolumny typu AutoIncrement. - - - Wartość elementu AutoIncrementStep musi być niezerowa. - - - Element ColumnName jest wymagany, jeśli jest on częścią elementu DataTable. - - - Nie można zmienić elementu DataType kolumny, gdy kolumna zawiera już dane. - - - Kolumna wymaga prawidłowego elementu DataType. - - - Wartość DefaultValue dla kolumny {0} jest typu {1} i nie może być konwertowana na {2}. - - - Wartość DefaultValue dla kolumny jest typu {0} i nie może być konwertowana na {1}. - - - Wartość DefaultValue dla kolumny {0} jest typu {1}, ale kolumna jest typu {2}. - - - Nie można zmienić właściwości ReadOnly dla kolumny wyrażenia. - - - Nie można zmienić właściwości Unique dla kolumny wyrażenia. - - - Nie można utworzyć wyrażenia w kolumnie, która ma typ AutoIncrement lub Unique. - - - Nie można ustawić wyrażenia, ponieważ kolumna nie może być elementem ReadOnly. - - - Nie można ustawić właściwości Expression dla kolumny {0}, ponieważ jest ona częścią ograniczeń. - - - Nie można utworzyć ograniczenia opartego na kolumnie Expression {0}. - - - Nie można ustawić właściwości Expression z powodu cyklicznego odwołania w wyrażeniu. - - - Element Column {0} zawiera wartości null. - - - Kolumna {0} nie zezwala na wartości nulls. - - - Kolumna {0} jest tylko do odczytu. - - - Kolumna {0} zawiera nieunikatowe wartości. - - - Kolumna {0} nie należy do tabeli {1}. - - - Kolumna musi należeć do tabeli. - - - Nie można zapisać wartości <{0}> w kolumnie {1}. Oczekiwany typ to {2}. - - - Nie można ustawić wartości null dla właściwości Column {0}. Zamiast tego użyj biblioteki DBNull. - - - Nie można ustawić kolumny {0}. Wartość narusza limit MaxLength tej kolumny. - - - Parametr MaxLength ma zastosowanie tylko do ciągowego typu danych. Właściwość Column {0} parametru MaxLength nie może być liczbą nieujemną. - - - Nie można ustawić właściwości Column {0} parametru MaxLength na {1}. Tabela zawiera co najmniej jeden ciąg dłuższy niż nowy limit. - - - Nie można ustawić właściwości Column {0} parametru MaxLength. Kolumna to SimpleContent. - - - Nie można ustawić właściwości Column {0} parametru DataType na {1}. Kolumna to SimpleContent. - - - Nie można ustawić właściwości Column {0} parametru MappingType na SimpleContent. Typ danych kolumny to {1}. - - - Element Column {0} przekracza limit MaxLength. - - - Kolumna „{0}” nie zezwala na wartości typu DBNull.Value. - - - Nie można zmienić przestrzeni nazw właściwości Column {0}. Kolumna to SimpleContent. - - - Nie można zmienić właściwości AutoIncrement elementu DataColumn z typem {0}, gdy kolumna zawiera już dane. - - - Kolumna {0} nie należy do tabeli podstawowej {1}. - - - Element DataColumn o typie {0} jest typem complexType. Nie można serializować wartości typu złożonego jako atrybutu - - - Właściwość DateTimeMode można ustawić tylko w elementach DataColumns typu DateTime. - - - {0} jest nieprawidłową wartością elementu DataSetDateTime. - - - Nie można zmienić wartości właściwości DateTimeMode z {0} na {1}, gdy tabela zawiera już dane. - - - Type {0} nie zawiera statycznej właściwości Null lub statycznego pola static Null. - - - Typ {0} nie implementuje interfejsu IRevertibleChangeTracking, dlatego nie można kontynuować wykonywania funkcji RejectChanges(). - - - Elementy SetAdded i SetModified mogą być wywoływane tylko dla elementów DataRows z niezmienioną właściwością DataRowState. - - - Liczba porządkowa „{0}” przekracza maksymalną liczbę. - - - Element DataSet nie obsługuje elementu System.Nullable<>. - - - Nie można zmienić nazwy ograniczenia na pusty ciąg, kiedy ograniczenie znajduje się w elemencie ConstraintCollection. - - - Nie można wymusić ograniczeń na ograniczeniu {0}. - - - Kolumna {0} jest musi być unikatowa. Klucz {1} już istnieje. - - - Ograniczenie {0} nie należy do tego elementu DataTable. - - - Nie można odnaleźć ograniczenia {0}. - - - Ograniczenie pasuje do znajdującego się już w kolekcji ograniczenia o nazwie {0}. - - - Ograniczenie Constraint o nazwie {0} już należy do tego elementu DataTable. - - - Te kolumny nie mają obecnie unikatowych wartości. - - - Te kolumny nie wskazują na tę tabelę. - - - To ograniczenie nie może być włączone, ponieważ nie wszystkim wartościom odpowiadają wartości nadrzędne. - - - To ograniczenie nie może być dodane, ponieważ element ForeignKey nie należy do tabeli {0}. - - - Nie można usunąć ograniczenia, które nie należy to tej tabeli. - - - Nie można usunąć unikatowego ograniczenia '{0}'. Usuń najpierw ograniczenie klucza obcego '{1}'. - - - Nie można usunąć tego wiersza, ponieważ na relacji {0} wymuszone są ograniczenia i usunięcie tego wiersza spowoduje porzucenie wierszy podrzędnych. - - - Nie można wprowadzić tej zmiany, ponieważ na relacji {0} wymuszone są ograniczenia i zmiana tej wartości spowoduje porzucenie wierszy podrzędnych. - - - Nie można wyczyścić tabeli {0}, ponieważ element ForeignKeyConstraint {1} wymusza ograniczenia oraz w elemencie {2} istnieją wiersze podrzędne. - - - Element ForeignKeyConstraint {0} wymaga, aby wartości klucza podrzędnego ({1}) istniały w tabeli nadrzędnej. - - - Właściwość nie dostępna, ponieważ {0}. - - - Nie można usunąć tego wiersza, ponieważ ma on wiersze podrzędne a na relacji {0} wymuszone są ograniczenia. - - - Nie można dodać ograniczenia klucza podstawowego, ponieważ klucz podstawowy został już ustawiony dla tabeli. - - - Nie można dodać ograniczenia do elementu DataTable {0}, który jest tabelą podrzędną w dwóch relacjach zagnieżdżonych. - - - Nie można utworzyć klucza z kolumn, które należą do różnych tabel. - - - Nie może być 0 kolumn. - - - Nie może być więcej niż {0} kolumn. - - - Nie można utworzyć klucza, gdy ta sama kolumna jest wymieniona więcej niż raz: {0} - - - Nie można usunąć ograniczenia unikatowości, ponieważ jest to klucz podstawowy tabeli. - - - Nie można usunąć ograniczenia unikatowości, ponieważ jest to klucz podstawowy tabeli {0}. - - - Kolumny nadrzędne i podrzędne mają niezgodne typy. - - - Elementy ParentKey i ChildKey są identyczne. - - - Elementy ParentColumns i ChildColumns powinny mieć taką samą długość. - - - Elementy ParentColumns i ChildColumns muszą mieć niezerową długość. - - - Wiersz nie należy do tego samego elementu DataSet co ta relacja. - - - Element RelationName jest wymagany, jeśli jest częścią elementu DataSet. - - - Element GetChildRows wymaga wiersza, którego właściwością Table jest {0}, a właściwością Table zaznaczonego wiersza jest {1}. - - - Ta relacja powinna łączyć dwie tabele w tym elemencie DataSet, aby można ją było dodać do tego elementu. - - - Element GetParentRow wymaga wiersza, którego tabelą jest {0}, a tabelą zaznaczonego wiersza jest {1}. - - - Element SetParentRow wymaga wiersza podrzędnego, którego tabelą jest {0}, a tabelą określonego wiersza jest {1}. - - - Nie można tworzyć relacji między tabelami w różnych elementach DataSet. - - - Nie można utworzyć relacji między tabelami w różnych elementach DataSet. - - - Dla tych kolumn podrzędnych już istnieje relacja. - - - Ta relacja nie należy do tej kolekcji relacji. - - - Ta relacja należy już do innego elementu DataSet. - - - Ta relacja należy już do tego elementu DataSet. - - - Relacja Relation o nazwie {0} już należy do tego elementu DataSet. - - - Relacja Relation {0} nie należy do tego elementu DataSet. - - - Nie można odnaleźć relacji {0}. - - - Nie można utworzyć kolekcji dla tabeli null. - - - Tabela, dla której relacje wyświetla ta kolekcja, została usunięta ze swojego elementu DataSet. - - - Nie można dodać relacji do kolekcji tej tabeli ParentRelation, jeśli ta tabela nie jest tabelą podrzędną. - - - Do kolekcji ChildRelation tej tabeli nie można dodać relacji, w której ta tabela nie jest tabelą nadrzędną. - - - Nie można ustawić właściwości Nested na false dla tej relacji. - - - Ta sama tabela {0} nie może być tabelą podrzędną w dwóch relacjach zagnieżdżonych. - - - Tabela ({0}) nie może być tabelą podrzędną dla samej siebie w relacjach zagnieżdżonych. - - - Nie można dodać elementu DataRelation lub elementu Constraint, które maja różne ustawienia Locale lub CaseSensitive w tabeli nadrzędnej i jej tabelach podrzędnych. - - - Nie można utworzyć elementu DataRelation, jeśli kolumn nadrzędnych lub podrzędnych nie ma w elemencie DataSet. - - - Tabela zagnieżdżona {0}, która dziedziczy przestrzeń nazw, nie może mieć wielu tabel nadrzędnych w różnych przestrzeniach nazw. - - - Tabela zagnieżdżona {0} z pustą przestrzenią nazw nie może mieć wielu tabel nadrzędnych w różnych przestrzeniach nazw. - - - Wiersz nie należy do tego samego elementu DataSet co ta relacja. - - - Nie można wykonać tej operacji na wierszu nieznajdującym się w tabeli. - - - Ta relacja i wiersz podrzędny nie należą do tego samego elementu DataSet. - - - Nie można zmienić proponowanej wartości w zdarzeniu RowChanging. - - - Nie można wywołać elementu EndEdit() wewnątrz zdarzenia OnRowChanging. - - - Nie można wywołać elementu BeginEdit() wewnątrz zdarzenia RowChanging. - - - Nie można wywołać elementu CancelEdit() wewnątrz zdarzenia OnRowChanging. Zgłoś wyjątek, aby anulować tę aktualizację. - - - Nie można wywołać elementu Delete wewnątrz zdarzenia OnRowDeleting. Zgłoś wyjątek, aby anulować to usunięcie. - - - Tablica wejściowa jest dłuższa niż liczba kolumn w tej tabeli. - - - Brak dostępnych danych o stanie Current. - - - Brak dostępnych danych o stanie Original. - - - Brak dostępnych danych o stanie Proposed. - - - Ten wiersz został usunięty z tabeli i nie zawiera żadnych danych. Funkcja BeginEdit() umożliwi tworzenie nowych danych w tym wierszu. - - - Nie można uzyskać dostępu do informacji o usuniętym wierszu za pośrednictwem tego wiersza. - - - Element Version musi być ustawiony na wartość Original, Current lub Proposed. - - - Brak wiersza w pozycji {0}. - - - Pozycja wstawiania wiersza {0} jest nieprawidłowa. - - - Dwukrotnie znaleziono wartość rowOrder={0} w tabeli o nazwie {1}. - - - Brak wartości w sekwencji rowOrder dla tabeli {0}. - - - Brak podanego wiersza DataRow w bieżącej kolekcji DataRowCollection. - - - Ten wiersz należy już do innej tabeli. - - - Ten wiersz należy już do tej tabeli. - - - Nie można usunąć tego wiersza, ponieważ został już usunięty. - - - Ten wiersz jest pusty. - - - Nie można usunąć wiersza, który został już usunięty. - - - Wiersz podrzędny ma wiele elementów nadrzędnych. - - - Nierozpoznany wzorzec bitów stanu wiersza. - - - Nie można zmienić nazwy elementu DataSet na pusty ciąg. - - - Nazwa „{0}” jest nieprawidłowa. Element DataSet nie może mieć tej samej nazwy co element DataTable. - - - Przestrzeń nazw schematu jest nieprawidłowa. Zamiast tego użyj: {0}. - - - Nie można zmienić właściwości CaseSensitive ani Locale. Ta zmiana doprowadziłaby do tego, że co najmniej jeden element DataRelation lub Constraint miałby różne ustawienia Locale lub CaseSensitive w tabelach pokrewnych. - - - Właściwość SchemaSerializationMode można ustawić tylko wtedy, jeśli jest ona zastępowana przez element pochodny DataSet. - - - Kolumny PrimaryKey nie należą do tej tabeli. - - - Nie można dodać zagnieżdżonej relacji lub kolumny typu Element do tabeli zawierającej kolumnę typu SimpleContent. - - - Element TableName jest wymagany, jeśli jest on częścią elementu DataSet. - - - Element DataTable już ma kolumnę SimpleContent. - - - Tabela nie ma klucza podstawowego. - - - {0} nie jest prawidłowym wpisem ciągu Sort. - - - Nie można serializować elementu DataTable. Element DataTable używany w jednym lub większej liczbie wyrażeń DataColumn nie jest elementem potomnym bieżącego elementu DataTable. - - - Ten element DataTable może być zdalny tylko jako część elementu DataSet. Co najmniej jedna kolumna wyrażenia ma odwołanie do innych obiektów DataTable. - - - Wartość właściwości formatu usług zdalnych musi być taka sama dla elementu DataSet i elementu DataTable. - - - Nie można serializować elementu DataTable. Nie ustawiono nazwy elementu DataTable. - - - Element DataTable o nazwie {0} już należy do tego elementu DataSet. - - - Element DataTable o nazwie {0} z tą samą przestrzenią nazw Namespace ({1}) już należy do tego elementu DataSet. - - - Tabela ({0}) nie może być tabelą podrzędną dla samej siebie w relacji zagnieżdżonej: istnieje konflikt między nazwą elementu DataSet i nazwą tabeli. - - - Nazwa „{0}” jest nieprawidłowa. Element DataTable nie może mieć tej samej nazwy co element DataSet. - - - Element DataTable już należy do innego elementu DataSet. - - - Element DataTable już należy do tego elementu DataSet. - - - Tabela {0} nie należy do tego elementu DataSet. - - - Nie można odnaleźć tabeli {0}. - - - Nie można usunąć tabeli, która ma istniejące relacje. Najpierw usuń relacje. - - - Nie można usunąć tabeli {0}, ponieważ odwołuje się ona do elementu ForeignKeyConstraint {1}. Najpierw usuń ograniczenie. - - - Element DataTable {0} nie odpowiada żadnemu elementowi DataTable istniejącemu w źródle. - - - W docelowym elemencie DataSet brak definicji dla {0}. - - - W docelowym elemencie DataSet brak {0} {1}. - - - <target>.{0} i <source>.{0} mają sprzeczne właściwości: niezgodność właściwości DataType. - - - Elementy <target>.PrimaryKey i <source>.PrimaryKey mają różną długość Length. - - - Niezgodność kolumn w elemencie PrimaryKey: <target>.{0} zamiast <source>.{1}. - - - Relacja {0} nie może być scalona, ponieważ klucze mają niezgodne kolumny. - - - W tabeli docelowej {0} brak definicji dla kolumny {1}. - - - Kolumna {0} elementu PrimaryKey nie istnieje w tabeli źródłowej. - - - Parametr RowStates musi być ustawiony na prawidłową kombinację wartości z wyliczenia DataViewRowState. - - - Funkcja Find znajduje wiersz w oparciu o porządek sortowania, a nie określono żadnego porządku sortowania. - - - Oczekiwano {0} wartości dla indeksowanego klucza, ale otrzymano {1} wartości. - - - Niewłaściwe użycie funkcji agregującej {0}() i typu: {1}. - - - Niewłaściwy typ magazynu: {0}. - - - Utrwalenie Xml elementu DataSet nie obsługuje wartości {0} jako wartości Char, w zamian użyj magazynu Byte. - - - Typ wartości niezgodny z typem kolumny - - - Typ {0} nie implementuje interfejsu IComparable. Nie można wykonać porównania. - - - Nie można ustawić {0}. - - - Nie można zmienić elementu DataSet w elemencie DataViewManager, który jest już domyślnym widokiem dla elementu DataSet. - - - Element RowStateFilter nie może pokazywać elementów ModifiedOriginals i ModifiedCurrents w tym samym czasie. - - - Nie można zmienić właściwości Table w elemencie DefaultView lub DataView pochodzącym z elementu DataViewManager. - - - Po ustawieniu właściwości DataSet nie można jej zmienić. - - - Element DataSet musi być ustawiony przed użyciem elementu DataViewManager. - - - Po ustawieniu właściwości Table nie można jej zmienić. - - - Element DataTable musi być ustawiony przed użyciem elementu DataView. - - - Nie można powiązać do elementu DataTable bez nazwy. - - - Nie można ustawić obiektu dla tej listy. - - - Nie można wywołać elementu AddNew w elemencie DataView, jeśli element AllowNew ma wartość false. - - - Element DataView nie jest otwarty. - - - W tabeli, do której odnosi się ten element DataView, brak elementu nadrzędnego dla tej relacji. - - - Nie można usunąć danych z elementu DataSource, jeśli element AllowDelete ma wartość false. - - - Nie można edytować danych z elementu DataSource, jeśli element AllowEdit ma wartość false. - - - Indeks {0} jest ujemny lub większy niż liczba wierszy. - - - Nie można dodać obiektów zewnętrznych do tej listy. - - - Nie można wyczyścić tej listy. - - - Nie można wstawić obiektów zewnętrznych do tej listy. - - - Nie można usunąć obiektów nieznajdujących się na liście. - - - {0} nie jest ani elementem DataColumn, ani elementem DataRelation dla tabeli {1}. - - - Min ({0}) musi być mniejsze lub równe max ({1}) w obiekcie Range. - - - To jest zakres null. - - - Element MinimumCapacity nie może być zerowy. - - - Nie można wygenerować identyfikatora dla nazwy '{0}'. - - - Istnieje więcej niż jedna tabela o tej samej nazwie {0} (nawet jeśli przestrzeń nazw jest inna). - - - Kolumna '{0}': typ '{1}' nie może być zerowy. - - - Kolumna {0}: typ {1} nie ma bezparametrowego konstruktora. - - - Kolumna {0}: typ {1} nie ma konstruktora z argumentem typu string. - - - Nie można przekonwertować obiektu typu „{0}” na obiekt typu „{1}”. - - - Data Exception. - - - Constraint Exception. - - - Usunięte wiersze są niedostępne. - - - Zduplikowana nazwa nie jest dozwolona. - - - Operacja nie jest obsługiwana w zdarzeniu RowChanging. - - - Nieprawidłowe ograniczenie. - - - Brak klucza podstawowego. - - - Wartość Null jest niedozwolona. - - - Kolumna jest oznaczona jako przeznaczona tylko do odczytu. - - - Nie odnaleziono wiersza w tabeli. - - - Nie odnaleziono wersji. - - - Dane elementu ReadOnly mają właściwość Modified. - - - Element DataTableReader jest nieprawidłowy dla bieżącego elementu DataTable {0}. - - - Schemat bieżącego elementu DataTable {0} w elemencie DataTableReader został zmieniony – element DataTableReader jest nieprawidłowy. - - - Nie można utworzyć elementu DataTableReader. Brak elementu DataTable w elemencie DataSet. - - - Nie można utworzyć elementu DataTableReader. Argument jest pusty. - - - Nie można utworzyć elementu DataTableReader. Argumenty zawierają wartość null. - - - Bieżący element DataRow jest w stanie Deleted albo Detached. - - - Bieżący element DataTable {0} jest pusty. Brak elementu DataRow w tabeli DataTable. - - - Indeks wewnętrzny elementu DataTable jest uszkodzony: {0}. - - - Kolekcja została zmodyfikowana; nie można wykonać operacji wyliczania. - - - Typ prosty „{0}” został już zadeklarowany z innym elementem „{1}”. - - - Niewłaściwe uwarstwienie. - - - Nie można zmienić nazwy tabeli po zmapowaniu skojarzonego elementu DataSet do załadowanego dokumentu XML. - - - Nie można zmienić przestrzeni nazw tabeli po zmapowaniu skojarzonego elementu DataSet do załadowanego dokumentu XML. - - - Nie można zmienić nazwy kolumny po zmapowaniu skojarzonego elementu DataSet do załadowanego dokumentu XML. - - - Nie można zmienić przestrzeni nazw kolumny po zmapowaniu skojarzonego elementu DataSet do załadowanego dokumentu XML. - - - Nie można zmienić właściwości ColumnMapping po zmapowaniu skojarzonego elementu DataSet do załadowanego dokumentu XML. - - - Nie można dodać ani usunąć kolumn z tabeli po zmapowaniu elementu DataSet do załadowanego dokumentu XML. - - - Nie można dodać lub usunąć tabel z elementu DataSet po zmapowaniu tego elementu do załadowanego dokumentu XML. - - - Nie można dodać, usunąć lub zmienić relacji zagnieżdżonych z elementu DataSet po zmapowaniu tego elementu do załadowanego dokumentu XML. - - - Parametr zestawu danych jest nieprawidłowy. Nie może mieć wartości null. - - - Nie można zmienić nazwy elementu DataSet po zmapowaniu tego elementu do załadowanego dokumentu XML. - - - Ten typ węzła nie może być klonowany: {0}. - - - Nie można załadować elementu XmlDataDocument, jeśli zawiera on już dane. Użyj nowego dokumentu XmlDataDocument. - - - Element DataSet może być skojarzony z co najwyżej jednym elementem XmlDataDocument. Nie można skojarzyć elementu DataSet z bieżącym obiektem XmlDataDocument, ponieważ zestaw danych jest już skojarzony z innym obiektem XmlDataDocument. - - - Ustaw ograniczenie DataSet.EnforceConstraints == false przed próbą edycji dokumentu XmlDataDocument przy użyciu operacji XML. - - - Element GetElementById() nie jest obsługiwany w dokumencie DataDocument. - - - Nie można stworzyć odwołań do jednostki w dokumencie DataDocument. - - - Wykonywanie funkcji Clear na elementach DateSet i DataTable nie jest obsługiwane w dokumencie XmlDataDocument. - - - Nie można usunąć kolumny, ponieważ jest ona wbudowana do tego elementu dataSet. - - - Nie można usunąć relacji, ponieważ jest ona wbudowana do tego elementu DataSet. - - - Występuje za każdym razem, gdy ulega zmianie dowolna właściwość dla tego sterownika. - - - Występuje, gdy zmieni się członkostwo tej kolekcji. - - - Nie można pobrać wartości, ponieważ jest to wartość DBNull. - - - Właściwość {0} wymaga systemu operacyjnego Microsoft WindowsNT lub opartego na systemie WindowsNT. - - - Nie można znaleźć żądanego dostawcy danych .NET Framework. Być może nie jest zainstalowany. - - - Żądana implementacja dostawcy danych programu .NET Framework nie ma pola wystąpienia typu pochodnego System.Data.Common.DbProviderFactory. - - - Nie można odnaleźć lub załadować zarejestrowanego dostawcy danych programu .NET Framework. - - - Brakująca nazwa kwalifikowana zestawu dostawcy danych programu .NET Framework jest wymagana. - - - Dozwolone są tylko elementy. - - - Węzły podrzędne nie są dozwolone. - - - nierozpoznany atrybut {0}. - - - Nierozpoznany element. - - - W każdym pliku konfiguracyjnym sekcja {0} może wystąpić tylko raz. - - - Nie znaleziono wymaganego atrybutu „{0}”. - - - Wymagany atrybut {0} nie może być pusty. - - - Oczekiwana jest niepusta tablica dla parametru {0}. - - - Jedyną dopuszczalną wartością właściwości {0} jest {1}. - - - Dopuszczalne wartości właściwości {0} to {1} lub {2}. - - - Określone wartości QuotePrefix i QuoteSuffix nie są zgodne. - - - Długość argumentu {0} przekracza limit wynoszący {1}. - - - Oczekiwano argumentu typu {1}, ale otrzymano typ {0}. - - - Nieprawidłowa wartość klucza „{0}”. - - - Użycie klucza {0} wymaga obecności klucza {1}. - - - Długość wartości dla klucza {0} przekracza limit wynoszący {1}. - - - Słowo kluczowe jest nieobsługiwane: „{0}”. - - - Nie można załadować pliku UDL. - - - Nieprawidłowy plik UDL. - - - Błąd wewnętrzny dostawcy danych programu .NET Framework: {0}. - - - Właściwości QuotePrefix i QuoteSuffix nie mogą być już zmieniane po wygenerowaniu polecenia Insert, Update lub Delete. - - - Właściwość DataAdapter.SelectCommand musi zostać zainicjowana. - - - Właściwość DataAdapter.SelectCommand.Connection musi zostać zainicjowana; - - - {0} „{1}”. - - - {0} „{1}” – nieprawidłowe użycie cudzysłowu. - - - {0} „{1}” – bieżący limit, „{2}” jest niewystarczający. - - - Operacja SqlBulkCopy.WriteToServer nie powiodła się, ponieważ element SqlBulkCopy.DestinationTableName jest nieprawidłową nazwą wieloczęściową - - - Przetwarzanie wyników z programu SQL Server nie powiodło się z powodu nieprawidłowej nazwy wieloczęściowej - - - SqlParameter.UdtTypeName jest nieprawidłową nazwą wieloczęściową - - - SqlParameter.TypeName jest nieprawidłową nazwą wieloczęściową - - - Operacja SqlCommand.DeriveParameters nie powiodła się, ponieważ właściwość SqlCommand.CommandText jest nieprawidłową nazwą wieloczęściową - - - Operacja OdbcCommandBuilder.DeriveParameters nie powiodła się, ponieważ właściwość OdbcCommand.CommandText jest nieprawidłową nazwą wieloczęściową. - - - Operacja OleDbCommandBuilder.DeriveParameters nie powiodła się, ponieważ właściwość OleDbCommandBuilder.CommandText jest nieprawidłową nazwą wieloczęściową. - - - Nawiązywanie połączenia z dublowanym wystąpieniem programu SQL Server z użyciem opcji połączenia MultiSubnetFailover jest nieobsługiwane. - - - Nie można mapować kolumn z elementu SourceColumn {0}, ponieważ element DataColumn {1} jest kolumną obliczaną. - - - Niezgodność typów niemożliwych do konwersji między elementem SourceColumn {0} z {1} a elementem DataColumn {2} z {3}. - - - Brak elementu DataColumn {0} w elemencie DataTable {1} dla elementu SourceColumn {2}. - - - Wymagane jest, aby element SourceColumn były niepustym ciągiem. - - - Brak mapowania elementu SourceColumn dla {0}. - - - Wartość wyliczenia elementu {0}, wynosząca {1}, nie jest obsługiwana przez metodę {2}. - - - Wartość wyliczenia elementu {0}, wynosząca {1}, nie jest obsługiwana przez dostawcę danych Odbc programu .NET Framework. - - - Wartość wyliczenia elementu {0}, wynosząca {1}, nie jest obsługiwana przez dostawcę danych OleDb programu .NET Framework. - - - Wartość wyliczenia elementu{0} , wynosząca {1}, nie jest obsługiwana przez dostawcę danych SqlClient programu .NET Framework. - - - Nie można pobrać danych ComputerNameDnsFullyQualified, {0}. - - - Brak elementu DataTable {0} dla elementu SourceTable {1}. - - - Element SourceTable musi być ciągiem niepustym - - - Brak mapowania elementu SourceTable: {0} - - - {0}: właściwość CommandText nie została zainicjowana - - - {0}: właściwość połączenia nie została zainicjowana. - - - Element {0} wymaga otwartego i dostępnego połączenia (Connection). {1} - - - Fill: właściwość SelectCommand.Connection nie została zainicjowana. - - - FillPage: właściwość SelectCommand.Connection nie została zainicjowana. - - - FillSchema: właściwość SelectCommand.Connection nie została zainicjowana. - - - Aktualizacja wymaga, aby element InsertCommand miał obiekt połączenia. Właściwość Connection elementu InsertCommand nie została zainicjowana. - - - Aktualizacja wymaga, aby element UpdateCommand miał obiekt połączenia. Właściwość Connection elementu UpdateCommand nie została zainicjowana. - - - Aktualizacja wymaga, aby element DeleteCommand miał obiekt połączenia. Właściwość Connection elementu DeleteCommand nie została zainicjowana. - - - Aktualizacja wymaga obiektu połączenia. Nie zainicjowano właściwości Connection. - - - Aktualizacja wymaga, aby klon polecenia miał obiekt połączenia. Właściwość Connection klona polecenia nie została zainicjowana. - - - Aktualizacja wymaga połączenia. - - - Operacja Update wymaga, aby element {0}Command miał obiekt z otwartym połączeniem. {1} - - - Operacja Update wymaga, aby element {0}Command miał obiekt z otwartym połączeniem. {1} - - - Operacja Update wymaga, aby element {0}Command miał obiekt z otwartym połączeniem. {1} - - - Operacja Update wymaga, aby polecenie aktualizujące miało obiekt z otwartym połączeniem. {1} - - - Przechowywana procedura {0} nie istnieje. - - - Transakcja przypisana do tego polecenia musi być najbardziej zagnieżdżoną oczekującą transakcją lokalną. - - - Transakcja nie jest skojarzona z bieżącym połączeniem lub została ukończona. - - - Transakcja skojarzona z bieżącym połączeniem została ukończona, ale nie została usunięta. Transakcja musi zostać usunięta, aby można było użyć połączenia do wykonywania instrukcji SQL. - - - Element {0} wymaga, aby polecenie miało transakcję, gdy połączenie przypisane do polecenia jest w fazie oczekującej transakcji lokalnej. Właściwość Transaction polecenia nie została zainicjowana. - - - Istnieje już skojarzony z tym poleceniem otwarty element SqlResultSet, który należy najpierw zamknąć. - - - Istnieje już skojarzony z tym poleceniem {0} otwarty element DataReader, który należy najpierw zamknąć. - - - Element {0} DeriveParameters obsługuje tylko element CommandType.StoredProcedure, a nie element CommandType.{1}. - - - Metoda {0} nie może być wywoływana więcej niż raz dla tego samego wykonania. - - - Nieprawidłowy wynik operacji asynchronicznej. - - - Właściwość SelectCommand nie została zainicjowana przed wywołaniem {0}. - - - Elementu StatementType {0} nie oczekiwano w tym miejscu. - - - Operacja FillSchema: oczekiwano niepustego ciągu w nazwie elementu SourceTable. - - - Nieprawidłowa wartość elementu MaxRecords wynosząca {0}; wartość musi być >= 0. - - - Nieprawidłowa wartość elementu StartRecord wynosząca {0}; wartość musi być >= 0. - - - Operacja Fill: oczekiwano niepustego ciągu w nazwie elementu SourceTable. - - - Hierarchiczne kolumny rozdziałów muszą mapować do elementu DataColumn typu AutoIncrement. - - - Element DataReader.GetFieldType({0}) zwrócił wartość null. - - - W przypadku używania niezerowych wartości dla elementów startRecords lub maxRecords określ tylko jeden element w tablicy dataTables. - - - Operacja Update nie może odnaleźć elementu TableMapping[{0}] lub elementu DataTable {0}. - - - Operacja Update: oczekiwano niepustej nazwy elementu SourceTable. - - - Brak elementu TableMapping, gdy element TableMapping.DataSetTable={0}. - - - Operacja Update wymaga prawidłowego klonu polecenia. - - - Automatyczne generowanie kodu SQL podczas operacji Update wymaga prawidłowego elementu SelectCommand. - - - Operacja Update wymaga prawidłowego elementu InsertCommand podczas przekazywania kolekcji DataRow z nowymi wierszami. - - - Operacja Update wymaga prawidłowego elementu UpdateCommand podczas przekazywania kolekcji DataRow ze zmodyfikowanymi wierszami. - - - Operacja Update wymaga prawidłowego elementu DeleteCommand podczas przekazywania kolekcji DataRow z usuniętymi wierszami. - - - Element DataRow[{0}] jest z innego elementu DataTable niż element DataRow[0]. - - - RowUpdatedEvent: wystąpiły błędy; brak dodatkowych informacji. - - - RowUpdatingEvent: wystąpiły błędy; brak dodatkowych informacji. - - - Podczas przetwarzania wsadowego wartość UpdateRowSource.FirstReturnedRecord lub UpdateRowSource.Both właściwości UpdatedRowSource polecenia jest nieprawidłowa. - - - Naruszenie współbieżności: element UpdateCommand dotyczy następującej liczby rekordów: {0} (oczekiwana liczba rekordów: {1}). - - - Naruszenie współbieżności: element DeleteCommand dotyczy następującej liczby rekordów: {0} (oczekiwana liczba rekordów: {1}). - - - Naruszenie współbieżności: polecenie w trybie wsadowym wpłynęło na {0} z oczekiwanych {1} rekordów. + + Metoda {0} nie może być wywoływana więcej niż raz dla tego samego wykonania. Nieprawidłowa wartość elementu CommandTimeout {0}; wartość musi być >= 0. - - {1}[{0}]: właściwość Size ma nieprawidłowy rozmiar 0. - - - Metoda {0}.Prepare wymaga, aby wszystkie parametry miały jawnie ustawiony typ. - - - Metoda {0}.Prepare wymaga, aby wszystkie parametry o zmiennej długości miały jawnie ustawiony niezerowy element Size. - - - Metoda {0}.Prepare wymaga, aby parametry typu {1} miały jawnie ustawione elementy Precision i Scale. - - - Niezgodne wywołanie metody końcowej dla elementu asyncResult. Oczekiwano wywołania {0}, ale zamiast tego wywołano {1}. - - - Nieprawidłowa operacja. Połączenie jest zamknięte. - - - Połączenie zostało wyłączone. - - - Nie można zarejestrować w transakcji, ponieważ transakcja lokalna jest w toku w połączeniu. Zakończ transakcję lokalną i spróbuj ponownie. - - - Połączenie ma obecnie zarejestrowaną transakcję. Zakończ bieżącą transakcję i spróbuj ponownie. - - - Baza danych nie może być zerowa, nie może być ciągiem pustym ani ciągiem złożonym tylko z odstępów. - - - Argument jest za długi. - - - Nieprawidłowa wartość elementu Connect Timeout, która musi być liczbą całkowitą >= 0. - - - Nieprawidłowe przesunięcie buforu źródłowego (rozmiar {0}): {1} - - - Nieprawidłowe przesunięcie buforu docelowego (rozmiar {0}): {1} - - - Wartość numeryczna jest za duża – nie mieści się w 96-bitowej zmiennej dziesiętnej. - - - Nieprawidłowa próba {0} podjęta, gdy strumień jest zamknięty. - - - Określona wartość elementu SeekOrigin jest nieprawidłowa. - - - Dynamiczne generowanie kodu SQL nie jest obsługiwane dla wielu tabel podstawowych. - - - Dynamiczne generowanie kodu SQL nie jest obsługiwane dla elementu SelectCommand, który nie zwraca żadnych informacji o tabeli podstawowej. - - - Dynamiczne generowanie kodu SQL dla elementu DeleteCommand nie jest obsługiwane dla elementu SelectCommand, który nie zwraca żadnych informacji o kolumnie klucza. - - - Dynamiczne generowanie kodu SQL dla elementu UpdateCommand nie jest obsługiwane dla elementu SelectCommand, który nie zwraca żadnych informacji o kolumnie klucza. - - - Dynamiczne generowanie kodu SQL dla elementu DeleteCommand nie jest obsługiwane dla elementu SelectCommand, który nie zawiera kolumny wersji wiersza. - - - Dynamiczne generowanie kodu SQL dla elementu UpdateCommand nie jest obsługiwane dla elementu SelectCommand, który nie zawiera kolumny wersji wiersza. - - - Dynamiczne generowanie kodu SQL nie jest obsługiwane dla nazw tabel {0} zawierających znak {1} stanowiący element QuotePrefix lub QuoteSuffix. - - - Nieprawidłowa próba odczytu z numeru kolumny {0}. Właściwość CommandBehavior.SequentialAccess umożliwia odczyt tylko z kolumn o liczbie porządkowej „{1}” lub większej. - - - Typu danych {0} nie można sformatować jako literału, ponieważ ma nieprawidłowe cyfry daty/godziny. - - - Wartości nie można sformatować jako literału żądanego typu. - - - Typu danych {0} nie można sformatować jako literału, ponieważ ma nieprawidłową maksymalną skalę. - - - Podana wartość literału nie jest prawidłowym literałem dla typu danych {0}. - - - „{0}”: długość wartości literału musi być parzysta. - - - {0}: wartość literału musi być ciągiem z cyframi szesnastkowymi. - - - Element {0} wymaga otwartego połączenia, gdy prefiks oferty nie został ustawiony. - - - Nie można określić prawidłowych wartości literałów logicznych. Nie można utworzyć literałów logicznych. - - - Literały macierzystego typu danych skojarzone z typem danych {0} nie są obsługiwane. - - - Typ danych parametru {0} jest nieprawidłowy. - - - Nie istnieje żadne mapowanie typu obiektu {0} do znanego zarządzanego typu macierzystego dostawcy. - - - Nie można obsłużyć nieznanego elementu TypeCode {0} zwróconego przez typ {1}. - - - Nie istnieje żadne mapowanie z elementu DbType {0} do znanego elementu {1}. - - - Używana wersja programu SQL Server nie obsługuje typu danych {0}. - - - Wartość parametru {0} jest poza zakresem. - - - Określona nazwa parametru „{0}” jest nieprawidłowa. - - - Wiele parametrów wartości zwracanej nie jest obsługiwane. - - - Nieprawidłowa wartość rozmiaru parametru {0}. Wartość musi być większa niż lub równa 0. - - - Nieprawidłowa wartość argumentu {0}. Wartość musi być większa niż lub równa 0. - - - Nieprawidłowa wartość dla tych metadanych. - - - Metadane muszą być elementem SqlDbType.Row - - - Nie można przekonwertować wartości parametru z {0} na {1}. - - - {0} nie obsługuje transakcji równoległych. - - - Ten element {0} zakończył się; nie można go już używać. - - - Nie można wywołać elementu {0}, jeśli rekord jest tylko do odczytu. - - - Nie można wywołać elementu {0}, jeśli element DbDataRecord jest tylko do odczytu. - - - Jawna konwersja typu obiektu „{0}” na typ danych „{1}” nie jest obsługiwana. - - - Suma przesunięcia buforu „{1}” i liczby dostępnych bajtów „{0}” jest większa niż długość przekazana w buforze. - - - Długość danych „{0}” ma wartość mniejszą niż 0. - - - Określona długość {0} nie należy do zakresu. - - - Nieprawidłowa próba {2} w indeksie dataIndex {0}. Właściwość CommandBehavior.SequentialAccess umożliwia odczyt tylko z kolumn o liczbie porządkowej {1} lub większej. - - - Przesunięcie musi odwoływać się do lokalizacji w wartości. - - - "Dostawca zarządzany za pośrednictwem ODBC wymaga, aby ograniczenie TABLE_NAME było określone i niezerowe dla kolekcji indeksów GetSchema. - - - Nieprawidłowa wartość argumentu dla metody {0}. - - - Dostawca ODBC nie zwrócił wyników z funkcji SQLGETTYPEINFO. - - - Nieoczekiwany argument null elementu DataTable - - - Nieoczekiwany zerowy argument null elementu DataSet. - - - Parametry połączenia przekraczają maksymalną dozwoloną długość wynoszącą {0}. - - - {0} – nie można zmapować typu. - - - Nieznany typ SQL – {0}. - - - Nieznany typ URT – {0}. - - - Nieprawidłowy argument ujemny! - - - Nie można ustawić właściwości dla otwartego połączenia. - - - Brak prawidłowego mapowania dla elementu SQL_TRANSACTION {0} do wartości wyliczenia System.Data.IsolationLevel. - - - {0} – nie można włączyć puli połączeń... - - - {0} – nie można przydzielić uchwytu środowiska. - - - {0} – nie można pobrać uchwytu deskryptora. - - - Poza transakcją - - - Nieprawidłowa wartość wyliczenia OdbcType={0}. - - - Użyj elementu IsDBNull tam, gdzie oczekiwane są dane DBNull.Value. - - - {0} [{1}] {2} - - - {0} – brak dostępnych informacji o błędzie - - - Połączenie jest zamknięte. - - - Połączenie wewnętrzne nie ma właściciela. - - - Dostawca danych Odbc programu .NET Framework wymaga programu Microsoft Data Access Components (MDAC) w wersji 2.6 lub nowszej. Znaleziono obecnie zainstalowaną wersję {0}. - - - Dostawca danych OleDb programu .NET Framework wymaga składników programu Microsoft Data Access Components (MDAC) w wersji 2.6 lub nowszej. Znaleziono obecnie zainstalowaną wersję {0}. - - - Interfejs {0} nie jest obsługiwany przez dostawcę {1}. Element GetOleDbSchemaTable jest niedostępny u bieżącego dostawcy. - - - Niepowodzenie elementu {0}, żaden komunikat o błędzie nie jest dostępny. Kod wyniku: {1}. - - - Brak dostępnych komunikatów o błędach, kod wyniku: {0}. - - - Dostawcy danych programu .NET Framework wymagają składników programu Microsoft Data Access Components(MDAC). Zainstaluj program Microsoft Data Access Components (MDAC) w wersji 2.6 lub nowszej. - - - Dostawca danych programu .NET Framework dla OLEDB (Microsoft.Data.OleDb) nie obsługuje dostawcy OLE DB firmy Microsoft dla sterowników ODBC (MSDASQL). Użyj dostawcy danych programu .NET Framework dla interfejsu ODBC (System.Data.Odbc). - - - Dostawca danych (Provider) programu .NET Framework dla OLEDB nie pozwoli dostawcy bazy danych OLE DB Provider na monitowanie użytkownika w środowisku nieinterakcyjnym. - - - Dostawca {0} nie jest zarejestrowany na lokalnym komputerze. - - - Interfejs ICommandText nie jest obsługiwany przez dostawcę {0}. Zamiast tego użyj elementu CommandType.TableDirect. - - - Interfejs ITransactionLocal nie jest obsługiwany przez dostawcę {0}. Transakcje lokalne są niedostępne u bieżącego dostawcy. - - - Format ciągu inicjowania jest niezgodny ze specyfikacją OLE DB. Rozpoczynanie od znaku char[{0}] w parametrach połączenia. - - - „Przetwarzanie asynchroniczne” nie jest obsługiwaną funkcją dostawcy OLE DB danych programu .NET Framework (Microsoft.Data.OleDb). - - - Dostawca OLE DB nie został określony w parametrze ConnectionString. Przykładem może być „Provider=SQLOLEDB;”. - - - Określenie dostawcy danych OLE DB w elemencie ConnectionString jest za długie. - - - Nie oczekiwano żadnych ograniczeń dla elementu DbInfoKeywords OleDbSchemaGuid. - - - Nie oczekiwano żadnych ograniczeń dla elementu DbInfoLiterals OleDbSchemaGuid. - - - Nie oczekiwano żadnych ograniczeń dla identyfikatora guid schematu OleDbSchemaGuid. - - - Schemat OleDbSchemaGuid {0} nie jest obsługiwany przez dostawcę {1}. - - - Ustawienie konfiguracji {0} ma błędną liczbę wartości. - - - Nie można załadować pliku XML określonego w ustawieniu konfiguracyjnym {0}. - - - Parametr polecenia[{0}] {1} jest nieprawidłowy. - - - Nie można konwertować wartości danych parametru polecenia[{0}] {1} z innego powodu niż niezgodność podpisu lub przepełnienie danych. - - - Nie można konwertować parametru polecenia[{0}] {1}, ponieważ wartość danych była typu signed, a typ użyty przez dostawcę był typem unsigned. - - - Nie można konwertować parametru polecenia[{0}] {1}, ponieważ wartość danych przepełniła typ użyty przez dostawcę. - - - Dostawca napotkał błąd podczas wysyłania wartości parametru polecenia[{0}] {1}, przetwarzanie zostało zatrzymane. - - - Parametr[{0}] {1} nie ma wartości domyślnej. - - - Wystąpił błąd parametru [{0}]: {1}. - - - Błąd wewnętrzny Microsoft.Data.OleDb.OleDbDataAdapter: nieprawidłowy metoda dostępu do parametru: {0}{1}. - - - Parameter[{0}]: właściwość typu OleDbType nie została zainicjowana: OleDbType.{1}. - - - Interfejs ICommandWithParameters nie jest obsługiwany przez dostawcę {0}. Parametry polecenia nie są obsługiwane przez bieżącego dostawcę. - - - Pobieranie informacji o parametrach procedury nie jest obsługiwane przez dostawcę {0}. - - - Nie można określić separatora dziesiętnego serwera. Nie można utworzyć literałów liczbowych innych niż całkowite. - - - Obiekt nie jest ani elementem ADODB.RecordSet ani ADODB.Record. - - - Nie można pobrać interfejsu {0} z obiektu ADODB.RecordSet. + + {1}[{0}]: właściwość Size ma nieprawidłowy rozmiar 0. - - Nie można pobrać interfejsu IRow z obiektu ADODB.Record. + + Metoda {0}.Prepare wymaga, aby wszystkie parametry miały jawnie ustawiony typ. - - Typ nie obsługuje interfejsu ISourcesRowset bazy danych OLE DB. + + Metoda {0}.Prepare wymaga, aby wszystkie parametry o zmiennej długości miały jawnie ustawiony niezerowy element Size. - - Nie można utworzyć kolekcji schematu ReservedWords, ponieważ dostawca nie obsługuje elementu IDBInfo. + + Metoda {0}.Prepare wymaga, aby parametry typu {1} miały jawnie ustawione elementy Precision i Scale. - - Wartość właściwości nie została ustawiona, ponieważ dostawca nie obsługuje właściwości {0} albo użytkownik próbował pobrać lub ustawić wartości właściwości poza grupą właściwości Initialization, podczas gdy obiekt źródła danych nie został zainicjowany. + + Niezgodne wywołanie metody końcowej dla elementu asyncResult. Oczekiwano wywołania {0}, ale zamiast tego wywołano {1}. - - Nie można zainicjować właściwości {0} z jednego z następujących powodów: - Typ danych wartości nie był typem danych właściwości ani nie miał wartości null. Na przykład właściwość DBPROP_MEMORYUSAGE ma typ danych Int32, a typ danych to Int64. - Wartość nie była prawidłową wartością. Na przykład właściwość to DBPROP_MEMORYUSAGE, a wartość była ujemna. - Wartość była prawidłową wartością właściwości, a dostawca obsługuje tę właściwość jako właściwość ustawialną, ale dostawca nie obsługuje określonej wartości. Obejmuje to przypadek, w którym wartość została dodana do właściwości w OLE DB po zapisaniu dostawcy. + + Nieprawidłowa operacja. Połączenie jest zamknięte. - - Wartość opcji Options była nieprawidłowa. + + Nie można zarejestrować w transakcji, ponieważ transakcja lokalna jest w toku w połączeniu. Zakończ transakcję lokalną i spróbuj ponownie. - - Element ColumnID był nieprawidłowy. + + Połączenie ma obecnie zarejestrowaną transakcję. Zakończ bieżącą transakcję i spróbuj ponownie. - - Właściwość {0} została określona w celu zastosowania dla wszystkich kolumn, ale nie można jej zastosować dla co najmniej jednej z nich. + + Baza danych nie może być zerowa, nie może być ciągiem pustym ani ciągiem złożonym tylko z odstępów. - - Właściwość {0} była tylko do odczytu lub użytkownik próbował ustawić wartości właściwości w grupie właściwości Initialization po zainicjowaniu obiektu źródła danych. Użytkownicy mogą ustawić wartość właściwości tylko do odczytu na jej bieżącą wartość. Ten stan jest również zwracany, jeśli nie można ustawić właściwości kolumny ustawialnej dla określonej kolumny. + + Nieprawidłowe przesunięcie buforu źródłowego (rozmiar {0}): {1} - - Opcjonalna wartość właściwości {0} nie została ustawiona na określoną wielkość. Ustawienie właściwości na określoną wartość nie było niemożliwe. + + Nieprawidłowe przesunięcie buforu docelowego (rozmiar {0}): {1} - - Wartość właściwości {0} nie została ustawiona, ponieważ próba jej ustawienia spowodowała wystąpienie konfliktu z istniejącą właściwością. + + Nieprawidłowa próba {0} podjęta, gdy strumień jest zamknięty. - - (Zarezerwowany). + + Określona wartość elementu SeekOrigin jest nieprawidłowa. - - Dostawca zwrócił nieznaną wartość DBPROPSTATUS_ {0}. + + Nieprawidłowa próba odczytu z numeru kolumny {0}. Właściwość CommandBehavior.SequentialAccess umożliwia odczyt tylko z kolumn o liczbie porządkowej „{1}” lub większej. - - Weryfikacja metody dostępu została odroczona i została wykonana, gdy metoda zwróciła dane. Powiązanie było nieprawidłowe dla tej kolumny lub parametru. + + Typ danych parametru {0} jest nieprawidłowy. - - Błąd wewnętrzny elementu OleDbDataAdapter: nieprawidłowy akcesor zestawu wierszy: liczba porządkowa Ordinal={0}, stan Status={1}. + + Nie istnieje żadne mapowanie typu obiektu {0} do znanego zarządzanego typu macierzystego dostawcy. - - Nie można konwertować wartości danych z innego powodu niż niezgodność podpisu lub przepełnienie danych. Na przykład dane zostały uszkodzone w magazynie danych, ale wiersz nadal można było pobrać. + + Nie można obsłużyć nieznanego elementu TypeCode {0} zwróconego przez typ {1}. - - Dostawca nie mógł przydzielić pamięci, do której należy zwrócić dane {0}. + + Nie istnieje żadne mapowanie z elementu DbType {0} do znanego elementu {1}. - - Nie można dokonać konwersji, ponieważ wartość danych {0} przepełniła typ określony dla wartości {0} w buforze użytkownika. + + Używana wersja programu SQL Server nie obsługuje typu danych {0}. - - Błąd wewnętrzny elementu OleDbDataAdapter: [get] Nieznany typ danych OLE DB: 0x{0} ({1}). + + Wartość parametru {0} jest poza zakresem. - - Nie można dokonać konwersji, ponieważ wartość danych {0} była typu signed, a typ określony dla wartości {0} w buforze użytkownika był typem unsigned. + + Określona nazwa parametru „{0}” jest nieprawidłowa. - - Błąd wewnętrzny elementu OleDbDataAdapter: [set] nieznany typ danych OLE DB: 0x{0} ({1}). + + Nieprawidłowa wartość rozmiaru parametru {0}. Wartość musi być większa niż lub równa 0. - - Dostawca nie mógł określić wartości {0}. Na przykład wiersz został właśnie utworzony, wartość domyślna kolumny {0} jest niedostępna, a użytkownik nie ustawił jeszcze nowej wartości {0}. + + Nieprawidłowa wartość argumentu {0}. Wartość musi być większa niż lub równa 0. - - Dostawca OLE DB zwrócił niespodziewaną wartość stanu wynoszącą {0}. + + Nieprawidłowa wartość dla tych metadanych. - - Element OleDbDataReader.Read musi być używany z tego samego wątku, w którym został stworzony, jeśli element ApartmentState tego wątku nie był elementem ApartmentState.MTA. + + Nie można przekonwertować wartości parametru z {0} na {1}. - - Nieokreślony błąd: {0} + + {0} nie obsługuje transakcji równoległych. - - Funkcja IErrorInfo.GetDescription nie powiodła się z {0}. + + Ten element {0} zakończył się; nie można go już używać. - - Funkcja IErrorInfo.GetSource nie powiodła się z {0}. + + Określona długość {0} nie należy do zakresu. - - Dane DBTYPE_VECTOR nie są obsługiwane przez dostawcę OLE DB danych programu .NET Framework (Microsoft.Data.OleDb). + + Nieprawidłowa próba {2} w indeksie dataIndex {0}. Właściwość CommandBehavior.SequentialAccess umożliwia odczyt tylko z kolumn o liczbie porządkowej {1} lub większej. Nieprawidłowa minimalna lub maksymalna wartość rozmiaru puli, minimalny rozmiar puli nie może być większy niż maksymalny rozmiar puli. - - Słowo kluczowe {0} jest przestarzałe. Zamiast tego użyj „{1}”. - Nie można pobrać z serwera adresu koordynatora transakcji rozproszonych (Distributed Transaction Coordinator) dla tego serwera. Czy usługa DTC jest włączona na serwerze? - - Długość wartości parametru połączenia <{0}> przekracza maksymalną dozwoloną długość 65535 znaków. - Nieprawidłowy „Rozmiar pakietu”. Wartość musi być liczbą całkowitą z przedziału od 512 do 32768. Nieprawidłowa transakcja lub nieprawidłowa nazwa dla punktu, w którym następuje zapis stanu transakcji. - - Wartość wyliczenia elementu {0}, wynosząca {1}, nie jest obsługiwana przez program SQL Server 7.0 ani przez program SQL Server 2000. - Wystąpienie użytkownika i tryb failover nie są zgodnymi opcjami. Wybierz tylko jeden z dwóch parametrów połączenia. @@ -2499,33 +540,21 @@ Nie można ustawić właściwości Credential, jeśli w parametrach połączenia ustawiono wartość „Authentication=Active Directory Integrated”. - - Określono nieprawidłowe parametry uwierzytelniania z uwierzytelnianiem certyfikatu. - Wystąpienie programu SQL Server, z którym próbowano się połączyć, wymaga szyfrowania, ale ten komputer go nie obsługuje. Wystąpienie programu SQL Server, z którym próbowano się połączyć, nie obsługuje szyfrowania. - - Wystąpienie programu SQL Server, z którym próbowano nawiązać połączenie, nie obsługuje funkcji CTAIP. - Nieobsługiwana wersja programu SQL Server. Dostawcy danych SqlClient programu .NET Framework można używać tylko z programem SQL Server w wersji 7.0 lub nowszej. Nie można zmienić elementu {0}, gdy operacja asynchroniczna jest w toku. - - 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. - Błąd wystąpienia. @@ -2556,27 +585,18 @@ Kod błędu 0x{0} - - Element ChangePassword wymaga programu SQL Server 9.0 lub nowszej wersji. - Przekierowywanie wystąpienia SSE nie jest obsługiwane dla wystąpień użytkowników nielokalnych. Uwierzytelnianie „{0}” nie jest obsługiwane. - - Metoda uwierzytelniania SQL „{0}” nie jest obsługiwana. - Nie można utworzyć wystąpienia dostawcy uwierzytelniania typu {1} dla elementu {0}. Nie można utworzyć wystąpienia elementu SqlAuthenticationInitializer typu {0}. - - Dostawca „{0}” zgłosił wyjątek podczas inicjowania. - Dostawca „{0}” nie obsługuje uwierzytelniania „{1}”. @@ -2586,24 +606,15 @@ Nie można odnaleźć dostawcy uwierzytelniania dla elementu „{0}”. Zainstaluj pakiet NuGet „Microsoft.Data.SqlClient.Extensions.Azure” (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), aby korzystać z metod uwierzytelniania usługi Active Directory (Entra ID). - - Nie można odczytać sekcji konfiguracji dla dostawców uwierzytelniania. - Parametr „{0}” nie może mieć wartości null ani być pusty. - - Operacja asynchroniczna została już ukończona. - Nie można kontynuować wykonywania polecenia, ponieważ istnieje oczekująca operacja asynchroniczna, która jest już w toku. Do elementu ExecuteXmlReader wysłano nieprawidłowe polecenie. Polecenie musi zwrócić wynik XML. - - Powiadomienia wymagają programu SQL Server 9.0 lub nowszej wersji. - Nieprawidłowy format nazwy trzyczęściowej dla elementu UdtTypeName. @@ -2697,9 +708,6 @@ Nie można zainicjować pakietu SSPI. - - Upłynął limit czasu. Limit czasu upłynął przed ukończeniem operacji lub serwer nie odpowiada. - Upłynął limit czasu połączenia. Limit czasu upłynął na początku fazy przed logowaniem. Może to być spowodowane niewystarczającym czasem podanym dla limitu czasu połączenia. @@ -2745,12 +753,6 @@ Wystąpienie użytkownika było żądane w parametrach połączenia, ale określony serwer nie obsługuje tej opcji. - - Weryfikacja aplikacji certyfikatu serwera nie powiodła się. - - - Określona długość danych {0} przekracza dozwoloną maksymalną długość {1}. - Nieprawidłowa próba odczytu, gdy brak jakichkolwiek danych. @@ -2775,9 +777,6 @@ Długość danych „{0}” ma wartość mniejszą niż 0. - - Nie można wywołać elementu {0}, jeśli element SqlDataRecord jest tylko do odczytu. - Mapowana kolekcja jest używana i w tym momencie nie można uzyskać do niej dostępu; @@ -2847,21 +846,6 @@ Nawiązywanie połączenia kontekstowego przy użyciu Microsoft.Data.SqlClient nie jest obsługiwane. - - Zagnieżdżone elementy TransactionScopes nie są obsługiwane. - - - Nieoczekiwane zdarzenie serwera: {0}. - - - 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}. - - - Zbyt wiele wartości. - Element Stream nie obsługuje zapisywania. @@ -2886,192 +870,36 @@ Null - - Wiadomość - - - Przepełnienie arytmetyczne. - - - Wystąpił błąd dzielenia przez zero. - - - Dane mają wartość null. Tej metody lub właściwości nie można wywołać dla wartości null. - - - Arytmetyka numeryczna powoduje obcięcie. - Przepełnienie elementu SqlDateTime. Musi należeć do zakresu od 1.01.1753 12:00:00 do 31.12.9999 23:59:59. - - Dwa ciągi, które mają zostać złączone, są posortowane w różny sposób. - - - Dwa ciągi, które mają zostać porównane, są posortowane w różny sposób. - - - Nieprawidłowa wartość flagi. - - - Konwersja z elementu SqlDecimal na element Decimal spowodowała przepełnienie. - Przepełnienie konwersji. - - Nieprawidłowy element SqlDateTime. - - - Określono strefę czasową. Element SqlDateTime nie obsługuje stref czasowych. - Nieprawidłowy rozmiar tablicy. Nieprawidłowa skala/precyzja numeryczna. - - Dane wejściowe były w niepoprawnym formacie. - - - Błąd elementu SqlType. - - - W tej wersji elementy SqlBytes i SqlChars nie obsługują długości przekraczającej 2GB. - Wystąpił błąd podczas odczytu. - - Element Stream został zamknięty lub usunięty. - - - Brak buforu. Operacja odczytu lub zapisu nie powiodła się. - - - Nie można ustawić długości na niezerową, ponieważ bieżąca wartość jest równa Null. - - - Bufor jest niewystarczający. Operacja odczytu lub zapisu nie powiodła się. - - - Nie można zapisać danych do przesunięcia niezerowego, ponieważ bieżąca wartość jest równa Null. - - - Nie można zapisać z przesunięcia większego niż bieżąca długość. Spowoduje to pozostawienie niezainicjowanych danych w buforze. - Zwrócone dane mają rozmiar większy niż 2 Gb. Użyj zachowania polecenia SequentialAccess, aby uzyskać wszystkie dane. - - Argument dla elementu GetDayOfWeek musi być liczbą całkowitą z przedziału od 1 do 7. - - - Element SQL Type nie został załadowany z danymi. - - - Element SQL Type został już załadowany z danymi. - - - Nieprawidłowa próba uzyskania dostępu do zamkniętego elementu XmlReader. - - - Nieprawidłowa próba wywołania elementu {0} podjęta, gdy strumień jest zamknięty. - - - Nieprawidłowa próba wywołania elementu {0} podjęta, gdy strumień jest nie do zapisu. - - - Nieprawidłowa próba wywołania elementu {0} podjęta, gdy strumień jest nie do odczytu. - - - Nieprawidłowa próba wywołania elementu {0} podjęta, gdy strumień jest niemożliwy do wyszukiwania. - - - Podklasa nie przesłoniła wymaganej metody. - Nie można utworzyć normalizatora dla „{0}”. Błąd wewnętrzny - - Parametr polecenia musi mieć niezerowy i niepusty tekst polecenia. - - - Tablica parametru MetaData musi mieć długość odpowiadającą argumentowi tablicy ParameterDirection. - - - Błąd mapowania karty danych. - - - Błąd karty danych. - - - Naruszenie współbieżności bazy danych. - Operacja przerwana. Operacja została przerwana z powodu wyjątku (aby uzyskać szczegółowe informacje, zobacz element InnerException). - - Określa, czy operacja Fill wywoła element DataRow.AcceptChanges. - - - Określa, czy operacja Update wywoła element DataRow.AcceptChanges. - - - Określa, czy kontynuować do następnego wiersza danych DataRow, gdy stanem zdarzeń Update – RowUpdating i RowUpdated – jest stan Status UpdateStatus.ErrorsOccurred. - - - Sposób wypełniania elementu DataTable z elementu DataReader. - - - Akcja podejmowana, gdy w elemencie TableMappings brakuje tabeli albo kolumny. - - - Akcja podejmowana, gdy w elemencie DataSet brakuje tabeli albo kolumny. - - - Określa, w jaki sposób mapować tabelę źródłową do tabeli DataSet. - - - Zdarzenie wyzwalane, gdy wystąpi odwracalny błąd podczas operacji Fill. - - - Czy operacja Fill ma zwracać wartości określone dla dostawcy, czy też wspólne wartości CLSCompliant. - - - DataColumn.ColumnName - - - Nazwa kolumny źródłowej – uwzględniana wielkość liter. - - - Liczba elementów w kolekcji - - - Określony obiekt DataColumnMapping. - - - Mapowania poszczególnych kolumn przy dopasowywaniu mapowania tej tabeli. - - - DataTable.TableName - - - Nazwa tabeli źródłowej DataTableMapping. W tej nazwie jest uwzględniana wielkość liter. - - - Liczba elementów w kolekcji - - - Określony obiekt DataTableMapping - Używane podczas aktualizacji na potrzeby usuniętych wierszy w elemencie DataSet. @@ -3090,63 +918,9 @@ Zdarzenie wyzwalane po każdym elemencie DataRow podczas operacji Update. - - Liczba wierszy do przetwarzania partiami przed wykonywaniem względem źródła danych. - - - Połączenie używane, jeśli polecenia Select/Insert/Update/DeleteCommands nie mają jeszcze połączenia. - - - Używane podczas operacji Update dla usuniętych wierszy w elemencie DataTable. - - - Używane podczas operacji Update na potrzeby nowych wierszy w tabeli DataTable. - - - Używane podczas operacji Fill. - - - Używane podczas operacji Update na potrzeby zmodyfikowanych wierszy w tabeli DataTable. - - - Czy operacja Fill ma zwracać wartości określone dla dostawcy, czy też wspólne wartości CLSCompliant. - - - Określa sposób mapowania tabeli źródłowej do elementu DataTable. - - - Sposób generowania poleceń Insert/Update/DeleteCommands, gdy nie zostały ustawione przez użytkownika. - - - Liczba wierszy do przetwarzania partiami przed wykonywaniem względem źródła danych. - - - Parametry połączenia użyte do nawiązania połączenia ze źródłem danych. - - - Nazwa sterownika ODBC do użycia podczas połączenia ze źródłem danych Data Source. - - - Nazwa DSN do użycia podczas połączenia ze źródłem danych Data Source. - - - Gdy ma wartość true, wskazuje, że powinny być używane funkcje zarządzanej obsługi puli połączeń. - - - Plik UDL do użycia podczas połączenia ze źródłem danych Data Source. - - - Określa, które usługi bazy danych OLE DB Services włączyć lub wyłączyć dla dostawcy OleDb Provider. - - - Nazwa dostawcy bazy danych OLE DB do użycia podczas połączenia ze źródłem danych Data Source. - Nazwa aplikacji. - - Gdy ma wartość true, włącza użycie funkcji asynchroniczności w dostawcy danych programu .NET Framework. - Nazwa pliku głównego (wraz z pełną nazwą ścieżki) bazą danych możliwej do dołączenia. @@ -3156,9 +930,6 @@ Gdy ma wartość true, wskazuje, że stan połączenia jest resetowany w przypadku usunięcia z puli. - - Gdy ma wartość true, wskazuje, że połączenie powinno pochodzić z kontekstu programu Sql Server. Dostępne tylko w przypadku uruchamiania w procesie programu Sql Server. - Nazwa rekordu języka programu SQL Server. @@ -3252,54 +1023,9 @@ Określa metodę uwierzytelniania w programie SQL Server. - - Określony certyfikat klienta na potrzeby uwierzytelniania w programie SQL Server. - - - Informacje używane do połączenia ze źródłem danych. - - - Bieżąca wartość limitu czasu, niemożliwa do ustawienia w elemencie ConnectionString. - - - Bieżąca wartość wykazu źródeł danych, „Database=X” w parametrach połączenia. - - - Bieżące źródło danych, „Server=X” w parametrach połączenia. - - - Bieżący sterownik ODBC. - - - Wersja produktu, do której uzyskuje dostęp sterownik ODBC. - - - Informacje używane do połączenia ze źródłem danych. - - - Bieżąca wartość limitu czasu połączenia, „Connect Timeout=X” w elemencie ConnectionString. - - - Bieżąca wartość wykazu źródeł danych, „Initial Catalog=X” w parametrach połączenia. - - - Bieżące źródło danych, „Data Source=X” w parametrach połączenia. - - - Bieżący identyfikator ProgID dostawcy OLE DB, „Provider=X” w parametrach połączenia. - - - Wersja produktu, do której uzyskuje dostęp dostawca OLE DB. - Token dostępu używany na potrzeby uwierzytelniania. - - Stan połączenia, synchroniczny lub asynchroniczny. „Przetwarzanie asynchroniczne=x” w parametrach połączenia. - - - Informacje używane do połączenia na potrzeby replikacji. - Informacje używane do połączenia ze źródłem danych DataSource, na przykład „Data Source=x;Initial Catalog=x;Integrated Security=SSPI”. @@ -3324,9 +1050,6 @@ Gromadzenie danych statystycznych dla tego połączenia. - - Niestandardowi dostawcy magazynu kluczy szyfrowania kolumn. - Identyfikator GUID służący do reprezentowania połączenia fizycznego. @@ -3363,99 +1086,9 @@ Automatyczne umieszczanie w powiadomieniach używane przez program Microsoft SQL Server. - - Sposób automatycznego generowania klauzuli where dla poleceń Update i Delete, gdy nie został określony przez użytkownika. - - - Wskazuje pozycję nazwy wykazu w kwalifikowanej nazwie tabeli w poleceniu tekstowym. - - - Znak oddzielający nazwę wykazu od pozostałej części identyfikatora w poleceniu tekstowym. - - - Znak oddzielający nazwę schematu od pozostałej części identyfikatora w poleceniu tekstowym. - - - Ciąg prefiksu otaczający obiekty sql. - - - Ciąg sufiksu otaczający obiekty sql. - - - Element DataAdapter, dla którego mają być automatycznie generowane polecenia. - - - Użyj schematu z elementu DataTable lub polecenia SelectCommand. - - - Sposób automatycznego generowania klauzuli set dla polecenia Update, gdy nie został określony przez użytkownika. - - - Element DataAdapter, dla którego mają być automatycznie generowane elementy OdbcCommands - - - Znak używany w poleceniu tekstowym jako cudzysłów otwierający dla identyfikatorów, które zawierają znaki specjalne. - - - Znak używany w poleceniu tekstowym jako cudzysłów zamykający dla identyfikatorów, które zawierają znaki specjalne. - - - Element DataAdapter, dla którego mają być automatycznie generowane polecenia OleDbCommands - - - Separator dziesiętny używany w literałach numerycznych. - - - Ciąg prefiksu otaczający obiekty sql - - - Ciąg sufiksu otaczający obiekty sql - Element DataAdapter, dla którego mają być automatycznie generowane elementy SqlCommands - - Separator dziesiętny używany w literałach numerycznych. - - - Znak używany w poleceniu tekstowym jako cudzysłów otwierający dla identyfikatorów, które zawierają znaki specjalne. - - - Znak używany w poleceniu tekstowym jako cudzysłów zamykający dla identyfikatorów, które zawierają znaki specjalne. - - - W przypadku korzystania ze scenariuszy Prepare, FillSchema i CommandBuilder opcję tę należy ustawić tylko dla parametrów numerycznych i dziesiętnych. - - - W przypadku korzystania ze scenariuszy Prepare, FillSchema i CommandBuilder opcję tę należy ustawić tylko dla parametrów numerycznych i dziesiętnych. - - - Macierzysty typ parametru. - - - Macierzysty typ parametru. - - - Nazwa parametru, np. „@p1” - - - Macierzysty typ parametru. - - - Nazwa serwera dla typu. - - - Przesunięcie w typach danych o zmiennej długości. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - Parametr ParameterDirection „{0}” określony dla parametru „{1}” nie jest obsługiwany. Parametry z wartościami przechowywanych w tabeli obsługują tylko parametr ParameterDirection.Input. @@ -3468,9 +1101,6 @@ Określono element TypeName dla parametru „{0}”. Właściwość TypeName może być ustawiona tylko dla parametrów strukturalnych. - - Typ SqlDbType.Structured jest obsługiwany tylko w przypadku typów o wielu wartościach. - Kolumna DateType pola {0} w tabeli schematów ma wartość null. Element DataType musi mieć wartość inną niż null. @@ -3483,18 +1113,9 @@ Liczba pól w rekordzie „{0}” jest niezgodna z liczbą w rekordzie oryginalnym. - - zakres: 0–8000 - napotkano nieoczekiwany błąd dostawcy danych SqlClient. {0} - - Nie można uzyskać informacji Type Info dla elementu {0},{1} - - - Dostawca nie może załadować następującego zestawu: {0}. - Właściwość UdtTypeName musi być ustawiona dla parametrów UDT. @@ -3504,72 +1125,9 @@ Określony typ nie został zarejestrowany na serwerze docelowym {0}. - - Parametry UDT nie są dozwolone w klauzuli where, chyba że są częścią klucza podstawowego. - - - „{0}” jest nieprawidłowym typem zdefiniowanym przez użytkownika, przyczyna: {1}. - - - obsługuje zarówno formaty w pamięci, jak i formaty zdefiniowane przez użytkownika - - - Nie można obsłużyć formatu macierzystego. - - - nie implementuje interfejsu IBinarySerialize - - - nie można serializować - - - brak konstruktorów publicznych - - - nie implementuje interfejsu INullable - - - nie ma konstruktora publicznego - brak atrybutu UDT - - Serializacja bez mapowania nie jest jeszcze obsługiwana. - - - brak metody „public static x Parse(System.Data.SqlTypes.SqlString)” - - - brak metody „public override string ToString()” - - - brak metody „public static x Null { get; }” - - - Format macierzysty nie obsługuje pól (jawnie ani przez inne pole) typu „{0}” - - - Typ nie jest publiczny - - - Natywny układ UDT nie jest układem sekwencyjnym z powodu typu „{0}” - - - Format macierzysty UDT (Native UDT) określa maksymalny rozmiar w bajtach (byte) - - - pole „{0}” jest oznaczone jako nieserializowane - - - Typ pola „{0}” jest oznaczony jako układ jawny, co jest niedozwolone w formacie natywnym - - - Odwołania do zestawów mające wiele wartości muszą mieć niezerowy identyfikator zestawu. - - - Typy strukturalne o wielu wartościach mogą być używane tylko dla parametrów i nie mogą być zagnieżdżane w innych typach. - Nazwa ścieżki jest nieprawidłowa. @@ -3585,9 +1143,6 @@ Błąd podczas próby podwyższenia poziomu transakcji. - - Obiekt zależności używany do obierania powiadomień dotyczących zapytań. - Właściwość wskazująca, czy dana zależność jest nieprawidłowa. @@ -3600,9 +1155,6 @@ Dodawanie polecenia do istniejącego obiektu zależności. - - Polecenie jest już skojarzone z innym obiektem zależności. Nie można zastąpić. - Ten obiekt SqlCommand został już skojarzony z innym obiektem SqlDependency. @@ -3630,12 +1182,6 @@ Określony limit czasu jest nieprawidłowy. Limit czasu nie może być < 0. - - Błąd powiadomienia. Typ={0}, Info={1}, Źródło={2}. - - - Element GetMetaData nie jest prawidłowy dla tego elementu SqlDbType. - Typ dbType {0} jest nieprawidłowy dla tego konstruktora. @@ -4104,9 +1650,6 @@ Błąd wewnętrzny. Zwykły tekst nie może mieć wartości null. - - Nie można zaszyfrować. Szyfrowanie spowodowało {0} B szyfru, który przekracza maksymalny dozwolony limit {1} B. Określona wartość w postaci zwykłego tekstu jest prawdopodobnie za duża (rozmiar zwykłego tekstu: {2} B). - Błąd wewnętrzny. Tekst szyfrowany nie może mieć wartości null. @@ -4224,9 +1767,6 @@ Program SQL Server nie zwrócił informacji o zaświadczaniach. Typ enklawy to „{0}”, a adres URL zaświadczania enklawy to „{1}”. - - Nie można ustanowić bezpiecznego kanału. Komunikat o błędzie: {0} - Błąd wewnętrzny. Argument o wartości null „{0}” określony podczas konstruowania obiektu typu „{1}”. Element „{0}” nie może mieć wartości null. @@ -4242,15 +1782,6 @@ Określa punkt końcowy usługi zaświadczania enklawy, która będzie używana do sprawdzenia, czy enklawa skonfigurowana w wystąpieniu programu SQL Server dla obliczeń w kolumnach bazy danych zaszyfrowanych przy użyciu funkcji Always Encrypted jest prawidłowa i bezpieczna. - - Nie można odczytać sekcji konfiguracji dla dostawców enklawy. Upewnij się, że sekcja jest poprawnie sformatowana w pliku konfiguracji aplikacji. Komunikat o błędzie: {0} - - - Nie można utworzyć wystąpienia dostawcy enklawy typu „{1}” dla nazwy „{0}”. Komunikat o błędzie: {2} - - - Błąd wewnętrzny. Właściwość SqlColumnEncryptionEnchaveProviderName nie może mieć wartości null ani być pusta. - Wystąpił błąd podczas odczytywania zestawu wyników „{0}”. Adres URL zaświadczania nie został określony w parametrach połączenia, ale zapytanie wymaga obliczeń enklawy. Typ enklawy to „{1}”. @@ -4260,9 +1791,6 @@ Błąd wewnętrzny. Typ enklawy odebrany z programu SQL Server ma wartość null lub jest pusty podczas wykonywania zapytania wymagającego obliczeń enklawy. - - Wykonanie zapytania wymaga obliczeń enklawy, ale w konfiguracji aplikacji brakuje sekcji dostawcy enklawy. - Nie znaleziono dostawcy enklawy dla typu enklawy „{0}” i protokołu zaświadczania „{1}”. Określ poprawny protokół zaświadczania w parametrach połączenia. @@ -4320,9 +1848,6 @@ Domyślne ustawienie szyfrowania kolumny dla wszystkich poleceń w ramach połączenia. - - Wymusza szyfrowanie parametru przed wysłaniem poufnych danych na serwer. - Obiekt słownika zawierający nazwy programu SQL Server i ich zaufane ścieżki klucza głównego kolumny. @@ -4332,9 +1857,6 @@ Nawiązywanie połączenia z dublowanym wystąpieniem programu SQL Server z użyciem opcji połączenia ApplicationIntent ReadOnly jest nieobsługiwane. - - Odebrano nieoczekiwane informacje routingu. - Odebrano nieprawidłowe informacje routingu. @@ -4485,9 +2007,6 @@ Nieobsługiwane uwierzytelnianie określone w tym kontekście: {0} - - Upłynął limit czasu uwierzytelniania interaktywnego usługi Active Directory. Użytkownik potrzebował zbyt dużo czasu, aby odpowiedzieć na żądanie uwierzytelnienia. - Nie można użyć opcji „Authentication=Active Directory Interactive”, jeśli właściwość Credential została ustawiona. @@ -4497,9 +2016,6 @@ Identyfikator procesu serwera (SPID) aktywnego połączenia. - - Upłynął limit czasu uwierzytelniania przepływu kodu urządzenia usługi Active Directory. Użytkownik potrzebował zbyt dużo czasu, aby odpowiedzieć na żądanie uwierzytelnienia. - Nie można ustawić właściwości Credential, jeśli w parametrach połączenia określono wartość „Authentication=Active Directory Device Code Flow”. @@ -4533,9 +2049,6 @@ Dla żądanej akcji są obsługiwane tylko tablice jednowymiarowe. - - Nie można usunąć określonego elementu, ponieważ nie znaleziono go w określonej kolekcji. - Wymagana jest liczba nieujemna. @@ -4563,87 +2076,36 @@ Nie można ustawić właściwości AccessTokenCallback, jeśli w parametrach połączenia określono wartość „Authentication=Active Directory Default”. - - {0} zwrócono {1}. - - - Błąd protokołu: odebrana wiadomość zawiera prawidłowy podpis, ale nie została zaszyfrowana, czego wymagają ustawienia obowiązującego poziomu zabezpieczeń. - - - Operacja {0} nie powiodła się z powodu błędu: {1}. - Metoda ta nie jest implementowana przez tę klasę. - - Żądany pakiet zabezpieczeń nie jest obsługiwany. - Nieprawidłowy typ połączenia physicalConnection. Wystąpił wyjątek podczas próby ustawienia przełącznika AppContext „{0}”={1}. - - Typ {0} nie jest obsługiwany na tej platformie. - Tryb niezmienny globalizacji nie jest obsługiwany. - - Nie można uwierzytelnić przy użyciu protokołu Kerberos. Upewnij się, że protokół Kerberos został zainicjowany na kliencie za pomocą funkcji „kinit”, a nazwa główna usługi została zarejestrowana dla serwera Microsoft SQL Server, aby umożliwić uwierzytelnianie protokołu Kerberos. - Słowo kluczowe „Biblioteka sieciowa” nie jest obsługiwane na tej platformie, zamiast tego należy poprzedzić „Źródło danych” żądanym protokołem („tcp:” dla połączenia TCP lub „np:” dla połączenia typu Nazwany potok). Gniazdo nie zgłosiło oczekiwanego elementu „{0}” z kodem błędu „{1}”. - - Serwer próbuje użyć funkcji, która nie jest obsługiwana na tej platformie. - Słowo kluczowe „{0}” nie jest obsługiwane na tej platformie. Aktualnie załadowana biblioteka System.Transactions.dll nie obsługuje transakcji globalnych. - - Odebrano nieobsługiwany token „{0}” podczas odczytywania danych z serwera. - - - „{0}” nie jest obsługiwanym typem uchwytu. - Obiekt SqlFileStream nie jest obsługiwany na tej platformie. Usługa LocalDB nie jest obsługiwana na tej platformie. - - Za mało miejsca w buforze. Wymagane: {0} wartość rzeczywista: {1}. - - - Operacja GSSAPI nie powiodła się. Stan: {0} (Stan pomocniczy: {1}). - - - Operacja GSSAPI nie powiodła się z powodu błędu — {0} ({1}). - - - Brak obsługi powiązania kanałów w systemach operacyjnych innych niż Windows. - - - Nazwa obiektu docelowego nie może być pusta, jeśli zostaną przekazane poświadczenia domyślne. - - - Żądany poziom ochrony nie jest obsługiwany przez aktualnie zainstalowane wdrożenie interfejsu GSSAPI. - - - Implementacja serwera nie jest obsługiwana. - - - Uwierzytelnianie NTLM nie jest możliwe w przypadku poświadczeń domyślnych na tej platformie. - Gdy jest używana przez element DataAdapter.Update, wartość parametru jest zmieniana z DBNull.Value na (Int32)1 lub (Int32)0, jeśli jest niezerowa. @@ -4668,9 +2130,6 @@ Przekroczono maksymalny rozmiar buforu: {0} - - Brak elementu DataColumn {0} dla elementu SourceColumn {2}. - Lista SqlBatchCommand nie została zainicjowana. 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 c9f63ccb4c..5873d61783 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Crescente - Índice {0} inválido para esse {1} com Count={2}. @@ -132,18 +129,12 @@ Outro {1} já contém {0}. - - Este {1} já contém {0}. - {0} só aceita objetos do tipo {1} não nulo. Tentativa de remoção de {0} que não estava contido em {1}. - - {0}.{1} deve ser exclusivo, '{2}' já existe na coleção. - A conexão não foi fechada. {0} @@ -171,12 +162,6 @@ Tentativa inválida de chamar {0} quando o leitor estava fechado. - - Não é possível se inscrever na transação porque a conexão é a conexão primária para uma transação delegada ou promovida. - - - Decrescente - Esperando cadeia de caracteres não vazia para parâmetro '{0}'. @@ -204,9 +189,6 @@ A propriedade ConnectionString não foi inicializada. - - Ocorreu uma Non CLS Exception. - O elemento de segurança fornecido não é um elemento de permissão. @@ -252,9 +234,6 @@ Dados - - StateChange - Atualizar @@ -264,48 +243,12 @@ ConnectionState que indica se a conexão está aberta ou fechada. - - Evento disparado quando o estado da conexão é alterado. - - - Tipo genérico do parâmetro. - - - Parâmetro de entrada, de saída ou bidirecional. - - - uma propriedade da fase de design usada para geração de código com rigidez de tipos. - - - Deslocamento em tipos de dados de comprimento variável. - - - Nome do parâmetro. - - - Tamanho dos tipos de dados de comprimento variável (cadeia de caracteres e matrizes). - - - Quando usado por um DataAdapter.Update, é o nome da coluna de origem usada para localizar o nome de DataSetColumn em ColumnMappings. Serve para copiar um valor entre o parâmetro e uma linha de dados. - - - Quando usado por um DataAdapter.Update (somente UpdateCommand), é a versão do valor DataRow usado para atualizar a fonte de dados. - - - Quando usado por DataAdapter.Update, o valor do parâmetro é alterado de DBNull.Value para (Int32)1 ou (Int32)0 se for não nulo. - - - Valor do parâmetro. - Não é possível criar a coleção '{0}' porque houve falha na execução da consulta SQL. Consulte a exceção interna para obter detalhes. Foram fornecidas mais restrições do que o suportado pelo esquema solicitado ('{0}'). - - '{2}' não é um valor válido para a restrição '{1}' da coleção de esquemas '{0}'. - Coleção solicitada ({0}) não definida. @@ -315,15 +258,6 @@ Esta versão do provedor não dá suporte à coleção solicitada ({0}). - - Falta uma das colunas de tabelas DataSourceInformation necessárias. - - - A tabela DataSourceInformation deve conter exatamente uma linha. - - - Falta uma ou mais colunas necessárias da coleção de restrições. - Há uma restrição sem linha correspondente na coleção de restrições. @@ -342,42 +276,21 @@ Falta a coleção '{0}' no XML de metadados. - - O XML de metadados é inválido. - - - O XML de metadados é inválido. A coleção {0} deve conter uma coluna {1} e ser uma coluna de cadeia de caracteres. - O XML de metadados é inválido. A coluna {1} da coleção {0} deve conter uma cadeia de caracteres não vazia. - - Ação - - - Comportamento - Preenchimento InfoMessage - - Mapeamento - StatementCompleted - - UDT - Notificação - - Esquema - XML @@ -393,9 +306,6 @@ Pooling - - ConnectionString Nomeada - Segurança @@ -408,2073 +318,204 @@ Resiliência da Conexão - - A coleção que retém informações personalizadas sobre o usuário. - - - Indica se cadeias de comparação dentro do DataSet diferenciam maiúsculas de minúsculas. - - - O nome deste DataSet. - - - Indica um "modo de exibição" personalizado dos dados contidos em DataSet. Este modo de exibição permite filtrar e pesquisar os dados, bem como navegar neles. - - - Indica se as regras de restrição devem ser seguidas. - - - Indica que o DataSet tem erros. - - - Indica uma localidade com a qual serão comparadas as cadeias de caracteres do DataSet. - - - Indica o espaço para nome do uri de XML para o elemento raiz apontado por este DataSet. - - - Indica o prefixo do espaço para nome usado neste DataSet. - - - A coleção que retém as relações deste DataSet. - - - A coleção que retém as tabelas deste DataSet. - - - Ocorre quando não é possível mesclar esquemas para duas tabelas com o mesmo nome. - - - Ocorre após a conclusão da Initialization. - - - Representa um cache de dados na memória. - - - Indica se a comparação de cadeias de caracteres dentro da tabela diferencia maiúsculas e minúsculas. - - - Retorna as relações filho desta tabela. - - - A coleção que retém as colunas desta tabela. - - - A coleção que retém as restrições desta tabela. - - - Indica o DataSet a que pertence esta tabela. - - - Este é o DataView padrão da tabela. - - - A expressão usada para calcular o valor de limite de dados desta linha. - - - Retorna se há erros na tabela. - - - Indica uma localidade com a qual serão comparadas as cadeias de caracteres da tabela. - - - Indica o tamanho inicial desta tabela. - - - Indica o espaço para nome do uri de XML para o elemento contido nesta tabela. - - - Indica o prefixo do espaço para nome usado para esta tabela na representação XML. - - - Retorna as relações pai desta tabela. - - - Indica a(s) coluna(s) que representa(m) a chave primária desta tabela. - - - Indica a coleção que retém as linhas de dados desta tabela. - - - Indica o nome usado para pesquisar esta tabela na coleção Tables de um DataSet. - - - Ocorre após a edição com êxito de uma linha da tabela. - - - Ocorre quando a linha está sendo alterada para que o manipulador de eventos possa modificar ou cancelar a alteração. O usuário pode modificar valores da linha e deve gerar uma exceção para cancelar a edição. - - - Ocorre depois que uma linha da tabela é excluída com êxito. - - - Ocorre quando uma linha da tabela é marcada para exclusão. Gere uma exceção para cancelar a exclusão. - - - Ocorre quando um valor é enviado a esta coluna. O usuário pode modificar o valor proposto e gerar uma exceção para cancelar a edição. - - - Ocorre quando um valor é alterado nesta coluna. - - - Ocorre antes da limpeza de todas as linhas da tabela. - - - Ocorre após a limpeza com êxito de todas as linhas da tabela. - - - Ocorre após uma nova DataRow ser instanciada. - - - O nome usado para pesquisar esta relação na coleção Relations de um DataSet. - - - Indica as colunas filho desta relação. - - - Indica as colunas pai desta relação. - - - Indica se as relações são aninhadas. - - - Para exclusões, indica que tipo de cascata deve ocorrer nesta relação. - - - Para atualizações, indica que tipo de cascata deve ocorrer nesta relação. - - - Para aceitar e rejeitar alterações, indica que tipo de cascata deve ocorrer nesta relação. - - - Indica as colunas filho desta restrição. - - - Indica as colunas pai desta restrição. - - - Indica a tabela filho desta restrição. - - - Indica as colunas desta restrição. - - - Indica se esta restrição é uma chave primária. - - - Indica o nome desta restrição. - - - Indica a tabela desta restrição. - - - Indica se valores nulos são permitidos na coluna. - - - Indica se a coluna se incrementa automaticamente com novas linhas adicionadas à tabela. O tipo desta coluna deve ser Int16, Int32 ou Int64. - - - Indica o valor inicial para uma coluna AutoIncrement. - - - Indica o incremento usado por uma coluna AutoIncrement. - - - Indica a legenda da interface de usuário padrão para esta coluna. - - - Indica o nome usado para pesquisar esta coluna na coleção Columns de uma DataTable. - - - Retorna a DataTable à qual esta coluna pertence. - - - Indica o tipo dos dados armazenados na coluna. - - - Indica o valor de coluna padrão usado ao serem adicionadas novas linhas à tabela. - - - Indica o valor calculado por esta coluna para cada linha com base em outras colunas em vez de obter entrada do usuário. - - - Indica como esta coluna é mantida no XML: como um atributo, elemento, nó de conteúdo simples ou nada. - - - Indica o uri de XML para elementos ou atributos armazenados nesta coluna. - - - Indica o Prefix usado para esta DataColumn na representação xml. - - - Indica o índice desta coluna na coleção Columns. - - - Indica se esta coluna permitirá alterações depois que uma linha for adicionada à tabela. - - - Indica se a coluna deve restringir que os valores nas linhas da tabela sejam únicos. - - - Indica o comprimento máximo do valor permitido pela coluna. - - - Indica o DateTimeMode dessa DataColumn. - - - Indica se este DataView e a interface do usuário associada a ele permitem exclusões. - - - Indica se este DataView e a interface do usuário associada a ele permitem edições. - - - Indica se este DataView e a interface do usuário associada a ele permitem a adição de novas linhas. - - - Retorna o número de itens atualmente neste modo de exibição. - - - Isso retorna um ponteiro ao DataViewManager que detém a propriedade deste DataSet (se houver algum). - - - Indica se este modo de exibição está aberto. - - - Indica uma expressão usada para filtrar os dados retornados por este DataView. - - - Indica as versões dos dados retornados por este DataView. - - - Indica os nomes da coluna e a ordem em que os dados são retornados por este DataView. - - - Indica se a classificação padrão deverá ser usada quando a propriedade Sort não estiver definida. - - - Indica a tabela usada por este DataView para obter dados. - - - Indica se os dados retornados por este DataView sofreram alguma alteração. - - - Indica a fonte de dados deste DataViewManager. - - - Indica as configurações de classificação/filtro/estado de qualquer tabela do DataSet correspondente. - - - DataSet não dá suporte para 'union' ou 'list' como simpleType. - - - Sintaxe {0} inválida: falta o atributo necessário '{1}'. - - - Valor '{1}' é inválido para atributo '{0}'. - - - Valor do atributo '{0}' deve ser '{1}' ou '{2}'. - - - Não é possível encontrar ElementType name='{0}'. - - - Nome da tabela pai ausente na relação '{0}'. - - - Nome da tabela filho ausente na relação '{0}'. - - - Chave da tabela pai ausente na relação '{0}'. - - - Chave de tabela filho ausente na relação '{0}'. - - - Tipo de dados não definido: '{0}'. - - - Tipo de dados não definido. - - - Seleção XPath inválida dentro do nó do campo. Não é possível encontrar: {0}. - - - Seleção XPath inválida dentro do nó do seletor: {0}. - - - Nó 'Key' inválido dentro da restrição chamada: {0}. - - - O nome de restrição {0} já está em uso no esquema. - - - Não é possível converter '{0}' no tipo '{1}'. - - - Falta a parte '{0}' na restrição '{1}' denominada '{2}'. - - - Definição de Relation inválida: chaves de comprimento diferentes. - - - DataSet não permite referência circular no ComplexType chamado '{0}'. - - - DataSet não pode criar uma instância de um ComplexType abstrato para o nó {0}. - - - Erro com o conversor de vários destinos ao gravar um Xml Schema. Consulte a exceção interna para obter detalhes. - - - Erro com o conversor de vários destinos ao gravar um Xml Schema. Uma cadeia de caracteres null ou vazia foi retornada. - - - Declaração duplicada '{0}'. - - - Não é possível carregar diffGram. Tabela '{0}' inexistente no DataSet de destino. - - - Não é possível carregar diffGram. Falta o nó 'sql'. - - - Nome de coluna '{0}' foi definido para diferentes tipos de mapeamento. - - - Prefixo '{0}' não é válido porque contém caracteres especiais. - - - Referência circular em tabela aninhada em si mesma '{0}'. - - - DataSet não pode expandir entidades. Use XmlValidatingReader e defina a propriedade EntityHandling da mesma maneira. - - - O tipo '{0}' não implementa a interface IXmlSerializable e, portanto, não pode continuar com a serialização. - - - Não é possível continuar com a desserialização. Os dados não implementam IXMLSerializable e, portanto, não há suporte para polimorfismo. - - - DataTable não oferece suporte a inferência de esquema de Xml. - - - Não é possível continuar com a serialização de DataTable '{0}'. Ele contém um DataRow com várias linhas pai na mesma Chave Estrangeira. - - - O atributo IsDataSet é inexistente no Schema de entrada. - - - Não é possível determinar o Elemento DataSet. O atributo IsDataSet existe mais de uma vez. - - - DataSet não irá serializar tipos que implementam IDynamicMetaObjectProvider mas também não implementam IXmlSerializable. - - - Recurso não implementado. {0}. - - - Erro de sintaxe: operando ausente depois do operador '{0}'. - - - Tipo incompatível na expressão '{0}'. - - - Expressão muito complexa. - - - Não é possível encontrar a coluna [{0}]. - - - A expressão contém uma constante de cadeia de caracteres inválida: {0}. - - - A expressão contém chamada de função indefinida {0}(). - - - Erro de sintaxe na expressão. - - - Número de argumentos inválido: função {0}(). - - - Falta o parêntese de fechamento na expressão. - - - Não é possível interpretar símbolo '{0}' na posição {1}. - - - {0} era esperado, mas o símbolo existente na posição {2} é {1}. - - - Não é possível converter de {0} para {1}. - - - Não é possível converter o valor ''{0}'' no Tipo: {1}. - - - Nome de coluna inválido [{0}]. - - - A expressão contém constante de data inválida '{0}'. - - - Apenas expressões constantes são permitidas na lista de expressões para o operador IN. - - - Erro no operador Like: o padrão da cadeia de caracteres '{0}' é inválido. - - - Erro de sintaxe: os itens seguintes à palavra-chave IN devem ser separados por vírgulas e devem ser delimitados por parênteses. - - - Tipo incompatível em argumento de função: {0}(), argumento {1}, era esperado {2}. - - - Tipo incompatível em argumento de função: {0}(), argumento {1}, era esperado um dos tipos inteiros. - - - Não é possível realizar a operação '{0}' em {1} e {2}. - - - Operador '{0}' é ambíguo nos operandos do tipo '{1}' e '{2}'. Não é possível misturar tipos assinados e não assinados. Use a função Convert() explícita. - - - Erro de sintaxe: a palavra-chave IN deve ser seguida por uma lista não vazia de expressões separadas por vírgulas e deve ser delimitada por parênteses. - - - A expressão contém operador sem suporte '{0}'. - - - A expressão contém nome inválido: '{0}'. - - - Erro de sintaxe: operando ausente antes do operador '{0}'. - - - A expressão tem muitos parênteses de fechamento excedentes. - - - A tabela [{0}] está envolvida em mais de uma relação. Mencione explicitamente um nome de relação na expressão '{1}'. - - - Erro de sintaxe em argumento agregado: era esperado um argumento de uma única coluna com possível qualificador 'Child'. - - - Referência não associada na expressão agregada '{0}'. - - - Não é possível avaliar uma expressão não constante sem linha atual. - - - Referência não associada na expressão '{0}'. - - - Não é possível avaliar. Expressão '{0}' não é agregada. - - - A expressão de filtro ''{0}'' não é avaliada como um termo Booliano. - - - Nome de tipo inválido '{0}'. - - - Erro de sintaxe na expressão Lookup: é esperada a palavra-chave 'Parent' seguida de um argumento de coluna única com possível qualificador de relação: Parent[(<nome_relação>)].<nome_relação>. - - - É necessária uma linha ou uma tabela para invocar DataFilter. - - - Argumento {0}() está fora do intervalo. - - - Erro de sintaxe: uso inválido do operador 'Is'. Sintaxe correta: <expression> Is [Not] Null. + + O deslocamento e o comprimento estavam fora dos limites para a matriz ou a contagem é maior do que o número de elementos do índice até o fim da coleção de origem. - - Valor muito grande ou muito pequeno para Type '{0}'. + + Não é possível converter o objeto do tipo '{0}' em objeto do tipo '{1}'. - - Erro de divisão por zero. + + O único valor aceitável para a propriedade '{0}' é '{1}'. - - Não é possível localizar a relação pai '{0}'. + + Os valores aceitáveis para a propriedade '{0}' são '{1}' ou '{2}'. - - O argumento 'hours' está fora do intervalo. O valor deve estar entre -14 e +14. + + Valores de QuotePrefix e QuoteSuffix especificados não coincidem. - - O argumento 'minutes' está fora do intervalo. O valor deve estar entre -59 e +59. + + O comprimento do argumento '{0}' excede seu limite de '{1}'. - - O intervalo fornecido para a hora um excede o total de 14 horas. + + Valor inválido para a chave '{0}'. - - A propriedade Kind do argumento DateTime fornecido não corresponde aos argumentos 'hours' e 'minutes'. + + O uso da chave '{0}' requer a presença da chave '{1}'. - - Uma DataColumn de tipo '{0}' não dá suporte à expressão. + + O comprimento do valor para a chave '{0}' excede seu limite de '{1}'. - - Falha ao ativar restrições. Uma ou mais linhas contêm valores que violam as restrições non-null, unique ou foreign-key. + + Palavra-chave não suportada: '{0}'. - - A coleção não é modificável. + + Erro interno do Provedor de Dados .NET Framework {0}. - - O nome especificado '{0}' corresponde a pelo menos dois nomes no objeto da coleção com maiúsculas e minúsculas diferentes, mas não há uma correspondência com coincidência de maiúsculas e minúsculas. + + {0} "{1}". - - O nome fornecido '{0}' coincide com ao menos dois nomes no objeto de coleção com espaços para nome diferentes. + + {0} "{1}", uso incorreto de aspas. - - O deslocamento e o comprimento estavam fora dos limites para a matriz ou a contagem é maior do que o número de elementos do índice até o fim da coleção de origem. + + {0} "{1}", o limite atual de "{2}" é insuficiente. - - Argumento '{0}' está fora do intervalo. + + O SqlBulkCopy.WriteToServer falhou, pois o SqlBulkCopy.DestinationTableName é um nome composto inválido - - Argumento '{0}' não pode ser nulo. + + O processamento de resultados do SQL Server falhou devido a um nome composto inválido - - Argumento '{0}' contém valor nulo. + + SqlParameter.UdtTypeName é um nome composto inválido - - Não é possível encontrar a coluna {0}. + + O SqlParameter.TypeName é um nome com várias partes inválido - - A coluna '{0}' já pertence a esta DataTable. + + O SqlCommand.DeriveParameters falhou, pois o valor da propriedade SqlCommand.CommandText é um nome composto inválido - - A coluna '{0}' já pertence a outra DataTable. + + Não há suporte para a conexão de uma instância espelhada do SQL Server usando a opção de conexão MultiSubnetFailover. - - Não é possível haver mais de uma coluna SimpleContent em uma DataTable. + + O método {2} não dá suporte ao valor de enumeração de {0}, {1}. - - Não é possível adicionar uma coluna SimpleContent a uma tabela que contém colunas de elementos ou relações aninhadas. + + O Provedor de Dados .NET Framework para SqlClient não dá suporte ao valor de enumeração {0}, {1}. - - Uma coluna chamada '{0}' já pertence a esta DataTable. + + {0}: propriedade CommandText não foi inicializada - - Não é possível adicionar uma coluna chamada '{0}': uma tabela aninhada com o mesmo nome já pertence a esta DataTable. + + {0}: propriedade Connection não foi inicializada. - - Uma coluna chamada '{0}' já pertence a esta DataTable: não é possível definir um nome de tabela aninhada como o mesmo nome. + + {0} requer uma Connection aberta e disponível. {1} - - Não é possível remover uma coluna que não pertence a esta tabela. + + O procedimento armazenado '{0}' não existe. - - Não é possível remover esta coluna porque ela é parte da chave primária. + + A transação não está associada à conexão atual ou já foi concluída. - - Não é possível remover esta coluna porque ela é parte da chave pai da relação {0}. + + A transação associada à conexão atual foi concluída, mas não foi descartada. A transação deve ser descartada antes do uso da conexão para executar as instruções SQL. - - Não é possível remover esta coluna porque ela é parte da restrição {0} na tabela {1}. + + {0} requer que o comando tenha uma transação quando a conexão atribuída ao comando estiver em uma transação local pendente. A propriedade Transaction do comando não foi inicializada. - - Não é possível remover esta coluna porque ela é parte de uma expressão: {0} = {1}. + + Já existe um DataReader aberto associado a este {0}, que precisa ser fechado primeiro. - - Não é possível definir a propriedade AutoIncrement para uma coluna calculada. + + {0} DeriveParameters só dá suporte para CommandType.StoredProcedure, e não para CommandType.{1}. - - Não é possível definir a propriedade AutoIncrement para uma coluna com DefaultValue definido. - - - Não é possível definir DefaultValue em uma coluna AutoIncrement. - - - AutoIncrementStep deve ser um valor não nulo. - - - ColumnName é necessário quando ele é parte de um DataTable. - - - Não será possível alterar o DataType de uma coluna com dados. - - - A coluna requer DataType válido. - - - DefaultValue da coluna {0} é do tipo {1} e não pode ser convertido em {2}. - - - DefaultValue da coluna é do tipo {0} e não pode ser convertido em {1}. - - - DefaultValue da coluna {0} é do tipo {1}, mas a coluna é do tipo {2}. - - - Não é possível alterar a propriedade ReadOnly para a coluna de expressões. - - - Não é possível alterar a propriedade Unique da coluna de expressões. - - - Não é possível criar uma expressão em uma coluna que tenha AutoIncrement ou Unique. - - - Não é possível definir a expressão porque a coluna não pode se tornar ReadOnly. - - - Não é possível definir a propriedade Expression na coluna {0} porque ela é parte de uma restrição. - - - Não é possível criar uma restrição com base na coluna Expression {0}. - - - Não é possível definir a propriedade Expression devido à referência circular na expressão. - - - A coluna '{0}' tem valores nulls. - - - Column '{0}' não permite valores nulls. - - - Coluna '{0}' é somente leitura. - - - A Column '{0}' contém valores não exclusivos. - - - A coluna '{0}' não pertence à tabela {1}. - - - A coluna deve pertencer a uma tabela. - - - Não foi possível armazenar <{0}> na coluna {1}. O tipo esperado é {2}. - - - Não é possível definir a Column '{0}' como null. Em seu lugar, use DBNull. - - - Não é possível definir a coluna '{0}'. O valor viola o limite de MaxLength desta coluna. - - - MaxLength aplica-se apenas a tipo de dados de cadeia de caracteres. Não é possível definir a propriedade MaxLength da Column '{0}' como um número não negativo. - - - Não é possível definir a propriedade MaxLength da Column '{0}' como '{1}'. Há pelo menos uma cadeia de caracteres da tabela maior do que o novo limite. - - - Não é possível definir a propriedade MaxLength da Column '{0}'. A Column é SimpleContent. - - - Não é possível definir a propriedade DataType da Column '{0}' como {1}. A Column é SimpleContent. - - - Não é possível definir a propriedade MappingType da Column '{0}' como SimpleContent. DataType de Column coluna é {1}. - - - A Column '{0}' excede o limite de MaxLength. - - - A coluna '{0}' não permite DBNull.Value. - - - Não é possível alterar a propriedade Namespace da coluna '{0}'. Column é SimpleContent. - - - Não é possível alterar AutoIncrement de um DataColumn com tipo '{0}' quando ele já tem dados. - - - A coluna '{0}' não pertence à tabela '{1}' subjacente. - - - DataColumn com tipo '{0}' é um complexType. Não é possível serializar valor de tipo complexo como Attribute - - - DateTimeMode somente pode ser definido em DataColumns do tipo DateTime. - - - '{0}' é um valor de DataSetDateTime inválido. - - - Não é possível alterar DateTimeMode de '{0}' para '{1}' quando a tabela tem dados. - - - Type '{0}' não contém campo ou propriedade Null static. - - - Tipo '{0}' não implementa IRevertibleChangeTracking; por isso não pode proceder com RejectChanges(). - - - SetAdded e SetModified somente podem ser chamados em DataRows com Unchanged DataRowState. - - - O ordinal ''{0}'' excede o número máximo. - - - DataSet não dá suporte a System.Nullable<>. - - - Não é possível trocar o nome de uma restrição por uma cadeia de caracteres vazia quando ela estiver na ConstraintCollection. - - - Não é possível aplicar restrições na restrição {0}. - - - A coluna '{0}' está restrita a conter valores exclusivos. O valor '{1}' já existe. - - - A restrição '{0}' não pertence a esta DataTable. - - - Não é possível encontrar a restrição {0}. - - - Restrição corresponde à restrição chamada {0} que já existe na coleção. - - - Uma Constraint nomeada '{0}' já pertence a esta DataTable. - - - Estas colunas não têm valores exclusivos no momento. - - - Estas colunas não apontam para esta tabela. - - - Esta restrição não pode ser ativada porque nem todos os valores possuem valores pai correspondentes. - - - Esta restrição não pode ser adicionada porque ForeignKey não pertence à tabela {0}. - - - Não é possível remover uma restrição que não pertence a esta tabela. - - - Não é possível remover restrição exclusiva '{0}'. Remova primeiro a restrição de chave estrangeira '{1}'. - - - Não é possível excluir esta linha porque as restrições são aplicadas na relação {0}. A exclusão desta linha deixará as linhas filho desamparadas. - - - Não é possível fazer esta alteração porque as restrições são aplicadas na relação {0}. A alteração deste valor deixará as linhas filho desamparadas. - - - Não é possível limpar a tabela {0} porque ForeignKeyConstraint {1} aplica restrições e existem linhas filho em {2}. - - - ForeignKeyConstraint {0} exige que valores de chave filho ({1}) existam na tabela pai. - - - Propriedade não acessível porque '{0}'. - - - Não é possível remover esta linha porque ela possui linhas filho, e as restrições na relação {0} são aplicadas. - - - Não é possível adicionar restrição de chave primária porque já foi definida uma chave primária para a tabela. - - - Não é possível adicionar restrição a DataTable '{0}', que é tabela filha em duas relações aninhadas. - - - Não é possível criar uma chave de colunas que pertençam a tabelas diferentes. - - - Não é possível ter 0 coluna. - - - Não é possível ter mais de {0} colunas. - - - Não é possível criar uma chave quando a mesma coluna estiver listada mais de uma vez: '{0}' - - - Não é possível remover restrição exclusiva porque ela é a chave primária de uma tabela. - - - Não é possível remover restrição exclusiva porque ela é a chave primária da tabela {0}. - - - As colunas pai e filho não têm colunas de tipo correspondente. - - - ParentKey e ChildKey são idênticos. - - - ParentColumns e ChildColumns devem ter o mesmo comprimento. - - - ParentColumns e ChildColumns devem ter comprimento zero. - - - A linha não pertence ao mesmo DataSet que esta relação. - - - RelationName é necessário quando ele é parte de um DataSet. - - - GetChildRows requer uma linha cuja Table seja {0}, mas a Table da linha especificada é {1}. - - - Esta relação deve conectar duas tabelas neste DataSet a serem adicionadas a este DataSet. - - - GetParentRow requer uma linha da tabela {0}, mas a tabela da linha especificada é {1}. - - - SetParentRow requer uma linha filho da tabela {0}, mas a tabela da linha especificada é {1}. - - - Não é possível haver uma relação entre tabelas de DataSets diferentes. - - - Não é possível criar uma relação entre tabelas de DataSets diferentes. - - - Já existe uma relação para essas colunas filho. - - - Esta relação não pertence a esta coleção de relações. - - - Esta relação já pertence a outro DataSet. - - - Esta relação já pertence a este DataSet. - - - Este DataSet já possui uma Relation nomeada '{0}'. - - - Relation {0} não pertence a este DataSet. - - - Não é possível encontrar a relação {0}. - - - Não é possível criar uma coleção em uma tabela null. - - - A tabela cujas relações são exibidas nesta coleção foi removida de seu DataSet. - - - Não é possível adicionar uma relação à coleção ParentRelation desta tabela, onde a tabela não é a tabela filho. - - - Não é possível adicionar uma relação à coleção ChildRelation desta tabela, onde a tabela não é a tabela pai. - - - Não é possível definir a propriedade 'Nested' como false para esta relação. - - - A mesma tabela '{0}' não pode ser tabela filha em duas relações aninhadas. - - - A tabela ({0}) não pode ser a sua própria tabela filho em relações aninhadas. - - - Não é possível adicionar DataRelation ou Constraint que têm configurações Locale ou CaseSensitive diferentes entre suas tabelas pai e filho. - - - Não será possível criar DataRelation se a coluna Parent ou Child não estiver em DataSet. - - - Tabela aninhada '{0}' com espaço para nome herdado não pode ter várias tabelas pais em espaços para nome diferentes. - - - Tabela aninhada '{0}' com espaço para nome vazio não pode ter várias tabelas pais em espaços para nome diferentes. - - - A linha não pertence ao mesmo DataSet que esta relação. - - - Não é possível realizar esta operação em uma linha que não esteja na tabela. - - - Esta relação e esta linha filho não pertencem ao mesmo DataSet. - - - Não é possível alterar um valor proposto no evento RowChanging. - - - Não é possível chamar EndEdit() dentro de um evento OnRowChanging. - - - Não é possível chamar BeginEdit() dentro do evento RowChanging. - - - Não é possível chamar CancelEdit() dentro de um evento OnRowChanging. Gere uma exception para cancelar esta atualização. - - - Não é possível chamar Delete dentro de um evento OnRowDeleting. Gere uma exception para cancelar a exclusão. - - - A matriz de entrada é maior do que o número de colunas desta tabela. - - - Não existem dados atuais a serem acessados. - - - Não existem dados originais a serem acessados. - - - Não existem dados propostos a serem acessados. - - - Esta linha foi removida de uma tabela e não possui dados. BeginEdit() permitirá a criação de dados novos nesta linha. - - - Informações sobre a linha excluída não podem ser acessadas através da linha. - - - A versão deve ser original, atual ou proposta. - - - Não há linha na posição {0}. - - - Ponto de inserção {0} da linha é inválido. - - - Valor={0} de rowOrder foi encontrado duas vezes para a tabela chamada '{1}'. - - - Faltam valores na sequência rowOrder da tabela '{0}'. - - - A DataRow especificada não está na DataRowCollection atual. - - - Esta linha já pertence a outra tabela. - - - Esta linha já pertence a esta tabela. - - - Não é possível excluir esta linha porque ela já foi excluída. - - - Esta linha está vazia. - - - Não é possível remover uma linha que já foi removida. - - - Uma linha filho tem várias linhas pai. - - - Padrão de bits de estado de linha não reconhecido. - - - Não é possível alterar o nome do DataSet por uma cadeia de caracteres vazia. - - - O nome '{0}' é inválido. Um DataSet não pode ter o mesmo nome da DataTable. - - - O espaço para nome do esquema é inválido. Em seu lugar, use o seguinte: {0}. - - - Não é possível alterar a propriedade CaseSensitive ou Locale. Essa alteração levaria pelo menos uma DataRelation ou Constraint a ter configurações Locale ou CaseSensitive diferentes entre suas tabelas relacionadas. - - - A propriedade SchemaSerializationMode somente pode ser definida se for substituída por DataSet derivado. - - - Colunas PrimaryKey não pertencem a esta tabela. - - - Não é possível adicionar uma relação aninhada ou uma coluna de elementos a uma tabela que contém uma coluna SimpleContent. - - - TableName é necessário quando ele é parte de um DataSet. - - - DataTable já tem uma coluna de conteúdo simples. - - - Tabela não tem uma chave primária. - - - {0} não é uma entrada de cadeia de caracteres de Sort válida. - - - Não é possível serializar a DataTable. Uma DataTable usada em uma ou mais expressões de DataColumn não descende de DataTable atual. - - - Essa DataTable somente pode ser remota como parte de DataSet. Uma ou mais colunas de expressão fazem referência a outra DataTable(s). - - - Não é permitido valor de propriedade de formato remoto diferente para DataSet e DataTable. - - - Não é possível serializar a DataTable. Nome de DataTable não definido. - - - Uma DataTable chamada '{0}' já pertence a este DataSet. - - - Este DataSet já possui uma DataTable nomeada '{0}' com o mesmo Namespace '{1}'. - - - A tabela ({0}) não pode ser a tabela filho dela mesma em uma relação aninhada: o nome do DataSet está em conflito com o nome da tabela. - - - O nome '{0}' é inválido. Uma DataTable não pode ter o mesmo nome do DataSet. - - - DataTable já pertence a outro DataSet. - - - DataTable já pertence a este DataSet. - - - Tabela {0} não pertence a este DataSet. - - - Não é possível encontrar a tabela {0}. - - - Não é possível remover uma tabela que possui relações existentes. Remova as relações primeiro. - - - Não é possível remover a tabela {0}, porque ForeignKeyConstraint {1} faz referência a ela. Remova a restrição primeiro. - - - DataTable '{0}' não corresponde a qualquer DataTable na origem. - - - DataSet de destino não tem definição para {0}. - - - DataSet de destino inexistente {0} {1}. - - - <target>.{0} e <source>.{0} têm propriedades conflitantes: propriedade DataType incompatível. - - - <target>.PrimaryKey e <source>.PrimaryKey têm Length diferente. - - - Colunas incompatíveis em PrimaryKey: <target>.{0} versus <source>.{1}. - - - A relação {0} não pode ser mesclada, pois as chaves têm colunas incompatíveis. - - - Tabela de destino {0} não tem definição para a coluna {1}. - - - Coluna PrimaryKey {0} não existe na tabela de origem. - - - O parâmetro RowStates deve ser definido com uma combinação válida de valores da enumeração DataViewRowState. - - - Find encontra uma fila com base em uma ordem de classificação, mas nenhuma ordem de classificação foi especificada. - - - Esperando {0} valor(es) para a chave que está sendo indexada, mas foi(foram) recebido(s) {1} valor(es). - - - Uso inválido da função de agregação {0}() e tipo: {1}. - - - Tipo de armazenamento inválido: {0}. - - - A persistência DataSet Xml não dá suporte para o valor '{0}' como valor Char. Em seu lugar, use armazenamento em Byte. - - - Tipo de valor incompatível com tipo de coluna - - - Tipo '{0}' não implementa interface IComparable. Não é possível fazer comparação. - - - Não é possível definir {0}. - - - Não é possível alterar DataSet em um DataViewManager que já é o modo de exibição padrão de um DataSet. - - - RowStateFilter não pode mostrar ModifiedOriginals e ModifiedCurrents ao mesmo tempo. - - - Não é possível alterar a propriedade Table em um DefaultView ou um DataView oriundo de um DataViewManager. - - - Não é possível alterar a propriedade DataSet depois que ela é definida. - - - DataSet deve ser definido antes de usar DataViewManager. - - - Não é possível alterar a propriedade Table depois que ela é definida. - - - DataTable deve ser definida antes de usar DataView. - - - Não é possível associar a uma DataTable sem nome. - - - Não é possível definir um objeto nesta lista. - - - Não é possível chamar AddNew em um DataView em que AllowNew é false. - - - DataView não está aberto. - - - A relação não é pai da tabela para a qual este DataView aponta. - - - Não é possível excluir em uma DataSource em que AllowDelete é false. - - - Não é possível editar em uma DataSource em que AllowEdit é false. - - - O índice {0} é negativo ou está acima da contagem de linhas. - - - Não é possível adicionar objetos externos a esta lista. - - - Não é possível limpar a lista. - - - Não é possível inserir objetos externos na lista. - - - Não é possível remover objetos que não estejam na lista. - - - {0} não é uma DataColumn nem uma DataRelation da tabela {1}. - - - Min ({0}) deve ser menor ou igual ao max ({1}) em um objeto Range. - - - Este é um intervalo null. - - - MinimumCapacity deve ser não negativo. - - - Não é possível gerar identificador para o nome '{0}'. - - - Existe mais de uma tabela com o mesmo nome '{0}' (mesmo se o espaço para nome for diferente). - - - Coluna '{0}': tipo '{1}' não pode ser nulo. - - - Coluna '{0}': tipo'{1}' não tem construtor sem parâmetros. - - - Coluna '{0}': tipo '{1}' não tem construtor com argumento de cadeia de caracteres. - - - Não é possível converter o objeto do tipo '{0}' em objeto do tipo '{1}'. - - - Data Exception. - - - Constraint Exception. - - - Linhas excluídas inacessíveis. - - - Nome duplicado não permitido. - - - Operação não tem suporte no evento RowChanging. - - - Restrição inválida. - - - Chave primária ausente. - - - Null não permitido. - - - Coluna marcada como somente leitura. - - - Linha não localizada na tabela. - - - Versão não encontrada. - - - Dados ReadOnly modificados. - - - DataTableReader inválido para DataTable '{0}' atual. - - - Esquema de DataTable '{0}' atual em DataTableReader alterado, DataTableReader é inválido. - - - DataTableReader não pode ser criado. Não há DataTable em DataSet. - - - Não é possível criar DataTableReader. Argumento vazio. - - - Não é possível criar DataTableReader. Argumentos contêm valor null. - - - DataRow atual em estado Deleted ou Detached. - - - DataTable '{0}' atual está vazia. Não há DataRow em DataTable. - - - O índice interno DataTable está corrompido: '{0}'. - - - A coleção foi modificada. Talvez a operação de enumeração não seja executada. - - - O tipo simples ''{0}'' já foi declarado com ''{1}'' diferente. - - - Foliação inválida. - - - Não será possível alterar o nome da tabela depois que o DataSet associado for mapeado para um documento XML carregado. - - - Não será possível alterar o espaço para nome da tabela depois que o DataSet associado for mapeado para um documento XML carregado. - - - Não será possível alterar o nome da coluna depois que o DataSet associado for mapeado para um documento XML carregado. - - - Não será possível alterar o espaço para nome da coluna depois que o DataSet associado for mapeado para um documento XML carregado. - - - Não será possível alterar a propriedade ColumnMapping depois que o DataSet associado for mapeado para um documento XML carregado. - - - Não será possível adicionar ou remover colunas da tabela depois que o DataSet for mapeado para um documento XML carregado. - - - Não será possível alterar o nome do DataSet depois que ele for mapeado para um documento XML carregado. - - - Não será possível alterar o nome do DataSet depois que ele for mapeado para um documento XML carregado. - - - O parâmetro DataSet é inválido. Ele não pode ser null. - - - Não será possível alterar o nome do DataSet depois que ele for mapeado para um documento XML carregado. - - - O tipo do nó não pode ser clonado: {0}. - - - Não é possível carregar XmlDataDocument caso ele já contenha dados. Use um novo XmlDataDocument. - - - DataSet pode ser associado somente a um XmlDataDocument. Não é possível associar DataSet ao XmlDataDocument atual porque o DataSet já está associado a outro XmlDataDocument. - - - Defina DataSet.EnforceConstraints == false antes de tentar editar XmlDataDocument usando operações XML. - - - Não há suporte para GetElementById() em DataDocument. - - - Não é possível criar referências de entidade em DataDocument. - - - A função Clear em DateSet e DataTable não oferece suporte em XmlDataDocument. - - - Não é possível remover coluna porque ela é interna a este dataSet. - - - Não é possível remover a relação porque ela é interna a esse conjunto de dados. - - - Ocorre sempre que uma propriedade deste controle é alterada. - - - Ocorre sempre que os membros desta coleção são alterados. - - - Não é possível obter o valor porque ele é DBNull. - - - A propriedade '{0}' requer um sistema operacional com base em Microsoft WindowsNT ou em WindowsNT. - - - Não foi possível localizar o Provedor de Dados .NET Framework solicitado. Talvez ele não esteja instalado. - - - A implementação do Provedor de Dados .NET Framework solicitada não tem um campo de Instância de um tipo derivado de System.Data.Common.DbProviderFactory. - - - Falha ao localizar ou carregar o Provedor de Dados .NET Framework registrado. - - - O nome qualificado pelo assembly do Provedor de Dados .NET Framework ausente é obrigatório. - - - Somente elementos permitidos. - - - Não são permitidos nós filhos. - - - Atributo '{0}' não reconhecido. - - - Elemento não reconhecido. - - - A seção '{0}' pode aparecer apenas uma vez por arquivo config. - - - Atributo '{0}' necessário não encontrado. - - - O atributo solicitado '{0}' não pode ser vazio. - - - Esperando matriz não vazia para o parâmetro '{0}'. - - - O único valor aceitável para a propriedade '{0}' é '{1}'. - - - Os valores aceitáveis para a propriedade '{0}' são '{1}' ou '{2}'. - - - Valores de QuotePrefix e QuoteSuffix especificados não coincidem. - - - O comprimento do argumento '{0}' excede seu limite de '{1}'. - - - Esperado o tipo de argumento {1}, mas recebido o tipo {0}. - - - Valor inválido para a chave '{0}'. - - - O uso da chave '{0}' requer a presença da chave '{1}'. - - - O comprimento do valor para a chave '{0}' excede seu limite de '{1}'. - - - Palavra-chave não suportada: '{0}'. - - - Não é possível carregar o arquivo UDL. - - - Arquivo UDL inválido. - - - Erro interno do Provedor de Dados .NET Framework {0}. - - - As propriedades QuotePrefix e QuoteSuffix não podem ser alteradas depois que um comando Insert, Update ou Delete é gerado. - - - É necessário inicializar a propriedade DataAdapter.SelectCommand. - - - É necessário inicializar a propriedade DataAdapter.SelectCommand.Connection; - - - {0} "{1}". - - - {0} "{1}", uso incorreto de aspas. - - - {0} "{1}", o limite atual de "{2}" é insuficiente. - - - O SqlBulkCopy.WriteToServer falhou, pois o SqlBulkCopy.DestinationTableName é um nome composto inválido - - - O processamento de resultados do SQL Server falhou devido a um nome composto inválido - - - SqlParameter.UdtTypeName é um nome composto inválido - - - O SqlParameter.TypeName é um nome com várias partes inválido - - - O SqlCommand.DeriveParameters falhou, pois o valor da propriedade SqlCommand.CommandText é um nome composto inválido - - - O OdbcCommandBuilder.DeriveParameters falhou, pois o valor da propriedade OdbcCommand.CommandText é um nome composto inválido - - - O OleDbCommandBuilder.DeriveParameters falhou, pois o valor da propriedade OleDbCommandBuilder.CommandText é um nome composto inválido - - - Não há suporte para a conexão de uma instância espelhada do SQL Server usando a opção de conexão MultiSubnetFailover. - - - Falha no mapeamento de coluna de SourceColumn '{0}' porque DataColumn '{1}' é uma coluna calculada. - - - Tipos não conversíveis incompatíveis entre SourceColumn '{0}' de {1} e DataColumn '{2}' de {3}. - - - DataColumn '{0}' em DataTable '{1}' de SourceColumn '{2}' inexistente. - - - É necessário que SourceColumn seja uma cadeia de caracteres não vazia. - - - Mapeamento de SourceColumn inexistente para '{0}'. - - - O método {2} não dá suporte ao valor de enumeração de {0}, {1}. - - - O Provedor de Dados .NET Framework para ODBC não dá suporte ao valor de enumeração {0}, {1}. - - - O Provedor de Dados .NET Framework para OLEDB não dá suporte ao valor de enumeração {0}, {1}. - - - O Provedor de Dados .NET Framework para SqlClient não dá suporte ao valor de enumeração {0}, {1}. - - - Não foi possível recuperar ComputerNameDnsFullyQualified, {0}. - - - DataTable '{0}' inexistente para SourceTable '{1}'. - - - É necessário que SourceTable seja uma cadeia de caracteres não vazia - - - Mapeamento de SourceTable inexistente: '{0}' - - - {0}: propriedade CommandText não foi inicializada - - - {0}: propriedade Connection não foi inicializada. - - - {0} requer uma Connection aberta e disponível. {1} - - - Fill: Propriedade SelectCommand.Connection não foi inicializada. - - - FillPage: Propriedade SelectCommand.Connection não foi inicializada. - - - FillSchema: Propriedade SelectCommand.Connection não foi inicializada. - - - Atualização requer que InsertCommand tenha um objeto de conexão. A propriedade Connection de InsertCommand não foi inicializada. - - - A atualização exige que UpdateCommand tenha um objeto de conexão. A propriedade Connection de UpdateCommand não foi inicializada. - - - A atualização exige que DeleteCommand tenha um objeto de conexão. A propriedade Connection de DeleteCommand não foi inicializada. - - - A atualização exige um objeto de conexão. A propriedade Connection não foi inicializada. - - - A atualização exige que o clone de comando tenha um objeto de conexão. A propriedade Connection do clone de comando não foi inicializada. - - - A atualização exige conexão. - - - Update exige que o {0}Command tenha um objeto de conexão aberta. {1} - - - Update exige que o {0}Command tenha um objeto de conexão aberta. {1} - - - Update exige que o {0}Command tenha um objeto de conexão aberta. {1} - - - Update exige que o comando de atualização tenha um objeto de conexão aberta. {1} - - - O procedimento armazenado '{0}' não existe. - - - A transação atribuída a este comando deve ser a transação local pendente mais aninhada. - - - A transação não está associada à conexão atual ou já foi concluída. - - - A transação associada à conexão atual foi concluída, mas não foi descartada. A transação deve ser descartada antes do uso da conexão para executar as instruções SQL. - - - {0} requer que o comando tenha uma transação quando a conexão atribuída ao comando estiver em uma transação local pendente. A propriedade Transaction do comando não foi inicializada. - - - Já existe um SqlResultSet aberto associado a este comando que deve ser fechado primeiro. - - - Já existe um DataReader aberto associado a este {0}, que precisa ser fechado primeiro. - - - {0} DeriveParameters só dá suporte para CommandType.StoredProcedure, e não para CommandType.{1}. - - - O método '{0}' não pode ser chamado mais de uma vez para a mesma execução. - - - Resultado de assinc incorreto. - - - A propriedade SelectCommand não foi inicializada antes de chamar '{0}'. - - - StatementType {0} não é esperado aqui. - - - FillSchema: era esperada uma cadeia de caracteres não vazia para o nome de SourceTable. - - - O valor de MaxRecords de {0} é inválido; o valor deve ser >= 0. - - - O valor de StartRecord de {0} é inválido; o valor deve ser >= 0. - - - Fill: era esperada uma cadeia de caracteres não vazia para o nome de SourceTable. - - - Colunas de capítulo hierárquico devem mapear para AutoIncrement DataColumn. - - - DataReader.GetFieldType({0}) retornou null. - - - Especifique apenas um item na matriz dataTables ao usar valores diferentes de zero para startRecords ou maxRecords. - - - Update não pode localizar TableMapping['{0}'] ou DataTable '{0}'. - - - Update: era esperado um nome de SourceTable não vazio. - - - TableMapping inexistente quando TableMapping.DataSetTable='{0}'. - - - Update exige um clone de comando válido. - - - A geração SQL automática durante o Update requer um SelectCommand válido. - - - Update exige um InsertCommand válido para transmitir coleção DataRow com novas linhas. - - - A atualização requer um UpdateCommand válido para transmitir coleção DataRow com linhas modificadas. - - - Update exige um DeleteCommand válido para transmitir coleção DataRow com linhas excluídas. - - - DataRow[{0}] e DataRow[0] são de DataTable diferentes. - - - RowUpdatedEvent: ocorreram erros; nenhuma informação adicional disponível. - - - RowUpdatingEvent: ocorreram erros; nenhuma informação adicional disponível. - - - No loteamento, o valor de propriedade do comando UpdatedRowSource de UpdateRowSource.FirstReturnedRecord ou UpdateRowSource.Both é inválido. - - - Violação de concorrência: UpdateCommand afetou {0} dos registros {1} esperados. - - - Violação de concorrência: DeleteCommand afetou {0} dos registros {1} esperados. - - - Violação de concorrência: o comando loteado afetou {0} dos {1} registros esperados. + + O método '{0}' não pode ser chamado mais de uma vez para a mesma execução. Valor {0} de CommandTimeout inválido; o valor deve ser >= 0. - - {1}[{0}]: a propriedade Size tem um tamanho inválido de 0. - - - O método {0}.Prepare exige que todos os parâmetros tenham um tipo explicitamente definido. - - - O método {0}.Prepare exige que todos os parâmetros de comprimento variável tenham um valor Size não nulo explicitamente definido. - - - O método {0}.Prepare exige que parâmetros do tipo '{1}' tenham Precision e Scale explicitamente definidos. - - - Chamada de método final para asyncResult incompatível. Era esperada camada de {0}, mas {1} foi chamado. - - - Operação inválida. A conexão está fechada. - - - A conexão foi desabilitada. - - - Não é possível se inscrever na transação, pois uma transação local está em andamento na conexão. Conclua a transação local e tente novamente. - - - A conexão atualmente tem transação inscrita. Conclua a transação atual e tente novamente. - - - Banco de dados não pode ser nulo, cadeia de caracteres vazia ou cadeia de caracteres contendo apenas espaços em branco. - - - O argumento é muito longo. - - - Valor 'Connect Timeout' inválido; o valor deve ser um número inteiro >= 0. - - - Deslocamento de buffer de origem inválido (tamanho de {0}): {1} - - - Deslocamento de buffer de destino inválido (tamanho de {0}): {1} - - - Valor numérico muito grande para um decimal de 96 bits. - - - Tentativa inválida de {0} quando o fluxo estava fechado. - - - Valor de SeekOrigin especificado é inválido. - - - Não há suporte para geração SQL dinâmica em várias tabelas base. - - - Não há suporte para geração SQL dinâmica em SelectCommand que não retorna informações sobre uma tabela base. - - - Não há suporte para geração SQL dinâmica para DeleteCommand em SelectCommand que não retorna informações importantes de coluna. - - - Não há suporte para geração SQL dinâmica para UpdateCommand em SelectCommand que não retorne informações importantes de coluna. - - - Não há suporte para geração SQL dinâmica para DeleteCommand em SelectCommand que não contenha uma coluna de versão de linha. - - - Não há suporte para geração SQL dinâmica para UpdateCommand em SelectCommand que não contenha uma coluna de versão de linha. - - - Não há suporte para geração SQL dinâmica em nomes de tabela '{0}' que contenham o caractere QuotePrefix ou QuoteSuffix '{1}'. - - - Tentativa inválida de leitura do ordinal de coluna '{0}'. Com CommandBehavior.SequentialAccess, só é possível ler do ordinal de coluna '{1}' ou maior. - - - Tipo de dados '{0}' não pode ser formatado como literal pois tem dígitos de data e hora inválidos. - - - O valor não pode ser formatado como um literal do tipo requerido. - - - Tipo de dados '{0}' não pode ser formatado como literal pois tem escalação máxima inválida. - - - O valor literal fornecido não é um literal válido para o tipo de dados '{0}'. - - - '{0}':O comprimento do valor literal deve ser uniforme. - - - '{0}':O valor literal deve ser uma cadeia de caracteres com dígitos hexadecimais - - - {0} requer conexão aberta quando o prefixo de aspas não tiver sido definido. - - - Não é possível determinar os valores literais Boolean. Não é possível criar literais Boolean. - - - Não há suporte para literais de tipo de dados nativo associados ao tipo de dados '{0}'. - - - Tipo de dados de parâmetro de {0} é inválido. - - - Não existe mapeamento do tipo de objeto {0} para um provedor gerenciado de tipo nativo. - - - Não é possível tratar um TypeCode {0} desconhecido retornado pelo tipo {1}. - - - Não existe mapeamento de DbType {0} para um {1} conhecido. - - - A versão do SQL Server em uso não dá suporte ao tipo de dados '{0}'. - - - Valor de parâmetro '{0}' está fora do intervalo. - - - Nome de parâmetro especificado '{0}' inválido. - - - Não há suporte para vários parâmetros de valores de retorno. - - - Valor '{0}' do parâmetro Size inválido. O valor deve ser maior ou igual a 0. - - - Valor inválido para argumento '{0}'. O valor deve ser maior ou igual a 0. - - - Valor inválido para este metadado. - - - Metadados devem ser SqlDbType.Row - - - Falha ao converter valor de parâmetro de {0} em {1}. - - - {0} não dá suporte para transações paralelas. - - - Este {0} foi concluído; ele não pode mais ser usado. - - - '{0}' não pode ser chamado quando o registro é somente leitura. - - - '{0}' não pode ser chamado quando o DbDataRecord é somente leitura. - - - Não há suporte para conversão implícita de tipo de objeto '{0}' para tipo de dados '{1}'. - - - O valor de deslocamento de buffer '{1}' mais os bytes disponíveis '{0}' é maior que o tamanho passado no buffer. - - - O comprimento de dados '{0}' é menor que 0. - - - O comprimento especificado '{0}' está fora do intervalo. - - - Tentativa de {2} inválida em dataIndex '{0}'. Com CommandBehavior.SequentialAccess, só é possível ler de dataIndex '{1}' ou maior. - - - Deslocamento deve fazer referência a um local dentro do valor. - - - "O provedor gerenciado ODBC requer que a restrição TABLE_NAME seja especificada e não nula para a coleção de índices GetSchema. - - - Valor de argumento inválido para o método '{0}'. - - - O provedor ODBC não retornou resultados de SQLGETTYPEINFO. - - - Argumento DataTable null inesperado - - - Argumento DataSet null inesperado. - - - Cadeia de conexão excede o comprimento máximo permitido de {0}. - - - {0} - não é possível mapear tipo. - - - Tipo SQL desconhecido - {0}. - - - Tipo URT desconhecido - {0}. - - - Argumento negativo inválido! - - - Não é possível definir propriedade em uma conexão aberta. - - - Não há mapeamento válido para uma SQL_TRANSACTION '{0}' para um valor de enumeração System.Data.IsolationLevel. - - - {0} - não é possível ativar pool de conexão... - - - {0} - não é possível alocar um identificador de ambiente. - - - {0} - não é possível obter o identificador do descritor. - - - Fora de uma transação - - - Valor de enumeração OdbcType inválido={0}. - - - Use o IsDBNull quando os dados DBNull.Value forem esperados. - - - {0} [{1}] {2} - - - {0} - nenhuma informação de erro disponível - - - A conexão está fechada. - - - Uma conexão interna não tem um proprietário. - - - O Provedor de Dados .NET Framework para ODBC exige os MDAC (Microsoft Data Access Components) versão 2.6 ou posterior. A versão {0} foi encontrada instalada no momento. - - - O Provedor de Dados .NET Framework para OLEDB exige os MDAC (Microsoft Data Access Components) versão 2.6 ou posterior. A versão {0} foi encontrada instalada no momento. - - - O provedor '{1}' não dá suporte para a interface '{0}'. GetOleDbSchemaTable não está disponível no provedor atual. - - - '{0}' falhou sem nenhuma mensagem de erro disponível. Código do resultado: {1}. - - - Nenhuma mensagem de erro disponível, código de resultado: {0}. - - - Os Provedores de Dados .NET Framework exigem os MDAC (Microsoft Data Access Components). Instale os MDAC (Microsoft Data Access Components) versão 2.6 ou posterior. - - - O Provedor de Dados .NET Framework para OLEDB (Microsoft.Data.OleDb) não dá suporte ao MSDASQL (Provedor OLE DB da Microsoft para Drivers ODBC). Use o Provedor de Dados .NET Framework para ODBC (System.Data.Odbc). - - - O Provedor de Dados .NET Framework para OLEDB não permitirá que o Provedor OLE DB faça solicitações ao usuário em um ambiente não interativo. - - - O provedor '{0}' não está registrado na máquina local. - - - O provedor '{0}' não dá suporte para a interface ICommandText. Em seu lugar, use CommandType.TableDirect. - - - O provedor '{0}' não dá suporte para a interface ITransactionLocal. Transações locais não disponíveis com o provedor atual. - - - Formato da cadeia de inicialização não está de acordo com a especificação de OLE DB. Iniciando próximo ao char[{0}] na cadeia de conexão. - - - Não há suporte para o recurso 'Processamento Assíncrono' do Provedor de Dados .NET Framework para OLE DB (Microsoft.Data.OleDb). - - - Provedor OLE DB não foi especificado em ConnectionString. Por exemplo, 'Provider=SQLOLEDB;'. - - - O provedor OLE DB especificado em ConnectionString é muito longo. - - - Não são esperadas restrições para o DbInfoKeywords OleDbSchemaGuid. - - - Não são esperadas restrições para DbInfoLiterals OleDbSchemaGuid. - - - Não são esperadas restrições para o guid de esquemas OleDbSchemaGuid. - - - O provedor '{1}' não dá suporte para o esquema {0} OleDbSchemaGuid. - - - A definição de configuração '{0}' tem o número errado de valores. - - - Não é possível carregar o arquivo XML especificado na definição de configuração '{0}'. - - - Parâmetro de comando [{0}] '{1}' inválido. - - - Valor dos dados do parâmetro de comando [{0}] '{1}' não pôde ser convertido por razões diferentes de sinais incompatíveis ou estouro de dados. - - - Falha na conversão do parâmetro de comando [{0}] '{1}' porque o valor dos dados tinha sinal e o tipo usado pelo provedor era sem sinal. - - - Falha na conversão do parâmetro de comando [{0}] '{1}' porque o valor dos dados estourou o tipo usado pelo provedor. - - - O provedor encontrou um erro ao enviar valor do parâmetro de comando [{0}] '{1}' e interrompeu o processamento. - - - Parâmetro [{0}] '{1}' não tem valor padrão. - - - Erro com o parâmetro[{0}]: {1}. - - - Erro interno do Microsoft.Data.OleDb.OleDbDataAdapter: acessador de parâmetro inválido: {0} {1}. - - - Parameter[{0}]: a propriedade OleDbType não foi inicializada: OleDbType.{1}. - - - O provedor '{0}' não dá suporte para a interface ICommandWithParameters. O provedor atual não dá suporte para os parâmetros de comando. - - - O provedor '{0}' não dá suporte para recuperação de informações sobre parâmetro de procedimento. - - - Não é possível determinar o separador decimal do servidor. Não é possível criar literais numéricos não inteiros. - - - Objeto não é um ADODB.RecordSet ou um ADODB.Record. - - - Não é possível recuperar a interface '{0}' do objeto ADODB.RecordSet. + + {1}[{0}]: a propriedade Size tem um tamanho inválido de 0. - - Não é possível recuperar a interface IRow do objeto ADODB.Record. + + O método {0}.Prepare exige que todos os parâmetros tenham um tipo explicitamente definido. - - O tipo não dá suporte para o ISourcesRowset da interface OLE DB + + O método {0}.Prepare exige que todos os parâmetros de comprimento variável tenham um valor Size não nulo explicitamente definido. - - Não é possível construir a coleção de esquemas ReservedWords, pois o servidor não dá suporte para IDBInfo. + + O método {0}.Prepare exige que parâmetros do tipo '{1}' tenham Precision e Scale explicitamente definidos. - - O valor da propriedade não foi definido porque o provedor não dá suporte à propriedade '{0}', ou o consumidor tentou obter ou definir valores de propriedades fora do grupo de propriedades Initialization e o objeto de fonte de dados não foi inicializado. + + Chamada de método final para asyncResult incompatível. Era esperada camada de {0}, mas {1} foi chamado. - - Falha ao inicializar a propriedade '{0}' por uma das seguintes razões: - O tipo de dados de valor não é o tipo de dados da propriedade ou não é nulo. Por exemplo, a propriedade é DBPROP_MEMORYUSAGE, cujo tipo de dados é Int32, e o tipo de dados é Int64. - O valor é inválido. Por exemplo, a propriedade é DBPROP_MEMORYUSAGE e o valor é negativo. - O valor é válido para a propriedade e o provedor dá suporte à propriedade como propriedade que pode ser definida, mas ele não dá suporte ao valor especificado. Isso inclui o caso em que o valor foi adicionado à propriedade em OLE DB após o provedor ter sido gravado. + + Operação inválida. A conexão está fechada. - - O valor Options era inválido. + + Não é possível se inscrever na transação, pois uma transação local está em andamento na conexão. Conclua a transação local e tente novamente. - - O elemento ColumnID não era válido. + + A conexão atualmente tem transação inscrita. Conclua a transação atual e tente novamente. - - Uma propriedade '{0}' foi especificada para ser aplicada a todas as colunas, mas pode ser que isso não ocorra a uma ou mais delas. + + Banco de dados não pode ser nulo, cadeia de caracteres vazia ou cadeia de caracteres contendo apenas espaços em branco. - - A propriedade '{0}' é somente leitura ou o consumidor tentou definir valores de propriedades no grupo da propriedade Initialization após a inicialização do objeto de fonte de dados. Consumidores podem definir o valor de uma propriedade somente leitura para o valor atual. Esse status será retornado também se uma propriedade de coluna não puder ser definida para a coluna particular. + + Deslocamento de buffer de origem inválido (tamanho de {0}): {1} - - O valor da propriedade '{0}' opcional não foi definido para o valor especificado e a configuração da propriedade para o valor especificado não foi possível. + + Deslocamento de buffer de destino inválido (tamanho de {0}): {1} - - O valor da propriedade '{0}' não foi definido pois isso geraria conflito com uma propriedade existente. + + Tentativa inválida de {0} quando o fluxo estava fechado. - - (Reservado). + + Valor de SeekOrigin especificado é inválido. - - O provedor retornou um valor DBPROPSTATUS_ desconhecido '{0}'. + + Tentativa inválida de leitura do ordinal de coluna '{0}'. Com CommandBehavior.SequentialAccess, só é possível ler do ordinal de coluna '{1}' ou maior. - - Validação do acessador foi adiada e executada enquanto o método retornava dados. Associação inválida para esta coluna ou este parâmetro. + + Tipo de dados de parâmetro de {0} é inválido. - - Erro interno de OleDbDataAdapter: acessador de definição de linha inválido: Ordinal={0} Status={1}. + + Não existe mapeamento do tipo de objeto {0} para um provedor gerenciado de tipo nativo. - - O valor dos dados não pôde ser convertido por razões diferentes de sinais incompatíveis ou estouro de dados. Por exemplo, os dados estavam corrompidos no repositório de dados, mas a linha ainda era recuperável. + + Não é possível tratar um TypeCode {0} desconhecido retornado pelo tipo {1}. - - O provedor não pôde alocar memória para retornar dados {0}. + + Não existe mapeamento de DbType {0} para um {1} conhecido. - - Falha na conversão porque o valor de dados {0} estourou o tipo especificado para a parte de valor {0} no buffer do consumidor. + + A versão do SQL Server em uso não dá suporte ao tipo de dados '{0}'. - - Erro interno de OleDbDataAdapter: [get] tipo de dados OLE DB desconhecido: 0x{0} ({1}). + + Valor de parâmetro '{0}' está fora do intervalo. - - Falha na conversão porque o valor dos dados {0} tinha sinal e o tipo especificado para a parte de valor {0} no buffer do consumidor era sem sinal. + + Nome de parâmetro especificado '{0}' inválido. - - Erro interno de OleDbDataAdapter: [set] Tipo de dados OLE DB desconhecido: 0x{0} ({1}). + + Valor '{0}' do parâmetro Size inválido. O valor deve ser maior ou igual a 0. - - O provedor não pôde determinar o valor {0}. Por exemplo, a linha acabou de ser criada, o padrão para a coluna {0} não estava disponível, e o consumido ainda não definiu um novo valor {0}. + + Valor inválido para argumento '{0}'. O valor deve ser maior ou igual a 0. - - Provedor OLE DB retornou um valor de status inesperado de {0}. + + Valor inválido para este metadado. - - O OleDbDataReader.Read deve ser usado a partir do mesmo thread em que foi criado caso ApartmentState desse thread não seja ApartmentState.MTA. + + Falha ao converter valor de parâmetro de {0} em {1}. - - Erro não especificado: {0} + + {0} não dá suporte para transações paralelas. - - Falha de IErrorInfo.GetDescription com {0}. + + Este {0} foi concluído; ele não pode mais ser usado. - - Falha de IErrorInfo.GetSource com {0}. + + O comprimento especificado '{0}' está fora do intervalo. - - Não há suporte para os dados DBTYPE_VECTOR no Provedor de Dados .NET Framework para OLE DB (Microsoft.Data.OleDb). + + Tentativa de {2} inválida em dataIndex '{0}'. Com CommandBehavior.SequentialAccess, só é possível ler de dataIndex '{1}' ou maior. Valores inválidos de tamanho mínimo e máximo de pool. O tamanho mínimo do pool não pode ser maior do que o tamanho máximo do pool. - - Palavra-chave '{0}' obsoleta. Em seu lugar, use '{1}'. - Não é possível obter o endereço do coordenador de transações distribuídas para o servidor, a partir do servidor. O DTC está habilitado no servidor? - - O comprimento do valor do parâmetro de conexão <{0}> excede o máximo permitido de 65535 caracteres. - 'Packet Size' inválido. O valor deve ser um inteiro >= 512 e <= 32768. Transação inválida ou nome inválido de um ponto da transação para executar salvamento. - - O SQL Server 7.0 ou SQL Server 2000 não dá suporte para o valor de enumeração {0}, {1}. - User Instance e Failover não são opções compatíveis. Escolha apenas uma das duas cadeias de conexão. @@ -2499,33 +540,21 @@ Não é possível definir a propriedade Credential quando 'Authentication=Active Directory Integrated' está especificado na cadeia de conexão. - - Parâmetros de autenticação incorretos especificados com a autenticação de certificado. - A instância do SQL Server à qual você tentou se conectar requer criptografia, porém não há suporte para isso neste computador. A instância do SQL Server à qual você tentou se conectar não dá suporte para criptografia. - - A instância do SQL Server à qual você tentou se conectar não dá suporte ao CTAIP. - Versão do SQL Server sem suporte. O Provedor de Dados .NET Framework para SqlClient pode ser usado somente com as versões 7.0 e posteriores do SQL Server. {0} não pode ser alterado enquanto a operação async está em andamento. - - 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. - Falha da instância. @@ -2556,27 +585,18 @@ Código de erro 0x{0} - - ChangePassword requer SQL Server 9.0 ou posterior. - Não há suporte para redirecionamento da Instância SSE para instâncias de usuários que não sejam locais. A autenticação '{0}' não tem suporte. - - Não há suporte para o método de autenticação SQL '{0}'. - Falha ao criar uma instância de um provedor de autenticação com o tipo '{1}' para '{0}'. Falha ao criar um SqlAuthenticationInitializer com o tipo '{0}'. - - O provedor '{0}' gerou uma exceção ao inicializar. - O provedor '{0}' não dá suporte à autenticação '{1}'. @@ -2586,24 +606,15 @@ Não é possível localizar um provedor de autenticação para '{0}'. Instale o pacote NuGet 'Microsoft.Data.SqlClient.Extensions.Azure' (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) para usar métodos de autenticação do Active Directory (Entra ID). - - Falha ao ler a seção de configuração para os provedores de autenticação. - O parâmetro '{0}' não pode ser nulo ou vazio. - - A operação assíncrona já foi concluída. - A execução do comando não pode prosseguir devido a uma operação assíncrona pendente que já está em andamento. Comando inválido enviado a ExecuteXmlReader. O comando deve retornar um resultado Xml. - - Notificações requerem SQL Server 9.0 ou posterior. - Formato de nome em 3 partes inválido para UdtTypeName. @@ -2697,9 +708,6 @@ Não é possível inicializar pacote SSPI. - - Timeout expirado. O timeout esgotou antes da conclusão da operação ou o servidor não está respondendo. - Tempo Limite de Conexão Expirado. O tempo limite decorrido no início da fase pré-logon. Talvez isso ocorra devido ao tempo insuficiente especificado como tempo limite de conexão. @@ -2745,12 +753,6 @@ Uma instância do usuário foi solicitada na cadeia de conexão, mas o servidor especificado não oferece suporte para esta opção. - - Falha na validação do certificado do servidor no aplicativo. - - - O comprimento dos dados especificados {0} excede o comprimento máximo de {1}. - Tentativa inválida durante a leitura quando não há dados presentes. @@ -2775,9 +777,6 @@ O comprimento de dados '{0}' é menor que 0. - - '{0}' não pode ser chamado quando o SqlDataRecord é somente leitura. - A coleção mapeada está sendo usada e não pode ser acessada desta vez; @@ -2847,21 +846,6 @@ Não há suporte para a conexão de contexto usando Microsoft.Data.SqlClient. - - Não há suporte para TransactionScopes aninhados. - - - Evento de servidor inesperado: {0}. - - - O comprimento de '{0}' deve corresponder ao comprimento de '{1}'. - - - A SqlDbType '{0}' é inválida para {1}. Há suporte apenas para {2}. - - - Excesso de valores. - O Stream não dá suporte para gravação. @@ -2886,192 +870,36 @@ Nulo - - Mensagem - - - Estouro Aritmético. - - - Erro de divisão por zero. - - - Dados são Null. Não é possível chamar este método ou esta propriedade em valores Null. - - - Aritmética numérica causa truncamento. - Estouro de SqlDateTime. Deve estar entre 1/1/1753 12:00:00 AM e 31/12/9999 11:59:59 PM. - - Duas cadeias de caracteres a serem concatenadas têm intercalação diferente. - - - Duas cadeias de caracteres a serem comparadas têm intercalação diferente. - - - Valor de sinalizador inválido. - - - Estouro na conversão de SqlDecimal em Decimal. - Estouros de conversão. - - SqlDateTime inválido. - - - Um fuso horário foi especificado. SqlDateTime não oferece suporte a fusos horários. - Tamanho de matriz inválido. Precisão ou escala numérica inválida. - - A entrada não estava em um formato correto. - - - Erro de SqlType. - - - SqlBytes e SqlChars não oferecem suporte a mais de 2GB nesta versão. - Erro durante a leitura. - - O Stream foi fechado ou descartado. - - - Não há buffer. Falha na operação de leitura ou gravação. - - - Não é possível definir o tamanho como não nulo, pois o valor atual é Null. - - - Buffer insuficiente. Falha na operação de leitura ou gravação. - - - Não é possível gravar em deslocamento não nulo, pois o valor atual é Null. - - - Não é possível gravar um deslocamento que seja maior que o tamanho atual. Dados não inicializados seriam deixados no buffer. - O volume dos dados retornados são maiores do que 2Gb. Use SequentialAccess command behavior para obter todos os dados. - - O argumento para GetDayOfWeek deve ser um número inteiro entre 1 e 7. - - - SQL Type não foi carregado com dados. - - - SQL Type já foi carregado com dados. - - - Tentativa inválida de acessar um XmlReader fechado. - - - Tentativa inválida de chamar {0} quando o fluxo está fechado. - - - Tentativa inválida de chamar {0} quando o fluxo não pode ser gravado. - - - Tentativa inválida de chamar {0} quando o fluxo está ilegível. - - - Tentativa inválida de chamar {0} quando o fluxo não pode ser buscado. - - - A subclasse não substituiu um método necessário. - Não é possível criar o normalizador para '{0}'. Erro Interno - - O parâmetro de comando deve ter um texto de comando não nulo e não vazio. - - - A matriz do parâmetro MetaData deve ter o comprimento equivalente ao argumento da matriz ParameterDirection. - - - Erro de mapeamento do adaptador de dados. - - - Erro de adaptador de dados. - - - Violação de concorrência DB. - Operação anulada. Operação anulada devido a uma exceção (consulte InnerException para obter detalhes). - - Se Fill chamará ou não DataRow.AcceptChanges. - - - Se Update chamará ou não DataRow.AcceptChanges. - - - Indica se deve ou não continuar com a DataRow seguinte quando o Status dos eventos Update (RowUpdating e RowUpdated) for UpdateStatus.ErrorsOccurred. - - - Como o adaptador preenche DataTable de DataReader. - - - A ação tomada quando uma tabela ou coluna de TableMappings estiver ausente. - - - A ação tomada quando uma tabela ou coluna de DataSet estiver ausente. - - - Como mapear a tabela de origem para a tabela de DataSet. - - - Evento disparado quando um erro recuperável ocorre durante Fill. - - - Caso Fill retorne valores específicos do provedor ou valores de CLSCompliant comuns. - - - DataColumn.ColumnName - - - Nome da coluna de origem - diferencia maiúsculas de minúsculas. - - - Número de itens na coleção - - - O objeto DataColumnMapping especificado. - - - Mapeamento de colunas individuais quando há correspondência do mapeamento da coluna. - - - DataTable.TableName - - - O nome da tabela de origem DataTableMapping. Este nome diferencia maiúsculas e minúsculas. - - - Número de itens na coleção - - - O objeto DataTableMapping especificado - Usado durante a atualização de linhas excluídas no DataSet. @@ -3090,63 +918,9 @@ Evento disparado depois de cada DataRow durante a atualização. - - Número de linhas a serem juntadas em lote antes da execução na fonte de dados. - - - Conexão usada em Select/Insert/Update/DeleteCommands ainda não tem uma conexão. - - - Usado durante Update de linhas excluídas no DataTable. - - - Usado durante Update de novas linhas no DataTable. - - - Usado durante Fill. - - - Usado durante Update de linhas modificadas no DataTable. - - - Caso Fill retorne valores específicos do provedor ou valores de CLSCompliant comuns. - - - Como mapear a tabela de origem para DataTable. - - - Como os comandos Insert/Update/DeleteCommands são gerados quando o usuário não os define. - - - Número de linhas a serem juntadas em lote antes da execução na fonte de dados. - - - A cadeia de conexão usada para conectar à Data Source. - - - O nome do driver ODBC a ser usado na conexão com o Data Source. - - - O DSN usado para conectar ao Data Source. - - - Se for true, indica que pool de conexão gerenciado deve ser usado. - - - O arquivo UDL usado para conectar ao Data Source. - - - Especifica que OLE DB Services deve ser habilitado ou desabilitado com o OleDb Provider. - - - O nome do Provedor OLE DB a ser usado na conexão com o Data Source. - O nome do aplicativo. - - Quando é true, permite o uso da funcionalidade Assíncrona no Provedor de Dados .NET Framework. - O nome do arquivo primário, incluindo o nome do caminho completo, de um banco de dados anexável. @@ -3156,9 +930,6 @@ Se for true, indica que o estado da conexão foi redefinido ao ser removido do pool. - - Se for true, indica que a conexão deve ser do contexto do Sql Server. Disponível somente durante execução no processo do Sql Server. - O nome de registro do SQL Server Language. @@ -3252,54 +1023,9 @@ Especifica o método de autenticação no SQL Server. - - Certificado do cliente especificado para autenticação no SQL Server. - - - Informações usadas para conectar a uma Fonte de Dados. - - - Valor atual de tempo limite da conexão não pode ser definido em ConnectionString. - - - Valor de catálogo da fonte de dados atual, 'Database=X' na cadeia de conexão. - - - Fonte de dados atual, 'Server=X' na cadeia de conexão. - - - Driver ODBC atual. - - - Versão do produto acessado pelo driver ODBC. - - - Informações usadas para conectar a uma Fonte de Dados. - - - Valor de tempo limite da conexão atual, 'Connect Timeout=X' em ConnectionString. - - - Valor de catálogo da fonte de dados atual, 'Initial Catalog=X' na cadeia de conexão. - - - Fonte de dados atual, 'Data Source=X' na cadeia de conexão. - - - ProgID do provedor OLE DB atual, 'Provider=X' na cadeia de conexão. - - - Versão do produto acessado pelo provedor OLE DB. - Token de acesso para usar para autenticação. - - Estado de conexão, síncrono ou assíncrono. ' Asynchronous Processing=x ' na cadeia de conexão. - - - Informações usadas para estabelecer conexão para replicação. - Informações usadas para conectar-se a DataSource, como 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'. @@ -3324,9 +1050,6 @@ Colete estatística para esta conexão. - - Provedores de armazenamento de chaves de criptografia de coluna personalizada. - Uma guid para representar a conexão física. @@ -3363,99 +1086,9 @@ Inscrição automática em notificações usadas pelo Microsoft SQL Server. - - Como a cláusula where é gerada automaticamente para os comandos Update e Delete quando não especificada pelo usuário. - - - Indica a posição do nome do catálogo em um nome de tabela qualificado em um comando de texto. - - - O caractere que separa o nome do catálogo do resto do identificador em um comando de texto. - - - O caractere que separa o nome do esquema do resto do identificador em um comando de texto. - - - Cadeia de caracteres de prefixo encapsulada com objetos sql. - - - Cadeia de caracteres de sufixo encapsulada com objetos sql. - - - O DataAdapter para o qual gerar comandos automaticamente. - - - Use esquema de DataTable ou SelectCommand. - - - Como a cláusula set é gerada automaticamente para o comando Update quando não especificada pelo usuário. - - - O DataAdapter para o qual OdbcCommands serão gerados automaticamente - - - O caractere usado em um comando de texto como aspas de abertura para delimitar identificadores que contêm caracteres especiais. - - - O caractere usado em um comando de texto como aspas de fechamento para delimitar identificadores que contêm caracteres especiais. - - - O DataAdapter para o qual OleDbCommands serão gerados automaticamente - - - Separador decimal usado em literais numéricos. - - - Cadeia de caracteres de prefixo encapsulada com objetos sql - - - Cadeia de caracteres de sufixo encapsulada com objetos sql - O DataAdapter para o qual SqlCommands serão gerados automaticamente - - Separador decimal usado em literais numéricos. - - - O caractere usado em um comando de texto como aspas de abertura para delimitar identificadores que contêm caracteres especiais. - - - O caractere usado em um comando de texto como aspas de fechamento para delimitar identificadores que contêm caracteres especiais. - - - É necessário definir parâmetros numéricos e decimais somente ao usar cenários Prepare, FillSchema e CommandBuilder. - - - É necessário definir parâmetros numéricos e decimais somente ao usar cenários Prepare, FillSchema e CommandBuilder. - - - Tipo nativo do parâmetro. - - - Tipo nativo do parâmetro. - - - Nome do parâmetro, como '@p1' - - - Tipo nativo do parâmetro. - - - O nome do servidor para o tipo. - - - Deslocamento em tipos de dados de comprimento variável. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - O ParameterDirection '{0}' especificado para o parâmetro '{1}' não tem suporte. Os Table-valued parameters somente têm suporte para ParameterDirection.Input. @@ -3468,9 +1101,6 @@ O TypeName especificado para o parâmetro '{0}'. O TypeName deve ser estabelecido apenas por parâmetros Structured. - - O tipo SqlDbType.Structured só tem suporte para tipos de valores variados. - A coluna DateType para o campo '{0}' na tabela do esquema é nula. A DataType não deve ser null. @@ -3483,18 +1113,9 @@ O número de campos no registro '{0}' não corresponde ao número do registro original. - - intervalo: 0-8000 - erro inesperado no provedor de dados SqlClient. {0} - - Não é possível obter Type Info de {0},{1} - - - Falha do provedor ao carregar o seguinte assembly: {0} - A propriedade UdtTypeName deve ser definida como parâmetros UDT. @@ -3504,72 +1125,9 @@ O tipo especificado não está registrado no servidor de destino.{0}. - - Os parâmetros UDT não são permitidos na cláusula where, a menos que façam parte da chave primária. - - - '{0}' é um tipo definido pelo usuário inválido. Motivo: {1}. - - - oferece suporte a formatos de memória e definidos pelo usuário - - - Não há suporte para formato nativo. - - - não implementa IBinarySerialize - - - não pode ser serializado - - - nenhum construtor público - - - não implementa INullable - - - não tem um construtor público - nenhum atributo UDT - - Ainda não há suporte para serialização sem mapeamento. - - - O método 'public static x Parse(System.Data.SqlTypes.SqlString)' é inexistente - - - O método 'public override string ToString()' é inexistente - - - O método 'public static x Null { get; }' é inexistente - - - O formato nativo não oferece suporte a campos (diretamente ou por meio de outro campo) de tipo '{0}' - - - O tipo não é público - - - A Native UDT não apresenta layout sequencial devido ao tipo '{0}' - - - A Native UDT especifica o tamanho máximo de byte - - - o campo '{0}' está marcado como não serializado - - - O tipo de campo ''{0}'' está marcado como layout explícito, o que não é permitido no formato Nativo - - - Referências de assembly com valores múltiplos devem ter uma identificação de Assembly diferente de zero. - - - Tipos Estruturados ou de valores múltiplos somente podem ser usados para parâmetros e não podem ser aninhados dentro de outro tipo. - O nome do caminho não é válido. @@ -3585,9 +1143,6 @@ Falha ao tentar promover transação. - - Objeto de dependência usado para receber notificações de consulta. - Propriedade para indicar se essa dependência é inválida. @@ -3600,9 +1155,6 @@ Para adicionar um comando a um objeto de dependência existente. - - O Command já está associado a outro objeto de dependência. Não é possível substituir. - Esse objeto SqlCommand já está associado a outro objeto SqlDependency. @@ -3630,12 +1182,6 @@ O Timeout especificado é inválido. O tempo limite não pode ser < 0. - - Erro de Notificação. Tipo={0}, Informações={1}, Origem={2}. - - - GetMetaData não é válido para esse SqlDbType. - O dbType {0} é inválido para esse construtor. @@ -4104,9 +1650,6 @@ Erro interno. O valor de texto sem formatação não pode ser nulo. - - Não é possível criptografar. A criptografia resultou em {0} bytes de texto cifrado que excedem o limite máximo permitido de {1} bytes. O valor especificado em texto sem formatação provavelmente é muito grande (tamanho de texto sem formatação é: {2} bytes). - Erro interno. O valor do texto sem formatação não pode ser nulo. @@ -4224,9 +1767,6 @@ As informações de atestado não foram retornadas pelo SQL Server. O tipo de enclave é '{0}' e a URL do atestado de enclave é '{1}'. - - Não é possível estabelecer um canal seguro. Mensagem de Erro: {0} - Erro Interno. Argumento {0} nulo especificado ao construir um objeto do tipo '{1}'. '{0}' não pode ser nulo. @@ -4242,15 +1782,6 @@ Especifica um ponto de extremidade de um serviço de atestação de enclave, que será usado para verificar se o enclave, configurado na instância do SQL Server para cálculos em colunas de banco de dados criptografados usando Always Encrypted, é válido e seguro. - - Falha ao ler a seção de configuração para provedores de enclave. Verifique se que a seção está formatada corretamente no seu arquivo de configuração de aplicativo. Mensagem de Erro: {0} - - - Falha ao criar uma instância de um provedor de enclave com tipo '{1}' para o nome '{0}'. Mensagem de erro: {2} - - - Erro Interno. SqlColumnEncryptionEnclaveProviderName não pode ser nulo ou vazio. - Erro ao ler o conjunto de regras '{0}'. A URL de Atestado não foi especificada na cadeia de conexão, mas a consulta exige computações de enclave. O tipo de enclave é '{1}'. @@ -4260,9 +1791,6 @@ Erro Interno. O tipo de enclave recebido do SQL Server é nulo ou vazio ao executar uma consulta que requer cálculos de enclave. - - Executar uma consulta requer cálculos de enclave, mas na configuração do aplicativo está faltando a seção de provedor de enclave. - Não foi encontrado nenhum provedor de enclave para o tipo enclave '{0}' e o protocolo de atestado '{1}'. Especifique o protocolo de atestado correto na cadeia de conexão. @@ -4320,9 +1848,6 @@ Configuração de criptografia de coluna padrão para todos os comandos na conexão. - - Força a criptografia do parâmetro antes do envio de dados sensíveis ao servidor. - Objeto de dicionário contendo nomes SQL Server e seus caminhos confiáveis de chave mestra da coluna. @@ -4332,9 +1857,6 @@ Não há suporte para a operação conectar com uma instância espelhada do SQL Server usando ApplicationIntent ReadOnly. - - Informações de roteamento inesperadas recebidas. - Informações de roteamento inválidas recebidas. @@ -4485,9 +2007,6 @@ Autenticação sem suporte especificada neste contexto: {0} - - A autenticação Interativa do Active Directory atingiu o tempo limite. O usuário demorou muito tempo para responder à solicitação de autenticação. - Não é possível usar 'Authentication=Active Directory Interactive' quando a propriedade Credential está configurada. @@ -4497,9 +2016,6 @@ SPID (ID do Processo do Servidor) da conexão ativa. - - A autenticação de Fluxo de Código de Dispositivo do Active Directory atingiu o tempo limite. O usuário demorou muito tempo para responder à solicitação de autenticação. - Não é possível definir a propriedade Credential quando 'Authentication=Active Directory Device Code Flow' está especificado na cadeia de conexão. @@ -4533,9 +2049,6 @@ A ação solicitada oferece suporte somente a matrizes dimensionais simples. - - Não é possível remover o item especificado porque ele não foi encontrado na Coleção especificada. - É necessário um número não negativo. @@ -4563,87 +2076,36 @@ Não é possível definir a propriedade AccessTokenCallback se 'Authentication=Active Directory Default' tiver sido especificado na cadeia de conexão. - - {0} retornou {1}. - - - Erro de protocolo: Uma mensagem recebida contém uma assinatura válida, mas não foi criptografada conforme exigido pelo Nível de Proteção efetivo. - - - {0} falhou com erro {1}. - Este método não foi implementado por esta classe. - - O pacote de segurança solicitado não é suportado. - Tipo de PhysicalConnection incorreto. Ocorreu uma exceção ao tentar definir o AppContext Switch '{0}'={1}. - - O tipo {0} não é suportado nesta plataforma. - O modo invariante de globalização não é suportado. - - Não é possível autenticar usando Kerberos. Certifique-se de que o Kerberos foi inicializado no cliente com 'kinit' e um nome principal de serviço foi registrado para o SQL Server para permitir a autenticação do Kerberos. - A palavra-chave 'Biblioteca de Rede' não é suportada nesta plataforma, prefixe a 'Fonte de Dados' com o protocolo desejado ('tcp:' para uma conexão TCP ou 'np:' para uma conexão de Canal Nomeado). O soquete não lançou o esperado '{0}' com código de erro '{1}'. - - O servidor está tentando usar um recurso que não é suportado nesta plataforma. - A palavra-chave '{0}' não é suportada nesta plataforma. O System.Transactions.dll atualmente carregado não oferece suporte a transações globais. - - Recebeu um token não suportado '{0}' ao ler dados do servidor. - - - '{0}' não é um tipo de identificador com suporte. - SqlFileStream não tem suporte nesta plataforma. LocalDB não é suportado nesta plataforma. - - Espaço de buffer insuficiente. Obrigatório: {0} Real: {1}. - - - Falha na operação GSSAPI com status: {0} (status secundário: {1}). - - - A operação GSSAPI falhou com erro - {0} ({1}). - - - Não há suporte para vinculação de canal em sistemas operacionais diferentes do Windows. - - - O nome de destino não deve estar vazio se as credenciais padrão forem passadas. - - - O nível de proteção solicitado não é compatível com a implementação GSSAPI atualmente instalada. - - - A implementação do servidor não é suportada. - - - A autenticação NTLM não é possível com credenciais padrão nesta plataforma. - Quando usado por DataAdapter.Update, o valor do parâmetro é alterado de DBNull.Value para (Int32)1 ou (Int32)0 se não for nulo. @@ -4668,9 +2130,6 @@ Tamanho máximo do buffer excedido: {0} - - DataColumn '{0}' de SourceColumn '{2}' inexistente. - A lista SqlBatchCommand não foi inicializada. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx index 57e1519dbe..9d1fcbc6f1 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - По возрастанию - Недопустимый индекс {0} для этого {1} с Count={2}. @@ -132,18 +129,12 @@ {0} уже содержится в другом {1}. - - {0} уже содержится в этом {1}. - Для {0} могут задаваться только объекты типа {1} с определенными значениями. Попытка удалить {0}, который не содержится в этом {1}. - - {0}.{1} должен быть уникальным, "{2}" уже имеется в этой коллекции. - Подключение не было закрыто. {0} @@ -171,12 +162,6 @@ Недопустимая попытка вызвать {0} при закрытом устройстве чтения. - - Невозможно включить в транзакцию, поскольку это главное подключение для делегированной или активизированной транзакции. - - - По убыванию - Для параметра "{0}" ожидается непустая строка. @@ -204,9 +189,6 @@ Свойство ConnectionString не инициализировано. - - Перехвачено исключение Non CLS Exception. - Указанный элемент безопасности не является элементом разрешений. @@ -252,9 +234,6 @@ Данные - - StateChange - Обновление @@ -264,48 +243,12 @@ ConnectionState, указывающий, открыто или закрыто подключение. - - Событие создается при изменении состояния подключения. - - - Общий тип параметра. - - - Входной, выходной или двунаправленный параметр. - - - свойство, используемое для создания кода со строгой типизацией. - - - Смещение в типах данных переменной длины. - - - Имя параметра. - - - Размер типов данных переменной длины (строки и массивы). - - - При использовании в DataAdapter.Update имя исходного столбца, применяемого для поиска имени DataSetColumn в ColumnMappings. Это необходимо для копирования значения между параметром и строкой данных. - - - При использовании в DataAdapter.Update (только команда UpdateCommand) версия значения DataRow, применяемого для обновления источника данных. - - - При использовании в DataAdapter.Update значение параметра изменяется с DBNull.Value на (Int32)1 или (Int32)0, если определено. - - - Значение параметра. - Не удается создать коллекцию "{0}", потому что произошел сбой запроса SQL. Дополнительные сведения см. во внутреннем исключении. Указано больше ограничений, чем поддерживается затребованной схемой ("{0}"). - - "{2}" не является действительным значением для ограничения "{1}" коллекции схемы "{0}". - Затребованная коллекция ({0}) не определена. @@ -315,15 +258,6 @@ Затребованная коллекция ({0}) не поддерживается поставщиком этой версии. - - Отсутствует один из обязательных столбцов в таблице DataSourceInformation. - - - Таблица DataSourceInformation должна содержать только одну строку. - - - Отсутствует один или несколько обязательных столбцов в коллекции ограничений. - Для одного из ограничений нет соответствующей строки в коллекции ограничений. @@ -342,42 +276,21 @@ Коллекции "{0}" нет в метаданных XML. - - Метаданные XML недействительны. - - - Метаданные XML недействительны. Коллекция {0} должна содержать столбец {1} строкового типа. - Метаданные XML недействительны. Столбец {1} коллекции {0} должен содержать непустую строку. - - Действие - - - Режим работы - Заливка InfoMessage - - Сопоставление - StatementCompleted - - UDT - Уведомление - - Схема - XML @@ -393,9 +306,6 @@ Объединение - - Именованная строка ConnectionString - Безопасность @@ -408,2073 +318,204 @@ Устойчивость подключений - - Коллекция, содержащая особую информацию пользователя. - - - Указывает, выполняется ли сравнение строк в DataSet с учетом регистра. - - - Имя этого DataSet. - - - Указывает особое "представление" данных, содержащихся в DataSet. Это представление позволяет выполнять фильтрацию, поиск и перемещение по особому представлению данных. - - - Указывает, следует ли соблюдать правила ограничений. - - - Указывает, что DataSet содержит ошибки. - - - Указывает языковой стандарт, в соответствии с которым должны сравниваться строки в DataSet. - - - Указывает пространство имен URI XML для корневого элемента, заданного этим DataSet. - - - Указывает префикс пространства имен, используемого для этого DataSet. - - - Коллекция, в которой хранятся отношения для этого DataSet. - - - Коллекция, в которой хранятся таблицы для этого DataSet. - - - Происходит, когда невозможно объединить схемы двух таблиц с одинаковыми именами. - - - Происходит после завершения инициализации Initialization. - - - Представляет кэш данных в памяти. - - - Указывает, выполняется ли сравнение строк в таблице с учетом регистра. - - - Возвращает для этой таблицы дочерние отношения. - - - Коллекция, в которой содержатся столбцы для этой таблицы. - - - Коллекция, в которой содержатся ограничения для этой таблицы. - - - Указывает DataSet, которому принадлежит эта таблица. - - - DataView по умолчанию для данной таблицы. - - - Выражение для вычисления привязанного к данным значения этой строки. - - - Возвращает признак того, содержит ли таблица ошибки. - - - Указывает языковой стандарт, в соответствии с которым должны сравниваться строки в данной таблице. - - - Указывает исходный начальный размер этой таблицы. - - - Указывает пространство имен URI XML для элементов, содержащихся в этой таблице. - - - Указывает префикс пространства имен, используемого для этой таблицы в XML-представлении. - - - Возвращает для этой таблицы родительские отношения. - - - Указывает столбцы, где представлен первичный ключ этой таблицы. - - - Указывает коллекцию, в которой содержатся строки данных для этой таблицы. - - - Указывает имя, используемое для поиска этой таблицы в коллекции таблиц Tables из DataSet. - - - Происходит после успешного завершения редактирования строки в этой таблице. - - - Происходит при изменении строки таким образом, чтобы обработчик события мог изменить или отменить изменение. Пользователь может изменить значения в данной строке и должен создать исключение для отмены редактирования. - - - Происходит после успешного удаления строки из этой таблицы. - - - Происходит, когда строка этой таблицы помечается для удаления. Для отмены этого удаления создайте исключение. - - - Происходит при передаче значения для этого столбца. Пользователь может изменить переданное значение и должен создать исключение для отмены редактирования. - - - Происходит при изменении значения в этом столбце. - - - Происходит перед очисткой всех строк в таблице. - - - Происходит после успешной очистки всех строк в таблице. - - - Происходит после создания нового экземпляра DataRow. - - - Имя, используемое для поиска этого отношения в коллекции отношений Relations из DataSet. - - - Указывает дочерние столбцы этого отношения. - - - Указывает родительские столбцы этого отношения. - - - Указывает, являются ли отношения вложенными. - - - Для удалений; указывает, какого рода каскадная схема должна использоваться в этом отношении. - - - Для обновлений; указывает, какого рода каскадная схема должна использоваться в этом отношении. - - - Для принятия и отклонения изменений; указывает, какого рода каскадная схема должна использоваться в этом отношении. - - - Указывает дочерние столбцы этого ограничения. - - - Указывает родительские столбцы этого ограничения. - - - Указывает дочернюю таблицу для этого ограничения. - - - Указывает столбцы для этого ограничения. - - - Указывает, является ли это ограничение первичным ключом. - - - Указывает имя этого ограничения. - - - Указывает таблицу для этого ограничения. - - - Указывает, допускаются ли в этом столбце значения null. - - - Указывает, увеличиваются ли значения в столбце автоматически при добавлении в таблицу новых строк. Этот столбец должен иметь тип Int16, Int32 или Int64. - - - Указывает начальное значение для столбца AutoIncrement. - - - Указывает приращение, используемое в столбце AutoIncrement. - - - Указывает для этого столбца заголовок интерфейса пользователя по умолчанию. - - - Указывает имя, используемое для поиска этого столбца в коллекции столбцов из таблицы данных. - - - Возвращает DataTable, которому принадлежит этот столбец. - - - Указывает тип данных, хранящихся в этом столбце. - - - Указывает для столбца значение по умолчанию при добавлении строк в таблицу. - - - Указывает значение, вычисляемое для каждой строки этого столбца на основе значений в других столбцах вместо ввода значения пользователем). - - - Указывает, как этот столбец сохраняется в XML: как атрибут, как элемент, как узел с простым содержимым или не сохраняется. - - - Указывает URI XML для элементов или атрибутов, хранящихся в этом столбце. - - - Указывает префикс Prefix, используемый для этого DataColumn в представлении xml . - - - Указывает индекс этого столбца в коллекции столбцов Columns. - - - Указывает, допускаются ли изменения в этом столбце при добавлении в таблицу строки. - - - Указывает, должны ли значения в строках этого столбца в данной таблице быть уникальными. - - - Указывает максимальную длину значения, которая допустима для этого столбца. - - - Указывает DateTimeMode этого DataColumn. - - - Указывает, разрешено ли удаление для этого DataView и связанного с ним интерфейса пользователя. - - - Указывает, разрешено ли редактирование для этого DataView и связанного с ним интерфейса пользователя. - - - Указывает, разрешено ли добавление новых строк для этого DataView и связанного с ним интерфейса пользователя. - - - Возвращает текущее число элементов в этом представлении. - - - Возвращает указатель назад на DataViewManager, которому принадлежит этот DataSet (если есть). - - - Указывает, открыто ли это представление. - - - Указывает выражение, используемое для фильтрации данных, возвращаемых этим DataView. - - - Указывает версии данных, возвращаемых этим DataView. - - - Указывает имена столбцов и порядок возвращения данных этим DataView. - - - Указывает, должна ли использоваться сортировка по умолчанию, если свойство Sort не задано. - - - Указывает таблицу, которую этот DataView использует для извлечения данных. - - - Указывает, что данные, возвращенные этим DataView, каким-либо образом изменились. - - - Указывает источник данных для этого DataViewManager. - - - Указывает параметры сортировки, фильтрации и состояния для любой таблицы в соответствующем DataSet. - - - DataSet не поддерживает union и list в качестве simpleType. - - - Недопустимый синтаксис {0}: отсутствует обязательный атрибут "{1}". - - - Значение "{1}" является недопустимым для атрибута "{0}". - - - Значение атрибута "{0}" должно быть равно "{1}" или "{2}". - - - Не удается найти имя ElementType name="{0}". - - - В отношении "{0}" отсутствует имя родительской таблицы. - - - В отношении "{0}" отсутствует имя дочерней таблицы. - - - В отношении "{0}" отсутствует ключ родительской таблицы. - - - В отношении "{0}" отсутствует ключ дочерней таблицы. - - - Не определенный тип данных: "{0}". - - - Тип данных не определен. - - - Недопустимый выбор XPath в узле поля. Не удается найти: {0}. - - - Недопустимый выбор XPath в узле селектора. {0}. - - - Недопустимый узел Key в ограничении с именем: {0}. - - - Имя ограничения {0} уже используется в данной схеме. - - - Невозможно привести "{0}" к типу "{1}". - - - Отсутствует часть "{0}" в составе ограничения "{1}" под названием "{2}". - - - Недопустимое определение отношения Relation: ключи разной длины. - - - DataSet не допускает циклических ссылок в ComplexType с именем "{0}". - - - DataSet не удается создать экземпляр абстрактного ComplexType для узла {0}. - - - Произошла ошибка в преобразователе нескольких целей во время записи Xml Schema. Подробные сведения см. во внутреннем исключении. - - - Произошла ошибка в преобразователе нескольких целей во время записи Xml Schema. Возвращено значение null или пустая строка. - - - Повторяющееся объявление "{0}". - - - Невозможно загрузить diffGram. В результирующем наборе данных отсутствует таблица "{0}". - - - Невозможно загрузить diffGram. Отсутствует узел sql. - - - Имя столбца "{0}" определено для различных типов сопоставления. - - - Префикс "{0}" является недопустимым, т.к. содержит специальные знаки. - - - Циклическая ссылка во вложенной в себя таблице "{0}". - - - DataSet не может расширять объекты подстановки. Используйте XmlValidatingReader и задайте соответствующее значения свойства EntityHandling. - - - Тип "{0}" не содержит реализации интерфейса IXmlSerializable; невозможно продолжить сериализацию. - - - Продолжить десериализацию невозможно. Поскольку данные не содержат реализации IXMLSerializable, полиморфизм не поддерживается. - - - DataTable не поддерживает выведение схемы из Xml. - - - Невозможно продолжить сериализацию DataTable "{0}". Содержит DataRow, имеющую несколько родительских строк для одного внешнего ключа. - - - Атрибут IsDataSet отсутствует во входной схеме Schema. - - - Не удается определить элемент набора данных. Атрибут IsDataSet встречается более одного раза. - - - DataSet не сериализует типы, в которых реализован интерфейс IDynamicMetaObjectProvider, но не реализован интерфейс IXmlSerializable. - - - Данная функциональная возможность не реализована. {0}. - - - Синтаксическая ошибка: отсутствует операнд после оператора "{0}". - - - Несоответствие типов в выражении "{0}". - - - Выражение слишком сложное. - - - Не удается найти столбец [{0}]. - - - Данное выражение содержит недопустимую строковую константу: {0}. - - - Выражение содержит вызов не определенной функции {0}(). - - - Синтаксическая ошибка в данном выражении. - - - Недопустимое число аргументов: функция {0}(). - - - В выражении отсутствуют закрывающая круглая скобка. - - - Не удается интерпретировать маркер "{0}" в позиции {1}. - - - Ожидалось {0}, но фактически в позиции {2} находится маркер {1}. - - - Невозможно выполнить преобразование из {0} в {1}. - - - Невозможно преобразовать значение "{0}" в тип: {1}. - - - Недопустимое имя столбца [{0}]. - - - Выражение содержит недопустимую константу даты "{0}". - - - В выражении для оператора IN допускаются только константные выражения. - - - Ошибка в операторе Like: шаблон строки "{0}" является недопустимым. - - - Синтаксическая ошибка: Элементы, стоящие за ключевым словом IN, должны быть разделены запятыми и заключены в круглые скобки. - - - Несоответствие типов в аргументе функции: {0}(), аргумент {1}, ожидалось {2}. - - - Несоответствие типов в аргументе функции: {0}(), аргумент {1}, ожидался целочисленный тип. - - - Невозможно выполнить операцию "{0}" над {1} и {2}. - - - Оператор "{0}" для операндов с типами "{1}" и "{2}" является неоднозначным. Невозможно использовать вместе типы со знаком и без знака. Используйте функцию Convert() явно. - - - Синтаксическая ошибка: За ключевым словом IN должен следовать непустой список выражений, разделенных запятыми и заключенных в круглые скобки. - - - Выражение содержит не поддерживаемый оператор "{0}". - - - Выражение содержит недопустимое имя: "{0}". - - - Синтаксическая ошибка: отсутствует операнд перед оператором "{0}". - - - Выражение содержит лишние круглые скобки. - - - Таблица [{0}] участвует в нескольких отношениях. В выражении "{1}" требуется указать имя отношения в явном виде. - - - Синтаксическая ошибка в составном аргументе: ожидается аргумент, состоящий из одного столбца, возможно, с квалификатором Child. - - - Не привязанная ссылка в составном выражении "{0}". - - - Невозможно вычислить непостоянное выражение без текущей строки. - - - Не привязанная ссылка в выражении "{0}". - - - Невозможно вычислить. Выражение "{0}" не является составным. - - - Выражение фильтра "{0}" не вычисляется как логический термин. - - - Недопустимое имя типа "{0}". - - - Синтаксическая ошибка в выражении Lookup: Ожидается слово Parent, за которым следует аргумент, состоящий из одного столбца, возможно, с квалификатором отношения: Parent[(<имя отношения>)].<имя столбца>. - - - Для вызова DataFilter требуется строка или таблица. - - - Аргумент для {0}() вне допустимого диапазона. + + Смещение и длина вышли за границы массива или значение счетчика превышает количество элементов от указателя до конца исходной коллекции. - - Синтаксическая ошибка: Недопустимое использование оператора Is. Допустимый синтаксис: <выражение> Is [Not] Null. + + Нельзя преобразовать объект типа "{0}" в объект типа "{1}". - - Значение недопустимо мало или недопустимо велико для типа "{0}". + + Свойство "{0}" может принимать только значение "{1}". - - Ошибка деления на нуль. + + Для свойства "{0}" допустимо только значение "{1}" или "{2}". - - Невозможно найти родительское отношение"{0}". + + Несоответствие указанных значений QuotePrefix и QuoteSuffix. - - Аргумент "hours" находится вне допустимого диапазона. Это значение должно находиться в диапазоне от -14 до +14. + + Длина аргумента "{0}" превышает установленное для него ограничение "{1}". - - Аргумент "minutes" находится вне допустимого диапазона. Это значение должно находиться в диапазоне от -59 до +59. + + Недопустимое значение ключа "{0}". - - Приведенный диапазон для первого времени превышает 14 часов. + + Использование ключа "{0}" требует наличия ключа "{1}". - - Свойство Kind приведенного аргумента DateTime не совпадает с аргументами "hours" и "minutes". + + Длина значения для ключа "{0}" превышает лимит “{1}”. - - DataColumn типа {0} не поддерживает выражения. + + Ключевое слово не поддерживается: "{0}". - - Не удалось включить ограничения. По меньшей мере одна строка содержит значение, нарушающее ограничения по отсутствию пустых значений, уникальности или внешним ключам. + + Внутренняя ошибка {0} поставщика данных .NET Framework. - - Сама коллекция не поддается изменению. + + {0} "{1}". - - Указанное имя "{0}" без учета регистра совпадает по меньшей мере с двумя именами в объекте коллекции, но с учетом регистра не совпадает ни с одним из этих имен. + + {0} "{1}", неправильное использование кавычек. - - Указанное имя "{0}" совпадает по меньшей мере с двумя именами в объекте коллекции с разными пространствами имен. + + {0} "{1}", недостаточное текущее предельное значение "{2}". - - Смещение и длина вышли за границы массива или значение счетчика превышает количество элементов от указателя до конца исходной коллекции. + + Сбой SqlBulkCopy.WriteToServer, поскольку SqlBulkCopy.DestinationTableName является недействительным составным именем. - - Аргумент "{0}" вне допустимого диапазона. + + Сбой обработки полученных с сервера SQL Server результатов из-за недопустимого составного имени. - - Аргумент "{0}" не может быть пустым. + + SqlParameter.UdtTypeName является недействительным составным именем. - - Аргумент "{0}" содержит пустое значение. + + SqlParameter.TypeName является недопустимым именем, состоящим из нескольких частей - - Невозможно найти столбец {0}. + + Сбой SqlCommand.DeriveParameters из-за того, что значение свойства SqlCommand.CommandText является недействительным составным именем. - - Столбец "{0}" уже принадлежит этому DataTable. + + Подключение к зеркальному экземпляру SQL Server с использованием параметра MultiSubnetFailover не поддерживается. - - Столбец "{0}" уже принадлежит другому DataTable. + + Значение перечисления {0}, {1}, не поддерживается методом {2}. - - DataTable не может содержать несколько столбцов SimpleContent. + + Поставщик данных .NET Framework для SQL Server (SqlClient) не поддерживает значение перечисления {0} "{1}". - - Невозможно добавить столбец SimpleContent к таблице со столбцами элементов или вложенными отношениями. + + {0}: Свойство CommandText не инициализировано - - Столбец с именем "{0}" уже принадлежит этому DataTable. + + {0}: Свойство Connection не инициализировано. - - Не удалось добавить столбец с именем "{0}": вложенная таблица с таким именем уже принадлежит этому DataTable. + + Для {0} нужно открытое и доступное подключение Connection. {1} - - Столбец с именем "{0}" уже принадлежит этому DataTable: невозможно присвоить имя вложенной таблицы таблице с тем же именем. + + Сохраненная процедура "{0}" не существует. - - Невозможно удалить столбец, не принадлежащий этой таблице. + + Транзакция не связана текущим соединением или уже завершена. - - Невозможно удалить этот столбец, т.к. он является частью первичного ключа. + + Транзакция, связанная с текущим соединением, завершена, но не удалена. Это соединение можно будет использовать для выполнения инструкций SQL только после удаления транзакции. - - Невозможно удалить этот столбец, т.к. он является частью родительского ключа в отношении {0}. + + {0} требует, чтобы команда имела транзакцию, если подключение, назначенное команде, находится в отложенной локальной транзакции Свойство Transaction для команды не инициализировано. - - Невозможно удалить этот столбец, т.к. он является частью ограничения {0} для таблицы {1}. + + {0} уже имеет связанный открытый поставщик DataReader, который сначала нужно закрыть. - - Невозможно удалить этот столбец, т.к. он является частью выражения: {0} = {1}. + + DeriveParameters {0} поддерживает только CommandType.StoredProcedure, но не поддерживает CommandType.{1}. - - Невозможно задать свойство AutoIncrement для вычисляемого столбца. - - - Невозможно задать свойство AutoIncrement для столбца с заданным DefaultValue. - - - Невозможно задать DefaultValue для столбца AutoIncrement. - - - AutoIncrementStep не должен быть равен нулю. - - - ColumnName обязателен, когда он является частью DataTable. - - - Невозможно изменить DataType столбца, т.к. он содержит данные. - - - Для столбца требуется допустимый DataType. - - - Значение DefaultValue столбца {0} имеет тип {1}, и его невозможно преобразовать в {2}. - - - Значение DefaultValue данного столбца имеет тип {0}, и его невозможно преобразовать в {1}. - - - Значение DefaultValue столбца {0} имеет тип {1}, хотя столбец имеет тип {2}. - - - Невозможно изменить свойство ReadOnly для столбца выражений. - - - Невозможно изменить свойство Unique для столбца выражений. - - - Невозможно создать выражение в столбце, которому назначено свойство AutoIncrement или Unique. - - - Невозможно задать выражение, т.к. столбец невозможно перевести в состояние ReadOnly. - - - Невозможно задать свойство Expression столбца {0}, т.к. оно является частью ограничения. - - - Невозможно создать ограничение на основе столбца Expression {0}. - - - Невозможно задать свойство Expression по причине циклической ссылки в выражении. - - - Столбец "{0}" содержит значения равные null. - - - В столбце "{0}" не допускаются значения равные nulls. - - - Столбец "{0}" доступен только для чтения. - - - Столбец "{0}" содержит повторяющиеся значения. - - - Столбец "{0}" не принадлежит таблице {1}. - - - Столбец должен принадлежать таблице. - - - Невозможно сохранить <{0}> в столбце {1}. Ожидался тип {2}. - - - Невозможно присвоить столбцу Column "{0}" значение null. Используйте DBNull. - - - Не удается задать столбец "{0}". Значение нарушает ограничение MaxLength для этого столбца. - - - MaxLength действует только в отношении данных строкового типа. Для свойства MaxLength столбца Column "{0}" невозможно установить значение в виде неотрицательного числа. - - - Невозможно присвоить свойству MaxLength столбца Column "{0}" значение "{1}". Длина по меньшей мере одной строки в таблице превышает новое предельное значение. - - - Невозможно задать свойство MaxLength столбца Column "{0}". Он является столбцом Column SimpleContent. - - - Невозможно присвоить свойству DataType столбца Column "{0}" значение {1}. Он является столбцом SimpleContent. - - - Невозможно присвоить свойству MappingType столбца Column "{0}" значение SimpleContent. DataType столбца равен {1}. - - - В столбце Column "{0}" превышено ограничение, которое задает MaxLength. - - - Для столбца "{0}" использование DBNull.Value не разрешено. - - - Невозможно изменить свойство Namespace столбца Column "{0}". Он является столбцом SimpleContent. - - - Не удалось изменить AutoIncrement для DataColumn типа "{0}", поскольку он содержит данные. - - - Столбец "{0}" не принадлежит базовой таблице "{1}". - - - DataColumn типа "{0}" относится к типу complexType. Невозможно сериализовать значение составного типа как атрибут Attribute. - - - DateTimeMode может быть назначен только для DataColumns типа DateTime. - - - "{0}" является недействительным значением DataSetDateTime. - - - Не удалось изменить DateTimeMode с "{0}" на "{1}", поскольку таблица содержит данные. - - - Тип Type "{0}" не содержит статическое свойство или поле static Null. - - - Тип "{0}" не содержит реализации IRevertibleChangeTracking; невозможно продолжить RejectChanges(). - - - SetAdded и SetModified могут вызываться только для DataRows с неизмененным DataRowState. - - - Порядковый номер "{0}" превышает максимальное число. - - - DataSet не поддерживает System.Nullable<>. - - - Невозможно изменить имя ограничения на пустую строку, когда оно входит в ConstraintCollection. - - - Невозможно наложить ограничения на ограничение {0}. - - - На столбец "{0}" наложено ограничение по уникальности. Значение "{1}" уже имеется. - - - Ограничение "{0}" не принадлежит этому DataTable. - - - Невозможно найти ограничение {0}. - - - Данное ограничение идентично ограничению с именем {0}, которое уже имеется в коллекции. - - - Ограничение Constraint с именем "{0}" уже принадлежит этому DataTable. - - - Эти столбцы в данный момент не содержат уникальных значений. - - - Эти столбцы не указывают на данную таблицу. - - - Невозможно включить это ограничение, т.к. не все значения имеют соответствующие родительские значения. - - - Невозможно добавить это ограничение, т.к. ForeignKey не принадлежит таблице {0}. - - - Невозможно удалить ограничение, не принадлежащее этой таблице. - - - Невозможно переместить уникальное ограничение "{0}". Сначала удалите ограничение внешнего ключа "{1}". - - - Невозможно удалить эту строку, т.к. на отношение {0} наложены ограничения, и удаление этой строки вызовет конфликты в дочерних строках. - - - Невозможно выполнить это изменение, т.к. на отношение {0} наложены ограничения, и изменение этого значения вызовет конфликты в дочерних строках. - - - Невозможно очистить таблицу {0}, т.к. ForeignKeyConstraint {1} налагает ограничения и в {2} содержатся дочерние строки. - - - Для ForeignKeyConstraint {0} требуется, чтобы в родительской таблице присутствовали значения дочерних ключей ({1}). - - - Свойство недоступно, т.к. "{0}". - - - Невозможно удалить эту строку, т.к. она имеет дочерние строки и на отношение {0} наложены ограничения. - - - Невозможно добавить ограничение для первичного ключа, т.к. для этой таблицы уже назначен первичный ключ. - - - Не удалось добавить ограничение к DataTable "{0}", которая является дочерней таблицей в двух вложенных отношениях. - - - Невозможно создать ключ из столбцов, принадлежащих разным таблицам. - - - Число столбцов не может равняться 0. - - - Число столбцов не может превышать {0}. - - - Невозможно создать ключ, когда один столбец участвует в перечислении несколько раз. "{0}" - - - Невозможно удалить ограничение по уникальности, т.к. оно образует первичный ключ таблицы. - - - Невозможно удалить ограничение по уникальности, т.к. оно образует первичный ключ таблицы {0}. - - - Среди родительских столбцов и дочерних столбцов отсутствуют столбцы совпадающих типов. - - - ParentKey и ChildKey совпадают. - - - ParentColumns и ChildColumns должны иметь одинаковую длину. - - - ParentColumns и ChildColumns должны иметь ненулевую длину. - - - Данная строка и это отношение принадлежат разным DataSet. - - - RelationName является обязательным, когда он - часть DataSet. - - - Для GetChildRows требуется строка из таблицы {0}, но таблицей Table заданной строки является {1}. - - - Это отношение должно соединять в данном DataSet две таблицы, подлежащие добавлению к этому DataSet. - - - Для GetParentRow требуется строка из таблицы {0}, но таблицей заданной строки является {1}. - - - Для SetParentRow требуется дочерняя строка из таблицы {0}, но таблицей заданной строки является {1}. - - - Отношения между таблицами в различных DataSet невозможны. - - - Невозможно создать отношение между таблицами в различных DataSet. - - - Для этих дочерних столбцов уже существует отношение. - - - Это отношение не принадлежит данной коллекции отношений. - - - Это отношение уже принадлежит другому DataSet. - - - Это отношение уже принадлежит этому DataSet. - - - Отношение Relation с именем "{0}" уже принадлежит этому DataSet. - - - Отношение Relation {0} не принадлежит этому DataSet. - - - Невозможно найти отношение {0}. - - - Невозможно создать коллекцию для таблицы со значением null. - - - Таблица, для которой эта коллекция отображает отношения, удалена из своего DataSet. - - - Невозможно добавить отношение в коллекцию ParentRelation этой таблицы, если для коллекции эта таблица не является дочерней. - - - Невозможно добавить отношение в коллекцию ChildRelation этой таблицы, не являющейся в этой коллекции родительской таблицей. - - - Невозможно присвоить свойству Nested этого отношения значение false (ложь). - - - Одна и та же таблица "{0}" не может быть дочерней в двух вложенных отношениях. - - - Во вложенных отношениях таблица ({0}) не может быть дочерней по отношению к себе. - - - Невозможно добавить DataRelation или Constraint, для которых значения параметров Locale или CaseSensitive различны в родительской и дочерней таблицах. - - - Невозможно создать DataRelation, если родительские или дочерние столбцы не входят в DataSet. - - - Вложенная таблица "{0}", которая наследует свое пространство имен, не может иметь несколько родительских таблиц в разных пространствах имен. - - - Вложенная таблица "{0}" с пустым пространством имен не может иметь несколько родительских таблиц в разных пространствах имен. - - - Данная строка и это отношение принадлежат разным DataSet. - - - Невозможно выполнить эту операцию над строкой, не содержащейся в данной таблице. - - - Это отношение и дочерняя строка не принадлежат одному DataSet. - - - Невозможно изменить предложенное значение в событии RowChanging. - - - Невозможно вызвать EndEdit() внутри события OnRowChanging. - - - Невозможно вызвать BeginEdit() внутри события RowChanging. - - - Невозможно вызвать CancelEdit() внутри события OnRowChanging. Для отмены этого обновления создайте исключение exception. - - - Невозможно вызвать Delete внутри события OnRowDeleting. Для отмены этого удаления создайте исключение exception. - - - Длина входного массива больше числа столбцов в этой таблице. - - - Отсутствуют данные Current для доступа. - - - Отсутствуют данные Original для доступа. - - - Отсутствуют данные Proposed для доступа. - - - Эта строка удалена из таблицы и не содержит данных. BeginEdit() позволит создать в этой строке новые данные. - - - Невозможно получить доступ к удаленной информации строки через данную строку. - - - Версия должна иметь значение Original, Current или Proposed. - - - В позиции {0} строка отсутствует. - - - Недопустимая позиция вставки строки {0}. - - - Значение rowOrder value={0} для таблицы с именем "{1}" обнаружено дважды. - - - Отсутствуют значения в последовательности rowOrder для таблицы "{0}". - - - Указанная DataRow отсутствует в текущей DataRowCollection. - - - Эта строка уже принадлежит другой таблице. - - - Эта строка уже принадлежит данной таблице. - - - Невозможно удалить эту строку, т.к. она уже удалена. - - - Эта строка пуста. - - - Невозможно удалить строку, которая уже удалена. - - - Дочерняя строка имеет несколько родительских строк. - - - Неопознанное сочетание битов состояния строки. - - - Невозможно изменить имя DataSet на пустую строку. - - - Недопустимое имя "{0}". Имя DataSet не может совпадать с именем DataTable. - - - Пространство имен данной схемы недействительно. Вместо него используйте указанное: {0}. - - - Невозможно изменить свойство CaseSensitive или Locale. Такое изменение приведет к тому, что по меньшей мере один DataRelation или Constraint будет иметь другое значение параметра Locale или CaseSensitive в связанных с ним таблицах. - - - Свойство SchemaSerializationMode может быть установлено только в том случае, если оно переопределено производным DataSet. - - - Столбцы PrimaryKey не принадлежат этой таблице. - - - Невозможно добавить вложенное отношение или столбец элементов в таблицу, содержащую столбец SimpleContent. - - - TableName является обязательным, если является частью DataSet. - - - DataTable уже содержит столбец с простым содержимым. - - - Таблица не имеет первичного ключа. - - - {0} не является допустимой строковой записью сортировки Sort. - - - Не удалось сериализовать DataTable. DataTable, использующаяся в одном или нескольких выражениях DataColumn, не является потомком текущей DataTable. - - - Удаленное взаимодействие с этой DataTable возможно только в составе DataSet. Имеются столбцы Expression, ссылающиеся на другие DataTable. - - - Невозможно использовать разные значения свойства удаленного форматирования для DataSet и DataTable. - - - Не удалось сериализовать DataTable. Не указано имя DataTable. - - - DataTable с именем "{0}" уже принадлежит этому DataSet. - - - DataTable "{0}" с тем же пространством имен Namespace "{1}" уже принадлежит этому DataSet. - - - Во вложенном отношении таблица ({0}) не может быть дочерней по отношению к себе: имя DataSet не согласуется с именем таблицы. - - - Недопустимое имя "{0}". Имя DataTable не может совпадать с именем DataSet. - - - DataTable уже принадлежит другому DataSet. - - - DataTable уже принадлежит этому DataSet. - - - Таблица {0} не принадлежит этому DataSet. - - - Невозможно найти таблицу {0}. - - - Невозможно удалить таблицу, для которой существуют отношения. Сначала удалите отношения. - - - Невозможно удалить таблицу {0}, т.к. ссылка на нее содержится в ForeignKeyConstraint {1}. Сначала удалите это ограничение. - - - DataTable "{0}" не соответствует ни одной DataTable в источнике. - - - В конечном DataSet отсутствует определение {0}. - - - В конечном DataSet отсутствует {1} {0}. - - - <target>.{0} и <source>.{0} имеют несовместимые свойства: несоответствие свойства DataType. - - - <target>.PrimaryKey и <source>.PrimaryKey имеют различную длину Length. - - - Несоответствие столбцов в PrimaryKey: <target>.{0} относительно <source>.{1}. - - - Отношение {0} не может быть объединено, поскольку ключи имеют несовпадающие столбцы. - - - В конечной таблице {0} отсутствует определение столбца {1}. - - - Столбец PrimaryKey {0} отсутствует в исходной таблице. - - - Параметру RowStates должно быть присвоено допустимое сочетание значений из перечисления DataViewRowState. - - - Поиск строк выполняется на основе порядка сортировки, а порядок сортировки не задан. - - - Ожидалось {0} значение (значений) индексируемого ключа, а получено {1} значение (значений). - - - Недопустимое использование агрегатной функции {0}() и типа: {1}. - - - Недопустимый тип хранилища: {0}. - - - Режим хранения Xml DataSet не поддерживает значение "{0}" как значение типа Char, используйте хранилище данных типа Byte. - - - Тип значения не соответствует типу столбца. - - - Тип "{0}" не содержит реализации интерфейса IComparable. Сравнение невозможно. - - - Невозможно задать {0}. - - - Невозможно изменить DataSet для DataViewManager, который уже является представлением по умолчанию для DataSet. - - - RowStateFilter не может одновременно отображать версии данных ModifiedOriginals и ModifiedCurrents. - - - Невозможно изменить свойство Table для DefaultView или для DataView, считанного из DataViewManager. - - - После задания свойства DataSet его невозможно изменить. - - - Перед использованием DataViewManager требуется задать DataSet. - - - После задания свойства Table его невозможно изменить. - - - Перед использованием DataView требуется задать DataTable. - - - Невозможно выполнить привязку к DataTable без имени. - - - Невозможно поместить объект в этот список. - - - Невозможно вызвать AddNew для DataView, в котором AllowNew имеет значение false (ложь). - - - DataView не открыт. - - - Отношение не назначено родительским для таблицы, на которую указывает этот DataView. - - - Невозможно выполнить удаление для DataSource, в ктором AllowDelete имеет значение false (ложь). - - - Невозможно выполнить редактирование для DataSource, в котором AllowEdit имеет значение false (ложь). - - - Индекс {0} отрицательный или превышает общее число строк. - - - Невозможно добавить в этот список внешние объекты. - - - Невозможно очистить этот список. - - - Невозможно вставить в этот список внешние объекты. - - - Невозможно удалить объекты, отсутствующие в этом списке. - - - {0} не является для таблицы {1} ни DataColumn, ни DataRelation. - - - В объекте Range минимум Min {0}) должен быть меньше или равен максимуму max ({1}). - - - Является неопределенным диапазоном null. - - - Значение MinimumCapacity должно быть неотрицательным. - - - Не удается создать идентификатор для имени "{0}". - - - Имеется несколько таблиц с именем "{0}" (даже если пространства имен отличаются). - - - Столбец "{0}": тип "{1}" не может иметь значение null. - - - Столбец "{0}": Тип "{1}" не имеет беспараметрического конструктора. - - - Столбец "{0}": Тип "{1}" не имеет конструктора со строковым аргументом. - - - Нельзя преобразовать объект типа "{0}" в объект типа "{1}". - - - Исключение данных Data Exception. - - - Исключение ограничения Constraint Exception. - - - Удаленные строки недоступны. - - - Повторяющиеся имена запрещены. - - - Операция не поддерживается в событии RowChanging. - - - Недействительное ограничение. - - - Отсутствует первичный ключ. - - - Значения Null не допускаются. - - - Столбец доступен только для чтения. - - - Строка в таблице не найдена. - - - Версия не найдена. - - - Изменены данные ReadOnly, доступные только для чтения. - - - Недействительный DataTableReader для текущей DataTable "{0}". - - - Схема текущей DataTable "{0}" в DataTableReader изменилась; DataTableReader недействителен. - - - Невозможно создать DataTableReader. В DataSet нет DataTable. - - - Не удается создать DataTableReader. Пустой аргумент. - - - Не удается создать DataTableReader. Аргументы содержат значение null. - - - Текущий элемент DataRow находится в состоянии Deleted или Detached. - - - Текущая DataTable "{0}" пуста. В DataTable нет DataRow. - - - Поврежден внутренний индекс DataTable: "{0}". - - - Коллекция изменена, возможно операция перечисления не будет выполнена. - - - Простой тип "{0}" уже объявлен с другим "{1}". - - - Недопустимая нумерация. - - - Невозможно изменить имя таблицы, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Невозможно изменить пространство имен таблицы, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Невозможно изменить имя столбца, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Невозможно изменить пространство имен столбца, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Невозможно изменить свойство ColumnMapping, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Невозможно добавить или удалить столбцы таблицы, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Невозможно добавить или удалить таблицы из DataSet, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Невозможно добавить, удалить или изменить вложенное отношение в DataSet, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Недопустимый параметр DataSet. Он не может иметь значение null. - - - Невозможно изменить имя DataSet, когда соответствующий DataSet сопоставлен загруженному документу XML. - - - Невозможно клонировать узел этого типа. {0}. - - - Невозможно загрузить XmlDataDocument, если он уже содержит данные. Используйте новый XmlDataDocument. - - - Невозможно назначить DataSet по меньшей мере одному XmlDataDocument. Невозможно назначить DataSet текущему XmlDataDocument, т.к. данный DataSet уже назначен другому XmlDataDocument. - - - Перед попыткой изменения XmlDataDocument с помощью операций XML присвойте DataSet.EnforceConstraints значение == false (ложь). - - - Метод GetElementById() для DataDocument не поддерживается. - - - Невозможно создать ссылки на объекты подстановки для DataDocument. - - - Функция очистки Clear DateSet и DataTable для XmlDataDocument не поддерживается. - - - Невозможно удалить столбец, т.к. он встроен в этот dataSet. - - - Невозможно удалить отношение, поскольку оно встроено в этот набор данных. - - - Происходит при каждом изменении любого свойства этого элемента управления. - - - Происходит при каждом изменении принадлежности этой коллекции. - - - Невозможно получить значение, поскольку оно равно DBNull. - - - Свойству "{0}" необходима Microsoft WindowsNT или другая операционная система на ее основе. - - - Запрошенный поставщик данных .NET Framework не найден. Возможно, он не установлен. - - - Реализация запрошенного поставщика данных .NET Framework не содержит поле Instance (Экземпляр) производного типа System.Data.Common.DbProviderFactory. - - - Не удалось найти или загрузить зарегистрированный поставщик данных .NET Framework. - - - Требуется полное имя сборки поставщика данных .NET Framework. - - - Разрешены только элементы. - - - Дочерние узлы не разрешены. - - - Неопознанный атрибут "{0}". - - - Неизвестный элемент. - - - В файле конфигурации может быть только один раздел "{0}". - - - Обязательный атрибут "{0}" не найден. - - - Обязательный атрибут "{0}" не может быть пустым. - - - Для параметра "{0}" ожидается непустой массив. - - - Свойство "{0}" может принимать только значение "{1}". - - - Для свойства "{0}" допустимо только значение "{1}" или "{2}". - - - Несоответствие указанных значений QuotePrefix и QuoteSuffix. - - - Длина аргумента "{0}" превышает установленное для него ограничение "{1}". - - - Ожидается аргумент типа {1}, получен аргумент типа {0}. - - - Недопустимое значение ключа "{0}". - - - Использование ключа "{0}" требует наличия ключа "{1}". - - - Длина значения для ключа "{0}" превышает лимит “{1}”. - - - Ключевое слово не поддерживается: "{0}". - - - Не удалось загрузить файл UDL. - - - Недопустимый файл UDL. - - - Внутренняя ошибка {0} поставщика данных .NET Framework. - - - Невозможно изменить свойства QuotePrefix и QuoteSuffix после того, как создана команда Insert, Update или Delete. - - - Требуется инициализировать свойство DataAdapter.SelectCommand. - - - Требуется инициализировать свойство DataAdapter.SelectCommand.Connection; - - - {0} "{1}". - - - {0} "{1}", неправильное использование кавычек. - - - {0} "{1}", недостаточное текущее предельное значение "{2}". - - - Сбой SqlBulkCopy.WriteToServer, поскольку SqlBulkCopy.DestinationTableName является недействительным составным именем. - - - Сбой обработки полученных с сервера SQL Server результатов из-за недопустимого составного имени. - - - SqlParameter.UdtTypeName является недействительным составным именем. - - - SqlParameter.TypeName является недопустимым именем, состоящим из нескольких частей - - - Сбой SqlCommand.DeriveParameters из-за того, что значение свойства SqlCommand.CommandText является недействительным составным именем. - - - Сбой OdbcCommandBuilder.DeriveParameters из-за того, что значение свойства OdbcCommand.CommandText является недействительным составным именем. - - - Сбой OleDbCommandBuilder.DeriveParameters из-за того, что значение свойства OleDbCommandBuilder.CommandText является недействительным составным именем. - - - Подключение к зеркальному экземпляру SQL Server с использованием параметра MultiSubnetFailover не поддерживается. - - - Сбой сопоставления столбцов из SourceColumn "{0}" из-за того, что DataColumn "{1}" является вычисляемым столбцом. - - - Не поддающееся преобразованию несоответствие типов между SourceColumn "{0}" из {1} и DataColumn "{2}" из {3}. - - - В DataTable "{1}" отсутствует DataColumn "{0}" для данного SourceColumn "{2}". - - - Значение SourceColumn не должно быть пустой строкой. - - - Отсутствует сопоставление SourceColumn для "{0}". - - - Значение перечисления {0}, {1}, не поддерживается методом {2}. - - - Поставщик данных .NET Framework для ODBC не поддерживает значение перечисления {0} "{1}". - - - Поставщик данных .NET Framework для OLE DB не поддерживает значение перечисления {0} "{1}". - - - Поставщик данных .NET Framework для SQL Server (SqlClient) не поддерживает значение перечисления {0} "{1}". - - - Не удалось извлечь ComputerNameDnsFullyQualified, {0}. - - - Для SourceTable "{1}" отсутствует DataTable "{0}". - - - Значение SourceTable не должно быть пустой строкой - - - Отсутствует сопоставление для SourceTable: "{0}" - - - {0}: Свойство CommandText не инициализировано - - - {0}: Свойство Connection не инициализировано. - - - Для {0} нужно открытое и доступное подключение Connection. {1} - - - Fill: Свойство SelectCommand.Connection не инициализировано. - - - FillPage: Свойство SelectCommand.Connection не инициализировано. - - - FillSchema: Свойство SelectCommand.Connection не инициализировано. - - - Для обновления требуется, чтобы InsertCommand имел объект подключения. Свойство Connection для InsertCommand не инициализировано. - - - Для обновления требуется, чтобы UpdateCommand имел объект подключения. Свойство Connection для UpdateCommand не инициализировано. - - - Для обновления требуется, чтобы свойство DeleteCommand имело объект подключения. Свойство Connection для DeleteCommand не инициализировано. - - - Для обновления требуется объект подключения. Свойство Connection не инициализировано. - - - Для обновления требуется, чтобы клон команды имел объект подключения. Свойство Connection для клона команды не инициализировано. - - - Для обновления требуется подключение. - - - Для элемента Update требуется, чтобы команда {0}Command имела открытый объект подключения {1} - - - Для элемента Update требуется, чтобы команда {0}Command имела открытый объект подключения {1} - - - Для элемента Update требуется, чтобы команда {0}Command имела открытый объект подключения {1} - - - Для элемента Update требуется, чтобы команда обновления имела открытый объект подключения. {1} - - - Сохраненная процедура "{0}" не существует. - - - Транзакция, назначенная этой команде, должна быть отложенной локальной транзакцией с наибольшим уровнем вложенности. - - - Транзакция не связана текущим соединением или уже завершена. - - - Транзакция, связанная с текущим соединением, завершена, но не удалена. Это соединение можно будет использовать для выполнения инструкций SQL только после удаления транзакции. - - - {0} требует, чтобы команда имела транзакцию, если подключение, назначенное команде, находится в отложенной локальной транзакции Свойство Transaction для команды не инициализировано. - - - Существует назначенный этой команде открытый SqlResultSet, который требуется предварительно закрыть. - - - {0} уже имеет связанный открытый поставщик DataReader, который сначала нужно закрыть. - - - DeriveParameters {0} поддерживает только CommandType.StoredProcedure, но не поддерживает CommandType.{1}. - - - Метод "{0}" не может быть вызван больше одного раза в одном и том же сеансе. - - - Неверный асинхронный результат. - - - Перед вызовом "{0}" не было инициализировано свойство SelectCommand. - - - StatementType {0} в этом месте не допускается. - - - FillSchema: для имени SourceTable ожидалась непустая строка. - - - Недопустимое значение MaxRecords, {0}; значение должно быть неотрицательным. - - - Недопустимое значение StartRecord, {0}; значение должно быть неотрицательным. - - - Fill: для имени SourceTable ожидалась непустая строка. - - - Столбцы иерархических глав должны быть сопоставлены с DataColumn со свойством AutoIncrement. - - - DataReader.GetFieldType({0}) вернул значение null. - - - Указывайте только один элемент в массиве dataTables в случае использования ненулевых значений для startRecords или maxRecords. - - - Операции Update не удалось найти TableMapping["{0}"] или DataTable "{0}". - - - Операция Update: ожидалось непустое имя SourceTable. - - - Отсутствует TableMapping при TableMapping.DataSetTable="{0}". - - - Для элемента Update требуется действительный клон команды. - - - Для автоматического создания SQL во время обновления (Update) требуется действительный SelectCommand. - - - Для элемента Update требуется действительный InsertCommand при передаче коллекции DataRow с новыми строками. - - - Для обновления требуется действительный UpdateCommand при передаче коллекции DataRow с измененными строками. - - - Для элемента Update требуется действительный DeleteCommand при передаче коллекции DataRow с удаленными строками. - - - DataRow[{0}] принадлежит не тому DataTable, которому принадлежит DataRow[0]. - - - RowUpdatedEvent: ошибки. Дополнительные сведения отсутствуют. - - - RowUpdatingEvent: ошибки. Дополнительные сведения отсутствуют. - - - Недействительное значение свойства UpdatedRowSource (UpdateRowSource.FirstReturnedRecord или UpdateRowSource.Both) для команды в процессе пакетирования. - - - Нарушение параллелизма: UpdateCommand затронула {0} из ожидаемых {1} записей. - - - Нарушение параллелизма: DeleteCommand затронула {0} из ожидаемых {1} записей. - - - Нарушение параллелизма: пакетная команда затронула {0} из ожидаемых {1} записей. + + Метод "{0}" не может быть вызван больше одного раза в одном и том же сеансе. Недопустимое значение CommandTimeout {0}; значение должно быть >= 0. - - {1}[{0}]: Свойство Size имеет недопустимый размер 0. - - - Для метода {0}.Prepare требуется, чтобы для всех параметров был явно указан тип. - - - Для метода {0}.Prepare требуется, чтобы всем параметрам переменной длины был в явном виде присвоен ненулевой Size. - - - Для метода {0}.Prepare требуется, чтобы для параметров типа "{1}" были в явном виде заданы Precision и Scale. - - - Вызов несоответствующего завершающего метода для asyncResult. Ожидался вызов {0}, но был вызван {1}. - - - Недопустимая операция. Подключение закрыто. - - - Подключение отключено. - - - Невозможно включить в транзакцию, т.к. через данное подключение выполняется локальная транзакция. Завершите локальную транзакцию и повторите попытку. - - - Для этого подключения транзакция уже внесена в список. Завершите текущую транзакцию и повторите попытку. - - - База данных не может быть пустой, не может являться пустой строкой или строкой, заполненной только пробелами или знаками табуляции. - - - Слишком длинный аргумент. - - - Недопустимое значение "времени ожидания подключения" Connect Timeout. Оно должно быть целым неотрицательным числом. - - - Недопустимое смещение в исходном буфере (размер: {0}): {1} - - - Недопустимое смещение в конечном буфере (размер: {0}): {1} - - - Численное значение не умещается в 96-разрядном десятичном числе. - - - Недопустимая попытка {0} при закрытом потоке. - - - Указанное значение SeekOrigin недействительно. - - - Динамическое создание SQL для нескольких базовых таблиц не поддерживается. - - - Динамическое создание SQL не поддерживается для SelectCommand, не возвращающего никакой информации о базовой таблице. - - - Динамическое создание SQL для DeleteCommand не поддерживается для SelectCommand, не возвращающей никаких сведений о столбце ключей. - - - Динамическое создание SQL для UpdateCommand не поддерживается для SelectCommand, не возвращающей никаких сведений о столбце ключей. - - - Динамическое создание SQL для DeleteCommand не поддерживается для SelectCommand, не содержащей столбца, в котором указаны версии строк. - - - Динамическое создание SQL для UpdateCommand не поддерживается для SelectCommand, не содержащей столбца, в котором указаны версии строк. - - - Динамическое создание SQL не поддерживается для имен таблиц "{0}", содержащих знак QuotePrefix или QuoteSuffix "{1}". - - - Недопустимая попытка считывания из столбца с порядковым номером "{0}". С помощью CommandBehavior.SequentialAccess считывание разрешено только из столбцов с порядковым номером "{1}" и выше. - - - Тип данных "{0}" не может быть отформатирован в качестве литерала, поскольку имеет недопустимые разряды даты и времени. - - - Значение не может быть отформатировано в качестве литерала требуемого типа. - - - Тип данных "{0}" не может быть отформатирован в качестве литерала, поскольку имеет недопустимый максимальный масштаб. - - - Указанное значение константы недопустимо для типа данных "{0}". - - - "{0}":длина значения литерала должна быть четным значением. - - - "{0}":значением литерала должна быть строка шестнадцатеричных цифр - - - Если не указан префикс с кавычками, то для {0} требуется открытое подключение. - - - Не удалось определить правильные значения логических констант. Логические литералы Boolean literals не могут быть созданы. - - - Константы собственного типа данных, назначенные типу данных "{0}", не поддерживаются. - - - Недопустимый тип данных параметра {0}. - - - Отсутствует сопоставление типа объекта {0} с известным собственным типом управляемого поставщика. - - - Не удалось обработать неизвестный код TypeCode {0}, возвращенный типом {1}. - - - Отсутствует сопоставление DbType {0} с известным {1}. - - - Используемая версия SQL Server не поддерживает тип данных "{0}". - - - Значение параметра "{0}" вне допустимого диапазона. - - - Указанное имя параметра "{0}" является недопустимым. - - - Параметры с несколькими возвращаемыми значениями не поддерживаются. - - - Недопустимое значение параметра размера "{0}". Значение должно быть больше или равно 0. - - - Недопустимое значение для аргумента "{0}". Значение должно быть больше или равно нулю. - - - Недопустимое значение для этих метаданных. - - - Метаданные должны быть SqlDbType.Row - - - Не удалось преобразовать значение параметра из {0} в {1}. - - - {0} не поддерживает параллельные транзакции. - - - Данный {0} завершен; его повторное использование невозможно. - - - Нельзя вызвать "{0}", если запись доступна только для чтения. - - - Нельзя вызвать "{0}", если запись DbDataRecord доступна только для чтения. - - - Неявное преобразование типа объекта "{0}" в тип данных "{1}" не поддерживается. - - - Смещение в буфере "{1}" и доступные байты "{0}" в сумме больше, чем длина, переданная в буфер. - - - Длина данных "{0}" меньше 0. - - - Указанная длина "{0}" находится вне допустимого диапазона. - - - Недопустимая попытка {2} для dataIndex "{0}". При CommandBehavior.SequentialAccess считывание разрешается только при dataIndex "{1}" и больше. - - - Смещение должно ссылаться на позицию в пределах значения. - - - "Управляемому поставщику ODBC необходимо, чтобы для коллекции индексов GetSchema ограничение TABLE_NAME было определено и не было пустым. - - - Недопустимое значение аргумента для метода "{0}". - - - Поставщик ODBC не вернул результаты из SQLGETTYPEINFO. - - - Непредусмотренный аргумент DataTable null - - - Непредусмотренный неопределенный аргумент null DataSet. - - - Длина строки подключения превышает максимальную разрешенную величину {0}. - - - {0} — не удается произвести сопоставление типа. - - - Неизвестный тип SQL - {0}. - - - Неизвестный тип URT - {0}. - - - Недопустимый отрицательный аргумент! - - - Невозможно назначить свойство для открытого подключения. - - - Отсутствует действительное сопоставление SQL_TRANSACTION "{0}" значению перечисления System.Data.IsolationLevel. - - - {0} - не удалось активировать использование пула подключений... - - - {0} - не удалось выделить дескриптор среды. - - - {0} — не удалось получить дескриптор. - - - Отсутствует в транзакции - - - Недопустимое значение value={0} перечисления OdbcType. - - - Используйте IsDBNull, если ожидаются данные DBNull.Value. - - - {0} [{1}] {2} - - - {0} - информация об ошибках недоступна - - - Соединение закрыто. - - - Внутреннее соединение не имеет владельца. - - - Поставщик данных .NET Framework для ODBC требует наличия компонентов доступа к данным MDAC как минимум версии 2.6. Найдена установленная версия {0}. - - - Поставщик данных .NET Framework для OLE DB требует наличия компонентов доступа к данным MDAC как минимум версии 2.6. Найдена установленная версия {0}. - - - Интерфейс "{0}" не поддерживается поставщиком "{1}". GetOleDbSchemaTable недоступен в составе текущего поставщика. - - - Сбой "{0}" без сообщения об ошибке, код результата: {1}. - - - Сообщение об ошибке не предусмотрено, код результата: {0}. - - - Поставщики данных .NET Framework требуют наличия компонентов доступа к данным MDAC. Установите эти компоненты как минимум версии 2.6. - - - Поставщик данных .NET Framework для OLE DB (Microsoft.Data.OleDb) не поддерживает поставщик Microsoft OLE DB для драйверов ODBC (MSDASQL). Используйте поставщик данных .NET Framework для ODBC (System.Data.Odbc). - - - Поставщик данных .NET Framework для OLE DB не позволит поставщику OLE DB отправлять пользователю запросы в неинтерактивной среде. - - - Поставщик "{0}" не зарегистрирован на локальном компьютере. - - - Интерфейс ICommandText не поддерживается поставщиком "{0}". Используйте CommandType.TableDirect. - - - Интерфейс ITransactionLocal не поддерживается поставщиком "{0}". Локальные транзакции недоступны с помощью текущего поставщика. - - - Формат строки инициализации не соответствует спецификации OLE DB. Начинается с char[{0}] в строке подключения. - - - "Асинхронная обработка" не является поддерживаемой функцией поставщика данных .NET Framework для OLE DB (System.Data.OleDb). - - - В ConnectionString не задан поставщик OLE DB. Образец задания поставщика: "Provider=SQLOLEDB;". - - - Недопустимо большая длина указанного в ConnectionString поставщика OLE DB. - - - Для поля DbInfoKeywords из OleDbSchemaGuid ограничений не предусмотрено. - - - Для поля DbInfoLiterals из OleDbSchemaGuid ограничений не предусмотрено. - - - Для идентификатора схемы guid OleDbSchemaGuid ограничений не предусмотрено. - - - OleDbSchemaGuid {0} не является схемой, поддерживаемой поставщиком "{1}". - - - Параметр конфигурации "{0}" имеет неверное число значений. - - - Не удается загрузить файл XML, указанный в параметре конфигурации "{0}". - - - Параметр команды [{0}] "{1}" является недопустимым. - - - Невозможно преобразовать значение данных параметра команды [{0}] "{1}" по причине, отличной от несоответствия знака или переполнения данных. - - - Сбой преобразования для параметра команды[{0}] "{1}" из-за того, что значение данных имеет знак, а поставщик использует тип без знака. - - - Сбой преобразования параметра команды[{0}] "{1}" в силу того, что для значения данных произошло переполнение типа, используемого поставщиком. - - - Поставщик обнаружил ошибку при передаче значения параметра команды [{0}] "{1}" и прекратил обработку. - - - Параметр [{0}] "{1}" не имеет значения по умолчанию. - - - Ошибка, связанная с параметром [{0}]: {1}. - - - Внутренняя ошибка System.Data.OleDb.OleDbDataAdapter. Недопустимый параметрический метод доступа: {0} {1}. - - - Параметр Parameter[{0}]: свойство OleDbType не инициализировано: OleDbType.{1}. - - - Интерфейс ICommandWithParameters не поддерживается поставщиком "{0}". Параметры команды не поддерживаются текущим поставщиком. - - - Извлечение сведений о параметрах процедур не поддерживается поставщиком "{0}". - - - Не удается определить используемый сервером разделитель для десятичной дробной части. Невозможно создать нецелую числовую константу. - - - Объект не является ни ADODB.RecordSet, ни ADODB.Record. - - - Не удалось извлечь интерфейс "{0}" из объекта ADODB.RecordSet. + + {1}[{0}]: Свойство Size имеет недопустимый размер 0. - - Не удалось извлечь интерфейс IRow из объекта ADODB.Record. + + Для метода {0}.Prepare требуется, чтобы для всех параметров был явно указан тип. - - Тип не поддерживает интерфейс OLE DB ISourcesRowset + + Для метода {0}.Prepare требуется, чтобы всем параметрам переменной длины был в явном виде присвоен ненулевой Size. - - Не удается создать коллекцию схемы ReservedWords, поскольку поставщик не поддерживает IDBInfo. + + Для метода {0}.Prepare требуется, чтобы для параметров типа "{1}" были в явном виде заданы Precision и Scale. - - Значение свойства не задано, поскольку поставщик не поддерживает свойство "{0}" либо пользователь попытался получить или задать значения свойств, не входящих в группу свойств инициализации, после инициализации объекта источника данных. + + Вызов несоответствующего завершающего метода для asyncResult. Ожидался вызов {0}, но был вызван {1}. - - При попытке инициализации свойства "{0}" произошел сбой. Возможные причины: - Тип данных значения не соответствовал типу данных свойства и был определен. Например, для свойства DBPROP_MEMORYUSAGE (тип данных Int32) указано значение с типом данных Int64. - Недопустимое значение. Например, для свойства DBPROP_MEMORYUSAGE указано отрицательное значение. - Указано действительное значение, но поставщик, поддерживая настройку свойства, не поддерживает именно этого значения. Например, значение добавлено к свойству в OLE DB после написания поставщика. + + Недопустимая операция. Подключение закрыто. - - Недействительное значение Options. + + Невозможно включить в транзакцию, т.к. через данное подключение выполняется локальная транзакция. Завершите локальную транзакцию и повторите попытку. - - Недействительный элемент ColumnID. + + Для этого подключения транзакция уже внесена в список. Завершите текущую транзакцию и повторите попытку. - - По определению свойство "{0}" должно применяться ко всем столбцам, но для одного или нескольких из них сделать это невозможно. + + База данных не может быть пустой, не может являться пустой строкой или строкой, заполненной только пробелами или знаками табуляции. - - Свойство "{0}" доступно только для чтения либо потребитель пытался установить значения свойств в группе Initialization уже после инициализации объекта источника данных. Потребитель может присвоить свойству, которое доступно только для чтения, его текущее значение. Это состояние возвращается также, если настраиваемое свойство столбца не может быть установлено для определенного столбца. + + Недопустимое смещение в исходном буфере (размер: {0}): {1} - - Необязательному свойству "{0}" указанное значение присвоено не было и это невозможно. + + Недопустимое смещение в конечном буфере (размер: {0}): {1} - - Значение свойства "{0}" не было установлено, поскольку это привело бы к возникновению конфликта с существующим свойством. + + Недопустимая попытка {0} при закрытом потоке. - - (зарезервировано). + + Указанное значение SeekOrigin недействительно. - - Поставщик вернул неизвестное значение DBPROPSTATUS_ "{0}". + + Недопустимая попытка считывания из столбца с порядковым номером "{0}". С помощью CommandBehavior.SequentialAccess считывание разрешено только из столбцов с порядковым номером "{1}" и выше. - - Проверка корректности функции доступа была отложена и выполнена при возвращении методом данных. Для этого столбца или параметра привязка была недействительной. + + Недопустимый тип данных параметра {0}. - - Внутренняя ошибка OleDbDataAdapter: недопустимая функция доступа к набору строк: Ordinal={0} Status={1}. + + Отсутствует сопоставление типа объекта {0} с известным собственным типом управляемого поставщика. - - Невозможно преобразовать значение данных по причине, отличной от несоответствия знака или переполнения данных. Например, данные были повреждены в хранилище данных, но строку все еще можно считать. + + Не удалось обработать неизвестный код TypeCode {0}, возвращенный типом {1}. - - Данный поставщик не может выделить память, в которую возвращаются данные {0}. + + Отсутствует сопоставление DbType {0} с известным {1}. - - Сбой преобразования из-за того, что для значения данных {0} произошло переполнение типа, указанного для части значения {0} в буфере потребителя. + + Используемая версия SQL Server не поддерживает тип данных "{0}". - - Внутренняя ошибка OleDbDataAdapter: [get] Неизвестный тип данных OLE DB: 0x{0} ({1}). + + Значение параметра "{0}" вне допустимого диапазона. - - Сбой преобразования из-за того, что значение данных {0} имеет знак, а для части значения {0} в буфере потребителя указан тип без знака. + + Указанное имя параметра "{0}" является недопустимым. - - Внутренняя ошибка OleDbDataAdapter: [set] Неизвестный тип данных OLE DB: 0x{0} ({1}). + + Недопустимое значение параметра размера "{0}". Значение должно быть больше или равно 0. - - Поставщик не смог определить значение {0}. Например, строка только что была создана, значение по умолчанию для столбца {0} не было доступно, а потребитель еще не задал нового значения {0}. + + Недопустимое значение для аргумента "{0}". Значение должно быть больше или равно нулю. - - Поставщик OLE DB возвратил непредусмотренное значение состояния, {0}. + + Недопустимое значение для этих метаданных. - - OleDbDataReader.Read должен выполняться в том же потоке, к котором был создан, если значением свойства ApartmentState этого потока не было ApartmentState.MTA. + + Не удалось преобразовать значение параметра из {0} в {1}. - - Нераспознанная ошибка: {0} + + {0} не поддерживает параллельные транзакции. - - Сбой IErrorInfo.GetDescription с {0}. + + Данный {0} завершен; его повторное использование невозможно. - - Сбой IErrorInfo.GetSource с {0}. + + Указанная длина "{0}" находится вне допустимого диапазона. - - Поставщик данных .NET Framework для OLE DB (System.Data.OleDb) не поддерживает данные DBTYPE_VECTOR. + + Недопустимая попытка {2} для dataIndex "{0}". При CommandBehavior.SequentialAccess считывание разрешается только при dataIndex "{1}" и больше. Недопустимое значение минимального или максимального размера пула; минимальный размер пула не может быть больше максимального размера пула. - - Ключевое слово "{0}" является устаревшим. Используйте вместо него "{1}". - Не удалось получить от сервера адрес координатора распределенных транзакций (DTC) для сервера. Убедитесь, что координатор распределенных транзакций (DTC) на сервере включен. - - Длина значения параметра подключения <{0}> превышает предельно допустимую длину в 65 535 знаков. - Недопустимый "Packet Size". Значение должно быть целым числом в диапазоне от 512 до 32 768 включительно. Недопустимая транзакция или недопустимое имя для точки сохранения в транзакции. - - Значение перечисления {0}, {1}, не поддерживается SQL Server 7.0 и SQL Server 2000. - Параметры User Instance и Failover несовместимы. Используйте в строке подключения только один из них. @@ -2499,33 +540,21 @@ Не удается задать свойство "Credential", если в строке подключения указано "Authentication=Active Directory Integrated". - - Указаны неверные параметры проверки подлинности с использованием сертификата. - Экземпляр SQL Server, к которому устанавливалось подключение, требует шифрования, но оно не поддерживается этим компьютером. Экземпляр SQL Server, к которому устанавливалось подключение, не поддерживает шифрования. - - Экземпляр SQL Server, к которому вы пытались подключиться, не поддерживает CTAIP. - Неподдерживаемая версия SQL Server. Поставщик данных .NET Framework для SQL Server (SqlClient) поддерживает только SQL Server 7.0 и более поздние версии. Нельзя изменить {0}, когда выполняется асинхронная операция. - - Для этой команды необходимо асинхронное подключение. Укажите в строке подключения «Asynchronous Processing=true». - Истекло время ожидания для попытки подключения. - - Истекло время ожидания. Подключение разорвано. - Сбой экземпляра. @@ -2556,27 +585,18 @@ Код ошибки: 0x{0} - - Для ChangePassword требуется SQL Server 9.0 или более поздней версии. - Перенаправление экземпляра SSE не поддерживается для нелокальных пользовательских интерфейсов. Проверка подлинности "{0}" не поддерживается. - - Метод проверки подлинности SQL "{0}" не поддерживается. - Не удалось создать экземпляр поставщика проверки подлинности с типом "{1}" для "{0}". Не удалось создать экземпляр SqlAuthenticationInitializer с типом "{0}". - - При инициализации поставщика "{0}" возникло исключение. - Поставщик "{0}" не поддерживает проверку подлинности "{1}". @@ -2586,24 +606,15 @@ Не удается найти поставщик проверки подлинности для "{0}". Установите пакет NuGet "Microsoft.Data.SqlClient.Extensions.Azure" (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) для использования методов проверки подлинности Active Directory (Entra ID). - - Не удалось прочитать раздел конфигурации для поставщиков проверки подлинности. - Параметр «{0}» не может иметь значение NULL или быть пустым. - - Асинхронная операция уже завершилась. - Выполнение команды не может быть продолжено до завершения отложенной асинхронной операции. В ExecuteXmlReader передана недопустимая команда. Команда должна возвращать результат Xml. - - Для уведомлений требуется SQL Server 9.0 или более поздней версии. - Недопустимый трехэлементный формат имени для UdtTypeName. @@ -2697,9 +708,6 @@ Невозможно инициализировать пакет SSPI. - - Истекло время ожидания (Timeout). Время ожидания истекло до завершения операции или сервер не отвечает. - Истекло время ожидания соединения. Время ожидания подключения истекло на начале этапа предварительного согласования. Это могло произойти из-за недостаточно большого временем ожидания соединения. @@ -2745,12 +753,6 @@ В строке подключения был затребован пользовательский экземпляр, но указанный сервер не поддерживает этот параметр. - - Сертификат сервера не прошел проверку приложения. - - - Указанная длина данных {0} превышает максимально допустимую длину {1}. - Недопустимая попытка чтения при отсутствии данных. @@ -2775,9 +777,6 @@ Длина данных "{0}" меньше 0. - - Нельзя вызвать "{0}", если SqlDataRecord доступна только для чтения. - Сопоставленная коллекция уже используется и сейчас недоступна; @@ -2847,21 +846,6 @@ Подключение к контексту с помощью Microsoft.Data.SqlClient не поддерживается. - - Вложенные TransactionScopes не поддерживаются. - - - Недопустимое серверное событие: {0}. - - - Длина "{0}" должна соответствовать длине "{1}". - - - SqlDbType "{0}" недопустим для {1}. Поддерживается только {2}. - - - Слишком много значений. - Поток Stream не поддерживает записи. @@ -2886,192 +870,36 @@ NULL - - Сообщение - - - Арифметическое переполнение. - - - Ошибка деления на нуль. - - - Неопределенные данные. Этот метод или свойство не может вызываться для значений равных Null. - - - Арифметическая операция над числами вызвала усечение. - Переполнение SqlDateTime. Должно находиться в пределах от 1/1/1753 12:00:00 AM и 12/31/9999 11:59:59 PM. - - Две соединяемые строки имеют различный порядок следования. - - - Две сравниваемые строки имеют различный порядок следования. - - - Недопустимое значение флага. - - - Переполнение при преобразовании из SqlDecimal в Decimal. - Переполнение при преобразовании. - - Недопустимая структура SqlDateTime. - - - Указана временная зона. SqlDateTime не поддерживает временных зон. - Недопустимый размер массива. Недопустимая числовая точность или шкала. - - Входные данные имели неверный формат. - - - Ошибка SqlType. - - - В этой версии SqlBytes и SqlChars не поддерживают длину, превышающую 2 ГБ (2GB). - Возникла ошибка при чтении. - - Поток Stream закрыт или удален. - - - Нет буфера. Сбой операции чтения или записи. - - - Невозможно назначить ненулевую длину, поскольку текущее значение равно Null. - - - Недостаточный размер буфера. Сбой операции чтения или записи. - - - Невозможно выполнить запись с ненулевым смещением, поскольку текущее значение равно Null. - - - Невозможно выполнить запись со смещением, которое превышает текущую длину. При этом в буфере останутся неинициализированные данные. - Размер возвращенных данных превышает 2 ГБ (2Gb). Используйте поведение command behavior SequentialAccess для получения всех данных. - - Аргументом для GetDayOfWeek должно быть целое число в диапазоне от 1 до 7. - - - SQL Type не был загружен с данными. - - - SQL Type был загружен с данными. - - - Недопустимая попытка получить доступ к закрытому XmlReader. - - - Недопустимая попытка вызвать {0} при закрытом потоке. - - - Недопустимая попытка вызвать {0}, когда поток не поддерживает записи. - - - Недопустимая попытка вызвать {0}, когда поток не поддерживает чтения. - - - Недопустимая попытка вызвать {0}, когда поток не поддерживает поиска. - - - Подклассы не переопределяют необходимый метод. - Не удается создать нормализатор для "{0}". Внутренняя ошибка - - Параметр команды должен содержать непустой и определенный текст. - - - Длина массива параметров MetaData должна быть равна аргументу массива ParameterDirection. - - - Ошибка сопоставления адаптера данных. - - - Ошибка адаптера данных. - - - Нарушение параллелизма БД. - Операция прервана. Операция прервана из-за исключения (дополнительные сведения см. в InnerException). - - Будет ли Fill вызывать DataRow.AcceptChanges. - - - Будет ли Update вызывать DataRow.AcceptChanges. - - - Переходить ли к следующему DataRow, когда при выполнении Update после событий RowUpdating и RowUpdated возникает Status: UpdateStatus.ErrorsOccurred. - - - Как адаптер заполняет DataTable из DataReader. - - - Действие, выполняемое при отсутствии таблицы или столбца в TableMappings. - - - Действие, выполняемое при отсутствии таблицы или столбца в DataSet. - - - Как исходная таблица отображается в таблицу DataSet. - - - Событие создается при возникновении устранимой ошибки во время использования метода Fill. - - - Должен Fill возвращать особые значения поставщика или обычные значения CLSCompliant. - - - DataColumn.ColumnName - - - Имя исходного столбца - с учетом регистра. - - - Число элементов в данной коллекции - - - Указанный объект DataColumnMapping. - - - Сопоставления отдельных столбцов при соответствии сопоставления этой таблицы. - - - DataTable.TableName - - - Имя исходной таблицы DataTableMapping. Это имя задается с учетом регистра. - - - Число элементов в данной коллекции - - - Указанный объект DataTableMapping - Используется при обновлении для удаленных строк в DataSet. @@ -3090,63 +918,9 @@ Событие создается после каждого DataRow во время обновления. - - Количество строк, которые необходимо сгруппировать перед выполнением по отношению к источнику данных. - - - Подключение, которое используется, если у команд Select/Insert/Update/DeleteCommands еще нет подключения. - - - Используется при обновлении для удаленных строк в DataTable. - - - Используется при операции Update для новых строк в DataTable. - - - Используется при выполнении Fill. - - - Используется при выполнении Update для измененных строк в DataTable. - - - Должен Fill возвращать особые значения поставщика или обычные значения CLSCompliant. - - - Как исходная таблица отображается в DataTable. - - - Как создаются команды Insert/Update/DeleteCommands, если они не определены пользователем. - - - Количество строк, которые необходимо сгруппировать перед выполнением по отношению к источнику данных. - - - Строка подключения, используемая для подключения к источнику данных Data Source. - - - Имя драйвера ODBC, который следует использовать для подключения к источнику данных Data Source. - - - Имя источника данных DSN, которое следует использовать для подключения к источнику данных Data Source. - - - Когда true (истина), то указывает, что необходимо использовать управляемую группировку подключений. - - - Файл UDL, который следует использовать для подключения к источнику данных Data Source. - - - Указывает, какие службы OLE DB Services необходимо включить или выключить с поставщиком OleDb Provider. - - - Имя поставщика OLE DB, который следует использовать для подключения к источнику данных Data Source. - Имя приложения. - - При значении true (истина) позволяет использовать асинхронные функции в поставщике данных .NET Framework. - Имя первичного файла прикрепляемой базы данных, включая полный путь. @@ -3156,9 +930,6 @@ Когда true (истина), то указывает, что состояние подключения было сброшено при удалении из пула. - - Когда true (истина), то указывает, что подключение должно устанавливаться из контекста Sql Server. Доступно только, если запущен в процессе Sql Server. - Имя записи SQL Server Language. @@ -3252,54 +1023,9 @@ Указывает метод проверки подлинности с SQL Server. - - Указанный сертификат клиента для проверки подлинности в SQL Server. - - - Информация, используемая для подключения к источнику данных. - - - Время ожидания текущего подключения; не может задаваться в ConnectionString. - - - Значение каталога текущего источника данных, "Database=X" в строке подключения. - - - Текущий источник данных, "Server=X" в строке подключения. - - - Текущий драйвер ODBC. - - - Версия продукта, к которому производит доступ драйвер ODBC. - - - Информация, используемая для подключения к источнику данных. - - - Время ожидания текущего подключения; "Connect Timeout=X" в ConnectionString. - - - Значение каталога текущего источника данных, "Initial Catalog=X", в строке подключения. - - - Текущий источник данных, "Data Source=X", в строке подключения. - - - Текущий идентификатор ProgID поставщика OLE DB, "Provider=X" в строке подключения. - - - Версия продукта, к которому производит доступ поставщик OLE DB. - Токен доступа, который следует использовать для аутентификации. - - Состояние подключения (синхронное или асинхронное). "Asynchronous Processing=x" в строке подключения. - - - MILSCHK SS: Incorrect term. Please change as follows: Данные, использующиеся при подключении для репликации. - Информация, используемая для подключения к DataSource, например, "Data Source=x;Initial Catalog=x;Integrated Security=SSPI". @@ -3324,9 +1050,6 @@ Сбор статистических данных для этого подключения. - - Поставщики хранилища ключей шифрования пользовательского столбца. - Идентификатор guid для представления физического соединения. @@ -3363,99 +1086,9 @@ Автоматический связующий объект в уведомлениях, использующихся Microsoft SQL Server. - - Как конструкция Where автоматически создается для команд Update и Delete, если она не указана пользователем. - - - Указывает позицию имени каталога в полном имени таблицы в составе текстовой команды. - - - Знак, отделяющий имя каталога от остальной части идентификатора в текстовой команде. - - - Знак, отделяющий имя схемы от остальной части идентификатора в текстовой команде. - - - Строка префикса, маркирующая объекты sql. - - - Строка суффикса, маркирующая объекты sql. - - - DataAdapter, для которого будут автоматически создаваться команды. - - - Используйте схему из DataTable или SelectCommand. - - - Как конструкция Set автоматически создается для команды Update, если она не указана пользователем. - - - DataAdapter, для которого будут автоматически создаваться OdbcCommands - - - Знак, который используется в текстовых командах в качестве открывающей кавычки для заключения в кавычки идентификаторов, содержащих специальные знаки. - - - Знак, который используется в текстовых командах в качестве закрывающей кавычки для заключения в кавычки идентификаторов, содержащих специальные знаки. - - - DataAdapter, для которого будут автоматически создаваться OleDbCommands - - - Разделитель для десятичной дробной части, использующийся в числовых константах. - - - Строка префикса, маркирующая объекты sql - - - Строка суффикса, маркирующая объекты sql - DataAdapter, для которого будут автоматически создаваться SqlCommands - - Разделитель для десятичной дробной части, использующийся в числовых константах. - - - Знак, который используется в текстовых командах в качестве открывающей кавычки для заключения в кавычки идентификаторов, содержащих специальные знаки. - - - Знак, который используется в текстовых командах в качестве закрывающей кавычки для заключения в кавычки идентификаторов, содержащих специальные знаки. - - - Нужно устанавливать только для десятичных и числовых параметров в сценариях Prepare, FillSchema и CommandBuilder. - - - Нужно устанавливать только для десятичных и числовых параметров в сценариях Prepare, FillSchema и CommandBuilder. - - - Собственный тип параметра. - - - Собственный тип параметра. - - - Имя данного параметра, например, "@p1" - - - Собственный тип параметра. - - - Имя сервера для типа. - - - Смещение в типах данных переменной длины. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - Для параметра "{1}" не поддерживается указанное значение ParameterDirection, равное "{0}". Возвращающие табличное значение параметры Table-valued parameters поддерживают только ParameterDirection.Input. @@ -3468,9 +1101,6 @@ Имя TypeName, заданное для параметра "{0}". Имя TypeName следует устанавливать только для структурных параметров Structured. - - Тип SqlDbType.Structured поддерживается только для многозначных типов. - Столбец DateType для поля "{0}" в таблице схемы имеет значение NULL. Этот столбец должен иметь значение, отличное от null. @@ -3483,18 +1113,9 @@ Число полей в записи "{0}" не совпадает с числом полей в первоначальной записи. - - диапазон: 0-8000 - Непредвиденная ошибка в поставщике данных SqlClient: {0} - - Не удается получить данные о типе Type Info для {0},{1} - - - Поставщику не удалось загрузить следующую сборку {0} - Свойство UdtTypeName для параметров UDT должно быть установлено. @@ -3504,72 +1125,9 @@ Указанный тип не зарегистрирован на сервере назначения.{0}. - - Не допускается использование параметров UDT в составе конструкции Where, кроме случаев, когда они являются частью первичного ключа. - - - "{0}" представляет собой недопустимый определенный пользователем тип, причина: {1}. - - - поддерживаются и форматы в памяти, и форматы, определенные пользователем. - - - Собственный формат не поддерживается. - - - Отсутствует реализация IBinarySerialize - - - не подлежит сериализации - - - нет публичных конструкторов - - - Отсутствует реализация INullable - - - не имеет общего конструктора - нет атрибута UDT - - Сериализация без сопоставления пока не поддерживается. - - - Отсутствует метод "public static x Parse(System.Data.SqlTypes.SqlString)" - - - Отсутствует метод "public override string ToString()" - - - Отсутствует метод "public static x Null { get; }" - - - Собственный формат не поддерживает полей (напрямую или через другое поле) типа "{0}" - - - Не публичный тип - - - Native UDT имеет непоследовательный макет из-за типа "{0}" - - - Элемент Native UDT указывает максимальный размер в байтах (byte size) - - - поле "{0}" отмечено как несериализованное - - - Тип поля "{0}" помечен как явный макет, что не допускается в собственном формате - - - Многозначные ссылки на сборку должны иметь ненулевой идентификатор сборки. - - - Многозначные типы Structured можно использовать лишь для параметров, их нельзя вложить в другой тип. - Недопустимый путь. @@ -3585,9 +1143,6 @@ Сбой при попытке активизации транзакции. - - Объект зависимости, использующийся для получения уведомлений о запросах. - Свойство, позволяющее определить, допустима ли данная зависимость. @@ -3600,9 +1155,6 @@ Для добавления команды к существующему объекту зависимости. - - Команда Command уже назначена другому объекту зависимости. Переопределение невозможно. - Этот объект SqlCommand уже связан с другим объектом SqlDependency. @@ -3630,12 +1182,6 @@ Указано недействительное время ожидания Timeout. Время ожидания не может быть отрицательным. - - Ошибка уведомления. Type={0}, Info={1}, Source={2}. - - - GetMetaData недопустим для этого SqlDbType. - dbType {0} недопустим для этого конструктора. @@ -4104,9 +1650,6 @@ Внутренняя ошибка. Открытый текст не может иметь значение Null. - - Не удается зашифровать. В результате шифрования было создано {0} байт зашифрованных данных, что превышает максимально допустимое значение, равное {1} байтам. Указанное значение открытого текста вероятнее всего превышает максимально допустимый размер (размер открытого текста: {2} байт). - Внутренняя ошибка. Зашифрованные данные не могут иметь значение Null. @@ -4224,9 +1767,6 @@ SQL Server не вернул сведения об аттестации. Тип анклава — "{0}", а URL-адрес аттестации анклава — "{1}". - - Не удается установить безопасный канал. Сообщение об ошибке: {0} - Внутренняя ошибка. При создании объекта типа "{1}" указан аргумент, равный NULL: "{0}". "{0}" не может быть равен NULL. @@ -4242,15 +1782,6 @@ Указывает конечную точку службы аттестации анклава, которая будет использоваться для проверки того, допустим и безопасен ли анклав, настроенный в экземпляре SQL Server для вычислений в столбцах базы данных, зашифрованных с помощью постоянного шифрования. - - Не удалось прочитать раздел конфигурации для поставщиков анклава. Убедитесь, что раздел правильно отформатирован в файле конфигурации приложения. Сообщение об ошибке: {0} - - - Не удалось создать экземпляр поставщика анклава с типом "{1}" для имени "{0}". Сообщение об ошибке: {2} - - - Внутренняя ошибка. SqlColumnEncryptionEnclaveProviderName не может быть пустым или иметь значение NULL. - Возникла ошибка при чтении набора результатов "{0}". Запрос требует вычислений на основе анклава, но в строке подключения не указан URL-адрес аттестации. Тип анклава — "{1}". @@ -4260,9 +1791,6 @@ Внутренняя ошибка. При выполнении запроса, требующего вычислений анклава, из SQL Server получен пустой или равный NULL тип. - - Для выполнения запроса требуются вычисления анклава, но в конфигурации приложения отсутствует раздел поставщика анклава. - Не найден поставщик для анклава типа "{0}" и протокола аттестации "{1}". Укажите корректный протокол аттестации в строке подключения. @@ -4320,9 +1848,6 @@ Параметр шифрования столбца по умолчанию для всех команд подключения. - - Выполняет принудительное шифрование параметра перед отправкой конфиденциальных данных на сервер. - Объект словаря, содержащий имена серверов SQL Server и пути к их доверенным главным ключам столбцов. @@ -4332,9 +1857,6 @@ Соединение с зеркальным экземпляром SQL Server с параметром ApplicationIntent, установленным в значение ReadOnly, не поддерживается. - - Получены непредвиденные сведения о маршрутизации. - Получены недопустимые данные маршрутизации. @@ -4485,9 +2007,6 @@ Указан неподдерживаемый в этом контексте режим проверки подлинности: {0} - - Истекло время ожидания интерактивной проверки подлинности Active Directory. Пользователь слишком долго не отвечал на запрос проверки подлинности. - Невозможно использовать Authentication=Active Directory Interactive, если задано свойство Credential. @@ -4497,9 +2016,6 @@ Идентификатор серверного процесса (SPID) активного подключения. - - Истекло время ожидания для проверки подлинности потока кода устройства Active Directory. Пользователь слишком долго не отвечал на запрос проверки подлинности. - Не удается задать свойство Credential, если в строке подключения указано "Authentication=Active Directory Device Code Flow". @@ -4533,9 +2049,6 @@ Для запрашиваемого действия поддерживаются только одномерные массивы. - - Невозможно удалить указанный элемент, так как он не найден в заданной коллекции. - Требуется неотрицательное число. @@ -4563,87 +2076,36 @@ Если в строке подключения указан параметр "Authentication=Active Directory Default", свойство AccessTokenCallback задать не удастся. - - Операция {0} возвратила {1}. - - - Ошибка протокола: полученное сообщение содержит действительную подпись, но она не была зашифрована в соответствии с действующим уровнем защиты. - - - Произошел сбой {0} с ошибкой {1}. - Этот метод в данном классе не реализован. - - Затребованный пакет безопасности не поддерживается. - Неверный тип physicalConnection. Возникло исключение при попытке настроить параметр AppContext "{0}"={1}. - - Тип {0} не поддерживается этой платформой. - Инвариантный режим глобализации не поддерживается. - - Невозможно выполнить проверку подлинности с помощью Kerberos. Убедитесь, что Kerberos инициализирован в клиенте с помощью "kinit", а имя субъекта-службы зарегистрировано в SQL Server для разрешения проверки подлинности Kerberos. - Ключевое слово "Библиотека сети" не поддерживается этой платформой. Вместо него используйте для параметра "Источник данных" префикс с требуемым протоколом ("tcp:" для TCP-подключения или "np:" для подключения к именованному каналу). Сокет не вызвал ожидаемый "{0}" с кодом ошибки "{1}". - - Сервер пытается использовать функцию, которая не поддерживается этой платформой. - Ключевое слово "{0}" не поддерживается этой платформой. Загруженная библиотека System.Transactions.dll не поддерживает глобальные транзакции. - - Получен неподдерживаемый токен "{0}" при чтении данных с сервера. - - - Тип дескриптора "{0}" не поддерживается. - SqlFileStream не поддерживается этой платформой. LocalDB не поддерживается этой платформой. - - Недостаточно места в буфере. Требуется: {0}, фактическое значение: {1}. - - - Сбой операции GSSAPI. Состояние: {0} (дополнительное состояние: {1}). - - - Операция GSSAPI завершилось ошибкой — {0} ({1}). - - - Привязка канала не поддерживается в операционных системах, отличных от Windows. - - - Если переданы учетные данные по умолчанию, имя целевого объекта не должно быть пустым. - - - Запрошенный уровень защиты не поддерживается при установленной в настоящее время реализации GSSAPI. - - - Реализация сервера не поддерживается. - - - Невозможно выполнить проверку подлинности NTLM с учетными данными по умолчанию на этой платформе. - При использовании в DataAdapter.Update значение параметра изменяется с DBNull.Value на (Int32)1 или (Int32)0, если определено. @@ -4668,9 +2130,6 @@ Превышен максимальный размер буфера: {0} - - Отсутствует DataColumn "{0}" для данного SourceColumn "{2}". - Список SqlBatchCommand не инициализирован. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx index b796999eab..65a0046cfb 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Artan düzende - Bu {1} için geçersiz {0} dizini, Count={2}. @@ -132,18 +129,12 @@ {0} zaten başka bir {1} içinde yer alıyor. - - {0} zaten bu {1} içinde yer alıyor. - {0}, yalnızca null olmayan {1} türündeki nesneleri kabul eder. Bu {1} içinde yer almayan bir {0} kaldırılmaya çalışıldı. - - {0}.{1} benzersiz olmalıdır, '{2}' zaten koleksiyonda var. - Bağlantı kapatılmadı. {0} @@ -171,12 +162,6 @@ Okuyucu kapalıyken {0} öğesini çağırma girişimi geçersiz. - - Bağlantı, temsilci seçilen veya yükseltilen bir işlem için birincil bağlantı olduğundan işleme kaydedilemiyor. - - - Azalan düzende - '{0}' parametresi için boş olmayan dize bekleniyor. @@ -204,9 +189,6 @@ ConnectionString özelliği başlatılmamış. - - CLS olmayan bir Exception yakalandı. - Verilen güvenlik öğesi, izin öğesi değil. @@ -252,9 +234,6 @@ Veri - - StateChange - Güncelleştir @@ -264,48 +243,12 @@ Bağlantının açık ya da kapalı olduğunu gösteren ConnectionState. - - Bağlantının durumu değiştiğinde harekete geçirilen olay. - - - Parametre genel türü. - - - Giriş veya çıkış parametresi veya çift yönlü parametre. - - - türü kesin olarak belirlenmiş kod oluşturma için kullanılan bir tasarım zamanı özelliği. - - - Değişken uzunlukta veri türlerinde uzaklık. - - - Parametrenin adı. - - - Değişken uzunluklu veri türlerinin boyutu (dize ve diziler). - - - Bir DataAdapter.Update tarafından kullanıldığında, ColumnMappings'de DataSetColumn adını bulmak için kullanılan kaynak sütun adı. Bunun amacı parametre ve veri satırı arasında bir değer kopyalamaktır. - - - Bir DataAdapter.Update tarafından kullanıldığında (yalnızca UpdateCommand), DataRow değerinin veri kaynağını güncelleştirmek için kullanılan sürümü. - - - DataAdapter.Update tarafından kullanıldığında, parametre değeri DBNull.Value değerinden (Int32)1 veya null değilse, (Int32)0 değerine değiştirilir. - - - Parametrenin değeri. - SQL sorgusu yürütülemediğinden '{0}' koleksiyonu oluşturulamıyor. Ayrıntılar için iç özel duruma bakın. İstenen şemanın ('{0}') desteklediğinden daha çok kısıtlama sağlandı. - - '{2}', '{0}' şema koleksiyonunun '{1}' kısıtlaması için geçerli bir değer değil. - İstenen koleksiyon ({0}) tanımlı değil. @@ -315,15 +258,6 @@ İstenen ({0}) koleksiyonu, sağlayıcının bu sürümü tarafından desteklenmiyor. - - Gereken DataSourceInformation tablolarından birinin gerekli sütunları eksik. - - - DataSourceInformation tablosu tam olarak bir satır içermelidir. - - - Gereken kısıtlamalar koleksiyonu sütunlarından bir veya daha fazlası eksik. - Kısıtlamalar koleksiyonunda kendisiyle eşleşen bir satır olmayan bir kısıtlama var. @@ -342,42 +276,21 @@ Meta veri XML’inde '{0}' koleksiyonu eksik. - - Meta veri XML’i geçersiz. - - - Meta veri XML’i geçersiz. {0} koleksiyonunun bir {1} sütunu içermesi ve bunun bir dize sütunu olması gerekiyor. - Meta veri XML’i geçersiz. {0} koleksiyonunun {1} sütunu boş olmayan bir dize içermeli. - - Eylem - - - Davranış - Doldur Bilgiİletisi - - Eşleme - StatementCompleted - - UDT - Bildirim - - Şema - XML @@ -393,9 +306,6 @@ Biriktirme - - Adlandırılmış ConnectionString - Güvenlik @@ -408,2073 +318,204 @@ Bağlantı Dayanıklılığı - - Özel kullanıcı bilgilerini tutan koleksiyon. - - - DataSet içinde dize karşılaştırmanın büyük/küçük harf duyarlı olup olmadığını gösterir. - - - Bu DataSet'in adı. - - - DataSet'in içerdiği verilerin özel bir "görünümünü" gösterir. Bu görünüm filtrelemeye, aramaya ve özel veri görünümünde gezinmeye olanak sağlar. - - - Kısıtlama kurallarına uyulup uyulmayacağını gösterir. - - - DataSet'te hata olduğunu gösterir. - - - DataSet içindeki dizeleri karşılaştırmada esas alınan yerel ayarı gösterir. - - - Bu DataSet'in işaret ettiği kök öğesinin XML URI ad alanını gösterir. - - - Bu DataSet için kullanılan ad alanının önekini gösterir. - - - Bu DataSet'in ilişkilerini tutan koleksiyon. - - - Bu DataSet'in tablolarını tutan koleksiyon. - - - Aynı ada sahip iki tablonun şemalarını birleştirmek mümkün olmadığında ortaya çıkar. - - - Initialization bitirildikten sonra oluşur. - - - Verilerin alındığı bellek içi önbelleğini gösterir. - - - DataSet içinde dizi karşılaştırmanın büyük/küçük harf duyarlı olup olmadığını gösterir. - - - Bu tablonun alt ilişkilerini döndürür. - - - Bu tablonun sütunlarını tutan koleksiyon. - - - Bu tablonun kısıtlamalarını tutan koleksiyon. - - - Bu tablonun ait olduğu DataSet'i gösterir. - - - Bu, tablonun varsayılan DataView'ıdır. - - - Bu satırın veriye bağlı hesaplamada kullanılan ifade. - - - Tabloda hata olup olmadığını döndürür. - - - Tablo içindeki dizeleri karşılaştırmada esas alınan yerel ayarı gösterir. - - - Bu tablo için başlangıç boyutunu gösterir. - - - Bu tablo içindeki öğelerin XML URI ad alanını gösterir. - - - XML gösteriminde bu tablo için kullanılan alan adının Önekini gösterir. - - - Bu tablonun üst ilişkilerini döndürür. - - - Bu tablonun birincil anahtarını oluşturan sütunları gösterir. - - - Bu tablonun veri satırlarını tutan koleksiyonu gösterir. - - - Bir DataSet'in Tables koleksiyonunda bu tabloyu aramak için kullanılan adı gösterir. - - - Tablodaki bir sütun başarıyla düzenlendiğinde gerçekleşir. - - - Olay işleyicisinin değişikliği değiştirebilmesi veya iptal edebilmesi için satır değiştirilirken gerçekleşir. Kullanıcı, satırdaki değerleri değiştirebilir ve düzenlemeyi iptal etmek için bir özel durum oluşturması gerekir. - - - Tablodaki bir sütun başarıyla silindiğinde gerçekleşir. - - - Tablodaki bir satır silinmek üzere işaretlendiğinde gerçekleşir. Silme işlemini iptal etmek için özel bir durum oluşturun. - - - Bu sütunun değerlerinden biri gönderildiğinde ortaya çıkar. Kullanıcı önerilen değeri değiştirebilir ve düzenlemeyi iptal etmek için bir özel durum oluşturması gerekir. - - - Bu sütunun değerlerinden biri değiştiğinde ortaya çıkar. - - - Tablodaki tüm satırlar temizlenmeden önce gerçekleşir. - - - Tablodaki tüm satırlar başarılı bir şekilde temizlendikten sonra gerçekleşir. - - - Yeni bir DataRow örneği oluşturulduktan sonra gerçekleşir. - - - Bir DataSet'in Relations koleksiyonunda bu ilişkiyi aramak için kullanılan ad. - - - Bu ilişkinin alt sütunlarını gösterir. - - - Bu ilişkinin üst sütunlarını gösterir. - - - İlişkilerin iç içe olup olmadığını gösterir. - - - Silme işlemleri için bu ilişkide ne tür bir basamaklandırmanın gerçekleşmesi gerektiğini belirtir. - - - Güncellemeler için bu ilişkide ne tür bir basamaklandırmanın gerçekleşmesi gerektiğini belirtir. - - - Değişiklikleri kabul etmek ve reddetmek için bu ilişki boyunca ne tür bir basamaklandırmanın gerçekleşmesi gerektiğini belirtir. - - - Bu kısıtlamanın alt sütunlarını gösterir. - - - Bu kısıtlamanın üst sütunlarını gösterir. - - - Bu ilişkinin alt tablosunu gösterir. - - - Bu kısıtlamanın sütunlarını gösterir. - - - Kısıtlamanın bir birincil anahtar olup olmadığını gösterir. - - - Bu kısıtlamanın adını gösterir. - - - Bu kısıtlamanın tablosunu gösterir. - - - Bu sütunda null değerlere izin verilip verilmediğini gösterir. - - - Tabloya eklenen yeni eklenen satırlar için sütunun otomatik olarak kendini artırıp artırmadığını belirtir. Bu sütunun türü Int16, Int32, or Int64 olmalı. - - - AutoIncrement bir sütun için başlangıç değerini gösterir. - - - AutoIncrement bir sütun tarafından kullanılan artım miktarını gösterir. - - - Bu sütunun varsayılan kullanıcı arabirimi başlığını gösterir. - - - Bir DataTable'ın Columns koleksiyonunda bu sütunu aramak için kullanılan adı gösterir. - - - Bu sütunun ait olduğu DataTable'ı döndürür. - - - Bu sütunda depolanan veri türünü gösterir. - - - Tabloya yeni satırlar eklenirken kullanılan varsayılan sütun değerini gösterir. - - - Bu sütunun, her bir satır için kullanıcıdan girdi almak yerine diğer sütunlara dayanarak hesapladığı değeri gösterir. - - - Bu sütunun XML'de ne olarak bulunduğunu gösterir: öznitelik, öğe, basit içerik düğümü veya hiçbir şey. - - - Bu sütunda saklanan öğe ve özniteliklerin XML URI'sini gösterir. - - - xml gösteriminde bu DataColumn için kullanılan Prefix'i gösterir. - - - Bu sütunun Columns koleksiyonundaki dizin numarasını gösterir. - - - Tabloya bir satır eklendikten sonra bu sütunun değişikliklere izin verip vermeyeceğini gösterir. - - - Bu sütunun, tablonun satırlarındaki değerlerini benzersiz olacak şekilde kısıtlayıp kısıtlamayacağını gösterir. - - - Bu sütunun izin verdiği en fazla değer uzunluğunu gösterir. - - - Bu DataColumn'ın DateTimeMode'unu gösterir. - - - Bu DataView ve ilişkili kullanıcı arabiriminin silmeye izin verip vermediğini gösterir. - - - Bu DataView ve ilişkili kullanıcı arabiriminin düzenlemeye izin verip vermediğini gösterir. - - - Bu DataView ve ilişkili kullanıcı arabiriminin yeni satır eklemeye izin verip vermediğini gösterir. - - - Bu görünümdeki geçerli öğe sayısını döndürür. - - - Bu, DataSet'in (varsa) sahibi olan DataViewManager'a geriye bir işaretçi döndürür. - - - Görünümün açık olup olmadığını belirtir. - - - Bu DataView tarafından döndürülen verileri filtrelemek için kullanılan ifadeyi gösterir. - - - Bu DataView tarafından döndürülen veri sürümlerini gösterir. - - - Bu DataView tarafından veri döndürmek için kullanılan sütun adlarını ve düzeni gösterir. - - - Sort özelliği ayarlı olmadığında varsayılan sıralamanın kullanılıp kullanılmayacağını gösterir. - - - Bu DataView'un veri almak için kullandığı tabloyu gösterir. - - - Bu DataView tarafından döndürülen verilerin bir şekilde değiştiğini gösterir. - - - Bu DataViewManager için veri kaynağını gösterir. - - - İlgili DataSet içindeki herhangi bir tablonun sıralama/filtreleme/durum ayarlarını gösterir. - - - DataSet, 'union' ve 'list' türlerini simpleType olarak desteklemiyor. - - - Geçersiz {0} sözdizimi: gerekli '{1}' özniteliği eksik. - - - '{1}' değeri '{0}' özniteliği için geçersizdir. - - - '{0}' özniteliğinin değeri '{1}' veya '{2}' olmalı. - - - Bulunamayan ElementType name='{0}'. - - - '{0}' ilişkisinde üst tablo adı eksik. - - - '{0}' ilişkisinde alt tablo adı eksik. - - - '{0}' ilişkisinde üst tablo anahtarı eksik. - - - '{0}' ilişkisinde alt tablo anahtarı eksik. - - - Tanımlanmamış veri türü: '{0}'. - - - Veri türü tanımlı değil. - - - Alan düğümünde geçersiz XPath seçimi. {0} bulunamıyor. - - - Seçici düğümünde geçersiz XPath seçimi: {0}. - - - {0} adlı kısıtlamada geçersiz 'Key' düğümü. - - - {0} kısıtlama adı bu şemada zaten kullanılıyor. - - - '{0}', '{1}' türüne dönüştürülemiyor. - - - '{2}' adlı '{1}' kısıtlamasında '{0}' kısmı eksik. - - - Geçersiz Relation tanımı: anahtarlar farklı uzunlukta. - - - DataSet, '{0}' adlı ComplexType içindeki döngüsel başvuruya izin vermiyor. - - - DataSet, {0} düğümü için soyut ComplexType örneği oluşturamıyor. - - - Bir Xml Şeması yazılırken çoklu hedef dönüştürücüde bir hata oluştu. Ayrıntılar için iç özel duruma bakın. - - - Bir Xml Şeması yazılırken çoklu hedef dönüştürücüde bir hata oluştu. Null veya boş bir dize döndürüldü. - - - Yinelenen bildirim '{0}'. - - - diffGram yüklenemiyor. Hedef veri kümesinde '{0}' tablosu eksik. - - - diffGram yüklenemiyor. 'sql' düğümü eksik. - - - Sütun adı '{0}' farklı eşleştirme türleri için tanımlı. - - - Önek '{0}' geçerli değil, özel karakterler içeriyor. - - - Kendi kendine iç içe '{0}' tablosunda döngüsel başvuru. - - - DataSet varlıkları genişletemiyor. XmlValidatingReader kullanın ve EntityHandling özelliğini buna göre ayarlayın. - - - '{0}' türü IXmlSerializable arabirimini uygulamadığından serileştirme işlemi sürdürülemiyor. - - - Seri durumdan çıkarma işlemine devam edilemiyor. Veriler IXMLSerializable uygulamadığından, değişik aşamalardan geçme desteklenmiyor. - - - DataTable, Xml'den şema çıkarsamayı desteklemiyor. - - - DataTable '{0}' seri hale getirme işlemine devam edilemiyor. Aynı Yabancı Anahtarda birden çok üst satırı olan bir DataRow içeriyor. - - - Girdi Schema içinde IsDataSet özniteliği eksik. - - - DataSet Öğesi belirlenemiyor. IsDataSet özniteliği birden çok kez var. - - - DataSet, IDynamicMetaObjectProvider'ı uygulayan ancak beraberinde IXmlSerializable'ı da uygulamayan türleri seri hale getirmez. - - - Özellik uygulanmadı. {0}. - - - Söz dizimi hatası: ‘{0}’ işlecinden sonra işlenen eksik. - - - '{0}' ifadesinde tür uyuşmazlığı. - - - İfade çok karmaşık. - - - [{0}] sütunu bulunamadı. - - - İfade geçersiz dize sabiti içeriyor: {0}. - - - İfade, tanımlanmamış işlev çağrısı içeriyor: {0}(). - - - İfadede sözdizimi hatası var. - - - Geçersiz sayıda bağımsız değişken: işlev {0}(). - - - İfadenin kapanış parantezi eksik. - - - {1} konumundaki '{0}' belirteci yorumlanamıyor. - - - {0} bekleniyor ancak {2} konumundaki belirteç gerçekte {1}. - - - {0} ile {1} arasında dönüştürme yapılamıyor. - - - '{0}' değeri, {1} Türüne dönüştürülemiyor. - - - Geçersiz sütun adı [{0}]. - - - İfade, geçersiz bir tarih sabiti içeriyor: '{0}'. - - - IN işlecinin ifade listesinde yalnızca sabit ifadelere izin verilir. - - - Like işlecinde hata: Dize deseni '{0}' geçersiz. - - - Sözdizimi hatası: IN anahtar sözcüğünü izleyen öğeler virgülle ayrılmış ve parantez içine alınmış olmalıdır. - - - İşlev bağımsız değişkeninde tür uyuşmazlığı: {0}(), bağımsız değişken {1}, {2} bekleniyor. - - - İşlev bağımsız değişkeninde tür uyuşmazlığı: {0}(), bağımsız değişken {1}, Tamsayı türlerinden biri bekleniyor. - - - {1} ve {2} üzerinde '{0}' işlemi gerçekleştirilemiyor. - - - '{0}' işleci, '{1}' ve '{2}' türündeki işlenenler üzerinde belirsizdir. İmzalı ve imzasız türler karıştırılamaz. Lütfen açık Convert() işlevini kullanın. - - - Sözdizimi hatası: IN anahtar sözcüğünün arkasından boş olmayan bir virgülle ayrılmış ifade listesi gelmelidir ve ayrıca parantez içine alınmış olmalıdır. - - - İfade, desteklenmeyen '{0}’ işlecini içeriyor. - - - İfade geçersiz bir ad içeriyor: '{0}'. - - - Söz dizimi hatası: ‘{0}’ işlecinden önce işlenen eksik. - - - İfadede çok fazla sayıda kapanış parantezi var. - - - [{0}] tablosu birden fazla ilişkide yer alır. '{1}' ifadesinde açıkça bir ilişki adından bahsetmelisiniz. - - - Toplama bağımsız değişkeninde sözdizimi hatası: 'Child' niteleyicisine sahip olma ihtimali olan tek bir sütun argümanı bekleniyor. - - - '{0}' toplama ifadesinde bağlı olmayan başvuru. - - - Geçerli satır olmadan sabit olmayan ifade değerlendirilemez. - - - '{0}' ifadesinde bağlı olmayan başvuru. - - - Değerlendirilemiyor. '{0}' ifadesi bir toplama değil. - - - '{0}' filtre ifadesi, bir Boole koşulu sonucu vermiyor. - - - Geçersiz tür adı: ‘{0}’. - - - Arama ifadesinde sözdizimi hatası: 'Parent' anahtar sözcüğü ve ardından olası ilişki niteleyicisine sahip tek bir sütun bağımsız değişkeni bekleniyor: Parent[(<relation_name>)].<column_name>. - - - DataFilter'ı Çağırmak için satır veya tablo gerekli. - - - {0}() bağımsız değişkeni aralığın dışında. - - - Sözdizimi hatası: Geçersiz 'Is' işleci kullanımı. Doğru sözdizimi: <expression> Null [Not]. + + Uzaklık ve uzunluk dizi sınırlarının dışında yada sayı, dizinden kaynak koleksiyonun sonuna kadar olan öğelerin sayısından daha büyük. - - Değer, '{0}' Türü için çok büyük ya da çok küçük. + + '{0}' türündeki nesne '{1}' türündeki nesneye dönüştürülemiyor. - - Sıfıra bölme hatasıyla karşılaşıldı. + + '{0}' özelliği için yalnızca '{1}' değeri kabul edilebilir. - - '{0}' üst ilişkisi bulunamadı. + + '{0}' özelliği için '{1}' veya '{2}' değeri kabul edilebilir. - - 'hours' bağımsız değişkeni geçerli aralığın dışında. Değer -14 ile +14 arasında olmalıdır. + + Belirtilen QuotePrefix ve QuoteSuffix değerleri eşleşmiyor. - - 'minutes' bağımsız değişkeni aralığın dışında. Değer -59 ile +59 arasında olmalıdır. + + '{0}' bağımsız değişkeninin uzunluğu, bu değişkenin '{1}' sınırını aştı. - - İlk zaman için sağlanan aralık, toplamda 14 saati aşıyor. + + '{0}' anahtarı için geçersiz değer. - - Sağlanan DateTime bağımsız değişkeninin Kind özelliği, 'hours' ve 'minutes' bağımsız değişkenleriyle eşleşmiyor. + + '{0}' anahtarının kullanımı için '{1}' anahtarının olması gerekir. - - '{0}’ türünde DataColumn, ifadeyi desteklemiyor. + + '{0}' anahtarına ait değerin uzunluğu, anahtarın '{1}' sınırını aştı. - - Kısıtlamalar etkinleştirilemedi. Bir veya daha fazla satır; null olmama, benzersiz olma veya yabancı anahtar kısıtlamalarını ihlal eden değerler içeriyor. + + Anahtar sözcük desteklenmiyor: '{0}'. - - Koleksiyonun kendisi değiştirilebilir değil. + + Dahili .NET Framework Veri Sağlayıcısı hatası {0}. - - Verilen ad ‘{0}’, koleksiyon nesnesinde büyük/küçük harf uyumu gözetilmediğinde en az iki adla eşleşiyor ancak gözetildiğinde ikisi ile de eşleşmiyor. + + {0} "{1}". - - Verilen ad ‘{0}’, koleksiyon nesnesinde, ad alanları farklı olan en az iki adla eşleşiyor. + + {0} "{1}", tırnak işaretlerinin yanlış kullanımı. - - Uzaklık ve uzunluk dizi sınırlarının dışında yada sayı, dizinden kaynak koleksiyonun sonuna kadar olan öğelerin sayısından daha büyük. + + {0} "{1}", geçerli "{2}" sınırı yetersiz. - - '{0}' bağımsız değişkeni aralığın dışında. + + SqlBulkCopy.DestinationTableName geçersiz bir çok parçalı ad olduğundan SqlBulkCopy.WriteToServer başarısız oldu - - '{0}' bağımsız değişkeni null olamaz. + + Geçersiz bir çok parçalı ad nedeniyle SQL Server sonuçları işlenemedi - - '{0}' bağımsız değişkeni null değer içeriyor. + + SqlParameter.UdtTypeName geçersiz bir çok parçalı ad - - {0} sütunu bulunamadı. + + SqlParameter.TypeName, geçersiz bir çok parçalı ad - - '{0}' sütunu zaten DataTable'a ait. + + SqlCommand.CommandText özelliğinin değeri, geçersiz çoklu bir ad olduğundan SqlCommand.DeriveParameters başarısız oldu - - '{0}' Sütunu zaten başka bir DataTable'a ait. + + MultiSubnetFailover bağlantı seçeneğiyle yansıtılmış bir SQL Server örneğine bağlanma desteklenmiyor. - - DataTable içinde birden fazla SimpleContent sütun olamaz. + + {0} numaralandırma değeri {1}, {2} metodu tarafından desteklenmiyor. - - Öğe sütunları veya iç içe ilişkiler içeren tabloya SimpleContent bir sütun eklenemez. + + {0} numaralandırma değeri, {1}, .NET Framework SqlClient Veri Sağlayıcısı tarafından desteklenmiyor. - - Bu DataTable'a ait '{0}' adlı bir sütun zaten var. + + {0}: CommandText özelliği başlatılmamış - - '{0}' adlı sütun eklenemez: bu DataTable'da aynı ada sahip bir iç içe tablo zaten var. + + {0}: Connection özelliği başlatılmamış. - - Bu DataTable'da '{0}' adlı bir sütun zaten var: aynı ada sahip bir iç içe tablo olamaz. + + {0} için açık ve kullanılabilen bir Connection gerekiyor. {1} - - Bu tabloya ait olmayan bir sütun kaldırılamaz. + + '{0}' saklı yordamı yok. - - Birincil anahtarın bir parçası olduğundan bu sütun silinemez. + + İşlem geçerli bağlantıyla ilişkili değil veya tamamlanmış. - - {0} ilişkisinin üst anahtarının bir parçası olduğundan bu sütun silinemez. + + Geçerli bağlantıyla ilişkili işlem tamamlandı, ancak atılmadı. Bağlantının SQL deyimlerini yürütmek için kullanılabilmesi için işlemin atılması gerekiyor. - - {1} tablosundaki {0} kısıtlamasının bir parçası olduğundan bu sütun silinemez. + + {0}, komuta atanan bağlantı bekleyen bir yerel işlemde olduğunda komutun bir işleme sahip olmasını gerektirir. Komuta ait Transaction özelliği başlatılmadı. - - {0} = {1} ifadesinin bir parçası olduğundan bu sütun silinemez. + + Bu {0} ile ilişkili, öncelikle kapatılması gereken açık bir DataReader zaten var. - - Hesaplanan bir sütun için AutoIncrement özelliği ayarlanamaz. + + {0} DeriveParameters yalnızca CommandType.StoredProcedure'ı destekler, CommandType.{1} desteklemez. - - DefaultValue değeri ayarlanmış bir sütun için AutoIncrement özelliği ayarlanamaz. - - - AutoIncrement bir sütuna DefaultValue ayarlanamaz. - - - AutoIncrementStep, sıfır dışında bir değer olmalıdır. - - - Bir DataTable'ın parçası olduğunda ColumnName gereklidir. - - - İçinde veri olan bir sütunun DataType'ı değiştirilemez. - - - Sütun, geçerli bir DataType gerektirir. - - - {0} sütununun DefaultValue değerinin türü {1} ve {2} türüne dönüştürülemez. - - - Sütunun DefaultValue değerinin türü {0} ve {1} türüne dönüştürülemez. - - - {0} sütununun DefaultValue değerinin türü {1}, ancak sütunun türü {2}. - - - İfade sütununun ReadOnly özelliği değiştirilemez. - - - İfade sütununun Unique özelliği değiştirilemez. - - - AutoIncrement veya Unique olan bir sütun için ifade oluşturulamaz. - - - Sütun ReadOnly yapılamadığından ifade ayarlanamaz. - - - Bir kısıtlamanın parçası olduğundan, {0} sütunu için Expression özelliği ayarlanamaz. - - - {0} Expression sütununa dayanan bir kısıtlama oluşturulamaz. - - - İfadede döngüsel bir başvuru olduğundan Expression özelliği ayarlanamaz. - - - '{0}' Sütunu null değerler içeriyor. - - - '{0}' Sütunu null değerlere izin vermiyor. - - - '{0}' sütunu salt okunur. - - - '{0}' Column sütunu benzersiz olmayan değerler içeriyor. - - - '{0}' sütunu, {1} tablosuna ait değil. - - - Sütun bir tabloya ait olmalıdır. - - - <{0}> {1} Sütununda depolanamadı. Beklenen tür {2}. - - - '{0}' Sütunu null olarak ayarlanamaz. Lütfen bunun yerine DBNull kullanın. - - - '{0}' sütunu ayarlanamıyor. Değer bu sütunun MaxLength sınırını ihlal ediyor. - - - MaxLength yalnızca dize veri türü için geçerlidir. '{0}' Sütunu MaxLength özelliğini negatif olmayan bir sayı olarak ayarlayamazsınız. - - - '{0}' Sütunu MaxLength özelliği '{1}' olarak ayarlanamaz. Tabloda yeni sınırdan daha uzun en az bir dize var. - - - Column '{0}' MaxLength özelliği ayarlanamıyor. Sütun SimpleContent. - - - '{0}' Sütunu DataType özelliği {1} olarak ayarlanamaz. Sütun SimpleContent. - - - '{0}' Sütunu MappingType özelliği SimpleContent olarak ayarlanamaz. DataType Sütunu {1}. - - - '{0}' Sütunu MaxLength sınırını aşıyor. - - - '{0}' sütunu DBNull.Value kullanımına izin vermiyor. - - - '{0}' Sütunu Namespace özelliği değiştirilemiyor. Sütun SimpleContent. - - - İçinde veri olduğunda, '{0}' türündeki DataColumn sütununun AutoIncrement özelliği değiştirilemez. - - - '{0}' sütunu, temel alınan '{1}' tablosuna ait değil. - - - '{0}' türüne sahip DataColumn bir complexType. Karmaşık türün değeri Attribute olarak serileştirilemiyor - - - DateTimeMode, yalnızca DateTime türündeki DataColumns sütunlarında ayarlanabilir. - - - '{0}' geçersiz DataSetDateTime değeridir. - - - İçinde veri olan bir tablonun DateTimeMode modu, '{0}' değerinden '{1}' değerine değiştirilemez. - - - '{0}' türü statik Null özelliği veya alanı içermiyor. - - - '{0}' türü IRevertibleChangeTracking'i uygulamıyor; bu nedenle RejectChanges() devam edemez. - - - SetAdded ve SetModified, yalnızca DataRowState' i Değişmeyen DataRows'da çağrılabilir. - - - '{0}' sıra sayısı, maksimum sayıyı aşıyor. - - - DataSet tarafından desteklenmeyen: System.Nullable<>. - - - ConstraintCollection içinde olan bir kısıtlamanın adı boş dize olacak şekilde değiştirilemez. - - - {0} kısıtlaması üzerinde kısıtlamalar zorlanamaz. - - - '{0}' Sütunu benzersiz olacak şekilde kısıtlanmış. '{1}' değeri zaten var. - - - '{0}' kısıtlaması bu DataTable'a ait değil. - - - {0} kısıtlaması bulunamadı. - - - Kısıtlama, koleksiyonda zaten var olan {0} adlı kısıtlamayla eşleşiyor. - - - Bu DataTable'a ait '{0}' adlı bir Constraint zaten var. - - - Bu satırlar şu anda benzersiz değerlere sahip değil. - - - Bu sütunlar bu tabloya işaret etmiyor. - - - Değerlerin tümünün karşılık gelen üst değerleri olmadığından kısıtlama etkinleştirilemez. - - - ForeignKey, {0} tablosuna ait olmadığından bu kısıtlama eklenemez. - - - Bu tabloya ait olmayan bir kısıtlama kaldırılamaz. - - - Benzersiz '{0}' kısıtlaması kaldırılamıyor. Önce '{1}' yabancı anahtar kısıtlamasını kaldırın. - - - {0} ilişkisinde kısıtlamalar zorlandığı ve bu satırın silinmesi alt satırlarını boşta bırakacağı için bu satır silinemez. - - - {0} ilişkisinde kısıtlamalar zorlandığı ve bu değerin değiştirilmesi alt satırları boşta bırakacağı için bu değişiklik yapılamaz. - - - ForeignKeyConstraint {1} kısıtlamaları zorladığı ve {2} içinde alt satırlar olduğu için {0} tablosu temizlenemez. - - - ForeignKeyConstraint {1}, alt anahtar değerlerinin ({0}) üst tabloda var olmasını gerektiriyor. - - - Özellik erişilebilir değil, nedeni: '{0}'. - - - Alt satırları olduğundan ve {0} ilişkisi üzerinde kısıtlamalar zorlandığından bu satır silinemez. - - - Tablonun birincil anahtarı zaten ayarlanmış olduğundan birincil anahtar kısıtlaması eklenemez. - - - İç içe iki ilişkideki alt tablo olan DataTable '{0}' tablosuna kısıtlama eklenemez. - - - Farklı tablolara ait olan Sütunlardan bir Anahtar oluşturulamaz. - - - Sütun sayısı 0 olamaz. - - - {0} sütundan fazla olamaz. - - - Aynı sütun birden fazla listelendiğinde Anahtar oluşturulamaz: '{0}' - - - Bir tablonun birincil anahtarı olduğundan kısıtlama kaldırılamaz. - - - {0} tablosunun birincil anahtarı olduğundan kısıtlama kaldırılamaz. - - - Üst Sütunlar ve Alt Sütunlar'da türü eşleşen sütunlar yok. - - - ParentKey ve ChildKey özdeş. - - - ParentColumns ve ChildColumns aynı uzunlukla olmalıdır. - - - ParentColumns ve ChildColumns sıfır uzunlukta olmamalıdır. - - - Satır, bu ilişkiyle aynı DataSet'e ait değil. - - - Bir DataSet parçası olduğunda, RelationName gereklidir. - - - GetChildRows, tablosu {1} olan bir satır gerektirir ancak belirtilen satırın Tablosu {0}. - - - Bu ilişkinin bu DataSet'e eklenmesi için DataSet'teki iki tabloyu birbirine bağlaması gerekir. - - - GetParentRow, tablosu {0} olan bir satır gerektirir ancak belirtilen satırın Tablosu {1}. - - - SetParentRow, Table'ı {0} olan bir alt satır gerektirir, ancak belirtilen satırın Table'ı {1}. - - - Farklı DataSet'lerdeki tablolar arasında ilişki olamaz. - - - Farklı DataSet'lerdeki tablolar arasında ilişki oluşturulamaz. - - - Bu alt sütunlar için bir ilişki zaten var. - - - Bu ilişki, bu ilişki koleksiyonuna ait değil. - - - Bu ilişki zaten başka bir DataSet'e ait. - - - Bu ilişki zaten bu DataSet'e ait. - - - Bu DataSet'e ait '{0}' adlı bir Relation zaten var. - - - {0} ilişkisi bu DataSet'e ait değil. - - - {0} ilişkisi bulunamadı. - - - null bir tabloda koleksiyon oluşturulamaz. - - - Bu koleksiyonun ilişkilerini gösterdiği tablo ait olduğu DataSet'ten çıkarılmış. - - - Bu tablo alt tablo olmadığında, tablonun ParentRelation koleksiyonuna ilişki eklenemez. - - - Bu tablo üst tablo olmadığında, tablonun ChildRelation koleksiyonuna ilişki eklenemez. - - - Bu ilişkinin 'Nested' özelliği false olarak ayarlanamaz. - - - İç içe iki ilişkide, aynı '{0}' tablosu alt tablo olamaz. - - - İç içe ilişkilerde tablo ({0}) kendisinin alt tablosu olamaz. - - - Üst veya alt tablolarında farklı Locale veya CaseSensitive ayarlarına sahip olan bir DataRelation veya Constraint eklenemez. - - - Üst veya Alt Sütunlar bir DataSet içinde değilse, DataRelation oluşturulamaz. - - - Ad alanını devralan '{0}' iç içe tablosunun, farklı ad alanlarına sahip birden çok üst tablosu olamaz. - - - Boş ad alanları olan '{0}' iç içe tablosunun, farklı ad alanlarına sahip birden çok üst tablosu olamaz. - - - Satır, bu ilişkiyle aynı DataSet'e ait değil. - - - Bu işlem tabloda olmayan bir satır üzerinde gerçekleştirilemez. - - - Bu ilişki ve alt satır aynı DataSet'e ait değil. - - - RowChanging olayı içinde önerilen bir değer değiştirilemez. - - - OnRowChanging olayı içinde EndEdit() çağrılamaz. - - - RowChanging olayı içinde BeginEdit() çağrılamaz. - - - OnRowChanging olayı içinde CancelEdit() çağrılamaz. Bu güncelleştirmeyi iptal etmek için özel durum oluşturun. - - - OnRowDeleting olayı içinde Delete çağrılamaz. Bu silme işlemini iptal etmek için özel durum oluşturun. - - - Girdi dizisi bu tablodaki sütun sayısından daha uzun. - - - Erişilecek Geçerli veri yok. - - - Erişilecek Özgün veri yok. - - - Erişilecek Önerilen veri yok. - - - Bu satır bir tablodan kaldırıldı ve hiç veri içermiyor. BeginEdit(), bu satırda yeni veri oluşturulmasına izin verir. - - - Satır üzerinden silinen satır bilgilerine erişilemez. - - - Sürüm Özgün, Geçerli veya Önerilen olmalıdır. - - - {0} konumunda satır yok. - - - Satır ekleme konumu {0} geçersiz. - - - '{1}' adlı tablo için rowOrder value={0} iki kez bulundu. - - - '{0}' tablosunun rowOrder sıralamasında değerler eksik. - - - Verilen DataRow geçerli DataRowCollection içinde değil. - - - Bu satır başka bir tabloya ait. - - - Bu satır zaten bu tabloya ait. - - - Bu satır zaten silinmiş olduğundan silinemez. - - - Bu satır boş. - - - Zaten çıkarılmış olan bir satır çıkarılamaz. - - - Bir alt satırın birden çok üst satırı var. - - - Tanınmayan satır durumu bit deseni. - - - DataSet'in adı boş dize olacak şekilde değiştirilemez. - - - '{0}' adı geçersiz. Bir DataSet, DataTable ile aynı ada sahip olamaz. - - - Şema ad alanı geçersiz. Lütfen bunun yerine şunu kullanın: {0}. - - - CaseSensitive veya Locale özelliği değiştirilemez. Bu değişiklik en az bir DataRelation veya Constraint'in ilişkili tablolarından farklı bir Locale veya CaseSensitive ayarına sahip olmasına neden olur. - - - SchemaSerializationMode özelliği, yalnızca türetilen DataSet tarafından geçersiz kılınırsa ayarlanabilir. - - - PrimaryKey sütunları bu tabloya ait değil. - - - SimpleContent sütunu içeren bir tabloya iç içe ilişki veya öğe sütunu eklenemez. - - - TableName, DataSet'in bir parçası olduğunda gereklidir. - - - DataTable'ın bir basit içerik sütunu zaten var. - - - Tablonun birincil anahtarı yok. - - - {0}, geçerli bir Sort dizisi girişi değil. - - - DataTable seri hale getirilemiyor. Bir veya daha fazla DataColumn ifadesinde kullanılan bir DataTable, geçerli DataTable’ın alt öğesi değil. - - - Bu DataTable, yalnızca DataSet’in bir parçası olarak uzaktan kullanılabilir. Bir veya daha fazla İfade Sütunu, diğer DataTable’lara başvuru içeriyor. - - - DataSet ve DataTable’ın uzaktan bağlanma biçimi özellik değerleri farklı olamaz. - - - DataTable seri hale getirilemiyor. DataTable adı ayarlanmamış. - - - Bu DataSet'e ait '{0}' adlı bir DataTable zaten var. - - - Bu DataSet’e ait olan ‘{0}’ adında ve aynı ‘{1}’ ad alanına sahip bir DataTable zaten mevcut. - - - Tablo ({0}), iç içe bir ilişkide kendisinin alt tablosu olamaz: DataSet adı tablo adıyla çakışıyor. - - - '{0}' adı geçersiz. Bir DataTable, DataSet ile aynı ada sahip olamaz. - - - DataTable zaten başka bir DataSet'e ait. - - - DataTable zaten bu DataSet'e ait. - - - {0} tablosu bu DataSet'e ait değil. - - - Tablo {0} bulunamadı. - - - Mevcut ilişkileri olan bir tablo kaldırılamaz. Önce ilişkileri kaldırın. - - - ForeignKeyConstraint {1} içinde başvurulduğu için tablo {0} kaldırılamıyor. Önce kısıtlamayı kaldırın. - - - DataTable '{0}', kaynaktaki hiçbir DataTable ile eşleşmiyor. - - - Hedef DataSet için {0} tanımı eksik. - - - Hedef DataSet için {0} {1} eksik. - - - <target>.{0} ve <source>.{0} çakışan özelliklere sahip: DataType özelliği uyuşmazlığı. - - - <target>.PrimaryKey ve <source>.PrimaryKey farklı Length değerine sahip. - - - PrimaryKey'de uyuşmayan sütunlar var: <target>.{0} karşılık <source>.{1}. - - - Anahtarların uyuşmayan sütunları olduğundan {0} İlişkisi birleştirilemez. - - - {0} hedef tablosunda {1} sütununun tanımı eksik. - - - Kaynak tabloda {0} PrimaryKey sütunu yok. - - - RowStates parametresi, DataViewRowState numaralandırması değerlerinin geçerli bir birleşim kümesi olmalıdır. - - - Find, bir satırı Sort düzenine göre bulur ancak Sort düzeni belirtilmedi. - - - Dizini oluşturulan anahtar için {0} değer bekleniyordu ancak {1} değer alındı. - - - {0}() küme işlevi ve Tür: {1} kullanımı geçersiz. - - - Geçersiz depolama türü: {0}. - - - DataSet Xml kalıcılığı '{0}' değerini Char değeri olarak desteklemiyor, lütfen Byte depolamayı kullanın. - - - Değer türü, sütun türüyle uyuşmuyor - - - '{0}' türü IComparable arabirimini uygulamıyor. Karşılaştırma yapılamaz. - - - {0} ayarlanamıyor. - - - Bir DataSet için varsayılan görünüm olan bir DataViewManager’da, DataSet değiştirilemez. - - - RowStateFilter, ModifiedOriginals ve ModifiedCurrents'ı aynı anda gösteremez. - - - DataViewManager'dan gelen bir DefaultView veya DataView üzerinde Table özelliği değiştirilemez. - - - DataSet özelliği ayarlandıktan sonra değiştirilemez. - - - DataSet, DataViewManager’ı kullanmadan önce ayarlanmalıdır. - - - Table özelliği ayarlandıktan sonra değiştirilemez. - - - DataTable, DataView’u kullanmadan önce ayarlanmalıdır. - - - Adı olmayan DataTable'a bağlanılamaz. - - - Bu listeye nesne ayarlanamaz. - - - AllowNew yanlış olduğunda DataView üzerinde AddNew çağrılamaz. - - - DataView açık değil. - - - İlişki, bu DataView'un işaret ettiği tablonun üstü olarak atanmamış. - - - AllowDelete false olduğunda DataSource üzerinde silme işlemi yapılamaz. - - - AllowEdit false olduğunda DataSource üzerinde düzenleme işlemi yapılamaz. - - - {0} dizini negatif veya satır sayısının üzerinde. - - - Listeye dış nesneler eklenemez. - - - Bu liste temizlenemez. - - - Bu listeye dış nesne eklenemez. - - - Listede olmayan nesneler kaldırılamaz. - - - {0}, {1} tablosunun DataColumn'u veya DataRelation'ı değil. - - - Bir Range nesnesinde min ({0}) değeri, max ({1}) değerine eşit veya bundan küçük olmalıdır. - - - Bu null bir aralık. - - - MinimumCapacity negatif olmamalıdır. - - - '{0}' adı için tanımlayıcı oluşturulamıyor. - - - Aynı '{0}' adına sahip birden çok tablo var (ad alanı farklı olsa bile). - - - Sütun '{0}': Tür '{1}' boş (null) olamaz. - - - Sütun '{1}': Tür '{0}' parametresiz oluşturucuya sahip değil. - - - Sütun '{0}': Tür '{1}' dize bağımsız değişkeni olan oluşturucuya sahip değil. - - - '{0}' türündeki nesne '{1}' türündeki nesneye dönüştürülemiyor. - - - Data Exception. - - - Constraint Exception. - - - Silinen satırlara erişilemez. - - - Yinelenen ada izin verilmez. - - - İşlem RowChanging olayında desteklenmiyor. - - - Geçersiz kısıtlama. - - - Birincil anahtar eksik. - - - Null değere izin verilmez. - - - Sütun salt okunur olarak işaretli. - - - Satır tabloda bulunamadı. - - - Sürüm bulunamadı. - - - ReadOnly veriler değiştirildi. - - - DataTableReader, geçerli DataTable '{0}' için geçersiz. - - - DataTableReader içindeki geçerli DataTable '{0}' şeması değiştirilmiş, DataTableReader geçersiz. - - - DataTableReader oluşturulamıyor. DataSet’te DataTable yok. - - - DataTableReader oluşturulamıyor. Bağımsız değişken Boş. - - - DataTableReader oluşturulamıyor. Bağımsız değişkenler null değer içeriyor. - - - Geçerli DataRow Deleted veya Detached durumunda. - - - Geçerli DataTable '{0}' boş. DataTable’da DataRow yok. - - - DataTable iç dizini bozuk: '{0}'. - - - Koleksiyon değiştirilmiş, numaralama işlemi yürütülemeyebilir. - - - '{0}' basit türü, zaten farklı bir '{1}' ile bildirildi. - - - Geçersiz yaprak numaralama. - - - İlişkili DataSet yüklü bir XML belgesiyle eşleştirildikten sonra tablo adı değiştirilemez. - - - İlişkili DataSet yüklü bir XML belgesiyle eşleştirildikten sonra tablo ad alanı değiştirilemez. - - - İlişkili DataSet yüklü bir XML belgesiyle eşleştirildikten sonra sütun adı değiştirilemez. - - - İlişkili DataSet yüklü bir XML belgesiyle eşleştirildikten sonra sütun ad alanı değiştirilemez. - - - İlişkili DataSet yüklü bir XML belgesiyle eşleştirildikten sonra ColumnMapping özelliği değiştirilemez. - - - DataSet yüklü bir XML belgesiyle eşleştirildikten sonra tabloya satır eklenemez ve çıkarılamaz. - - - DataSet yüklü bir XML belgesiyle eşleştirildikten sonra DataSet'e tablo eklenemez veya çıkarılamaz. - - - DataSet yüklü bir XML belgesiyle eşleştirildikten sonra DataSet'e İç İçe ilişki eklenemez, çıkarılamaz ve değiştirilemez. - - - DataSet parametresi geçersiz. Null olamaz. - - - DataSet yüklü bir XML belgesiyle eşleştirildikten sonra DataSet adı değiştirilemez. - - - Bu tür düğüm kopyalanamaz: {0}. - - - Zaten veri içeriyorsa XmlDataDocument yüklenemez. Lütfen yeni bir XmlDataDocument kullanın. - - - DataSet en fazla bir XmlDataDocument ile ilişkilendirilebilir. DataSet zaten başka bir XmlDataDocument ile ilişkili olduğundan DataSet geçerli XmlDataDocument ile ilişkilendirilemiyor. - - - XML işlemleri kullanarak XmlDataDocument'i düzenlemeyi denemeden önce lütfen DataSet.EnforceConstraints == false olarak ayarlayın. - - - DataDocument üzerinde GetElementById() desteklenmiyor. - - - DataDocument üzerinde varlık başvuruları oluşturulamaz. - - - XmlDataDocument üzerindeki DateSet ve DataTable Clear işlevi desteklenmiyor. - - - Bu dataSet'e yerleşik olduğundan sütun kaldırılamaz. - - - Bu ilişki, dataSet içinde yerleşik olduğundan kaldırılamaz. - - - Bu denetimin bir özelliği değiştiğinde hep ortaya çıkar. - - - Bu koleksiyonun üyeliği her değiştiğinde gerçekleşir. - - - DBNull olduğu için değer alınamadı. - - - '{0}' özelliği Microsoft WindowsNT veya WindowsNT tabanlı işletim sistemi gerektirir. - - - İstenen .NET Framework Veri Sağlayıcısı bulunamıyor. Yüklenemiyor. - - - İstenen .NET Framework Veri Sağlayıcısı'nın uygulamasında, System.Data.Common.DbProviderFactory türetilen türünün Instance alanı yok. - - - Kayıtlı .NET Framework Veri Sağlayıcısı bulunamadı veya yüklenemedi. - - - Eksik olan .NET Framework Veri Sağlayıcısı bütünleştirilmiş kodu tanımlanmış adı gerekli. - - - Yalnızca öğelere izin verilir. - - - Alt düğümlere izin verilmez. - - - '{0}' özniteliği tanınmadı. - - - Tanınmayan öğe. - - - Her yapılandırma dosyasında tek bir '{0}' bölümü olabilir. - - - Gerekli '{0}' özniteliği bulunamadı. - - - Gerekli '{0}' özniteliği boş olamaz. - - - '{0}' parametresi için boş olmayan dizi bekleniyor. - - - '{0}' özelliği için yalnızca '{1}' değeri kabul edilebilir. - - - '{0}' özelliği için '{1}' veya '{2}' değeri kabul edilebilir. - - - Belirtilen QuotePrefix ve QuoteSuffix değerleri eşleşmiyor. - - - '{0}' bağımsız değişkeninin uzunluğu, bu değişkenin '{1}' sınırını aştı. - - - {1} türünde bağımsız değişken beklenirken {0} türü alındı. - - - '{0}' anahtarı için geçersiz değer. - - - '{0}' anahtarının kullanımı için '{1}' anahtarının olması gerekir. - - - '{0}' anahtarına ait değerin uzunluğu, anahtarın '{1}' sınırını aştı. - - - Anahtar sözcük desteklenmiyor: '{0}'. - - - UDL dosyası yüklenemiyor. - - - Geçersiz UDL dosyası. - - - Dahili .NET Framework Veri Sağlayıcısı hatası {0}. - - - Bir Insert, Update veya Delete komutu üretildikten sonra QuotePrefix ve QuoteSuffix özellikleri değiştirilemez. - - - DataAdapter.SelectCommand özelliğinin başlatılması gerekiyor. - - - DataAdapter.SelectCommand.Connection özelliğinin başlatılması gerekiyor; - - - {0} "{1}". - - - {0} "{1}", tırnak işaretlerinin yanlış kullanımı. - - - {0} "{1}", geçerli "{2}" sınırı yetersiz. - - - SqlBulkCopy.DestinationTableName geçersiz bir çok parçalı ad olduğundan SqlBulkCopy.WriteToServer başarısız oldu - - - Geçersiz bir çok parçalı ad nedeniyle SQL Server sonuçları işlenemedi - - - SqlParameter.UdtTypeName geçersiz bir çok parçalı ad - - - SqlParameter.TypeName, geçersiz bir çok parçalı ad - - - SqlCommand.CommandText özelliğinin değeri, geçersiz çoklu bir ad olduğundan SqlCommand.DeriveParameters başarısız oldu - - - OdbcCommand.CommandText özelliğinin değeri geçersiz bir çok parçalı ad olduğundan OdbcCommandBuilder.DeriveParameters başarısız oldu - - - OleDbCommandBuilder.CommandText özelliğinin değeri, geçersiz bir çok parçalı ad olduğundan OleDbCommandBuilder.DeriveParameters başarısız oldu - - - MultiSubnetFailover bağlantı seçeneğiyle yansıtılmış bir SQL Server örneğine bağlanma desteklenmiyor. - - - DataColumn '{1}' hesaplanmış bir sütun olduğundan SourceColumn '{0}' eşlemesi başarısız oldu. - - - '{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 DataTable '{1}' içinde DataColumn '{0}' eksik. - - - SourceColumn, boş olmayan bir dize olmalıdır. - - - '{0}' için SourceColumn eşlemesi eksik. - - - {0} numaralandırma değeri {1}, {2} metodu tarafından desteklenmiyor. - - - {0} numaralandırma değeri {1}, .NET Framework Odbc Veri Sağlayıcısı tarafından desteklenmiyor. - - - {0} Numaralandırma değeri {1}, .NET Framework OleDb Veri Sağlayıcısı tarafından desteklenmiyor. - - - {0} numaralandırma değeri, {1}, .NET Framework SqlClient Veri Sağlayıcısı tarafından desteklenmiyor. - - - ComputerNameDnsFullyQualified {0} alınamadı. - - - '{1}' SourceTable için '{0}' DataTable eksik. - - - SourceTable, boş olmayan bir dize olmalıdır - - - Eksik SourceTable eşlemesi: '{0}' - - - {0}: CommandText özelliği başlatılmamış - - - {0}: Connection özelliği başlatılmamış. - - - {0} için açık ve kullanılabilen bir Connection gerekiyor. {1} - - - Fill: SelectCommand.Connection özelliği başlatılmamış. - - - FillPage: SelectCommand.Connection özelliği başlatılmamış. - - - FillSchema: SelectCommand.Connection özelliği başlatılmamış. - - - Update özelliği için InsertCommand içinde bir bağlantı nesnesi olmalıdır. InsertCommand için Connection özelliği başlatılmadı. - - - Update özelliği için UpdateCommand içinde bir bağlantı nesnesi olmalıdır. UpdateCommand için Connection özelliği başlatılmadı. - - - Update özelliği için DeleteCommand içinde bir bağlantı nesnesi olmalıdır. DeleteCommand için Connection özelliği başlatılmadı. - - - Update için bir bağlantı gerekiyor. Connection özelliği başlatılmamış. - - - Update özelliği için komut kopyasında bir bağlantı nesnesi olmalıdır. Komut kopyalamanın Connection özelliği başlatılmadı. - - - Güncelleştirme, bir bağlantı gerektiriyor. - - - Update, {0}Command'in açık bir bağlantı nesnesinin olmasını gerektirir. {1} - - - Update, {0}Command'in açık bir bağlantı nesnesinin olmasını gerektirir. {1} - - - Update, {0}Command'in açık bir bağlantı nesnesinin olmasını gerektirir. {1} - - - Update, güncelleştirme yapan komutun açık bir bağlantı nesnesinin olmasını gerektirir. {1} - - - '{0}' saklı yordamı yok. - - - Bu komuta atanan işlem en içte kalan beklemedeki yerel işlem olmalıdır. - - - İşlem geçerli bağlantıyla ilişkili değil veya tamamlanmış. - - - Geçerli bağlantıyla ilişkili işlem tamamlandı, ancak atılmadı. Bağlantının SQL deyimlerini yürütmek için kullanılabilmesi için işlemin atılması gerekiyor. - - - {0}, komuta atanan bağlantı bekleyen bir yerel işlemde olduğunda komutun bir işleme sahip olmasını gerektirir. Komuta ait Transaction özelliği başlatılmadı. - - - Bu komut ile ilişkili, öncelikle kapatılması gereken açık bir SqlResultSet zaten var. - - - Bu {0} ile ilişkili, öncelikle kapatılması gereken açık bir DataReader zaten var. - - - {0} DeriveParameters yalnızca CommandType.StoredProcedure'ı destekler, CommandType.{1} desteklemez. - - - Aynı yürütme işlemi için '{0}' metodu birden fazla kez çağrılamaz. - - - Yanlış zaman uyumsuz sonuç. - - - '{0}' çağrılmadan önce SelectCommand özelliği başlatılmamış. - - - Burada StatementType {0} beklenmiyor. - - - FillSchema: SourceTable adı için boş olmayan bir dize bekleniyor. - - - MaxRecords değeri {0} geçersiz; değer >= 0 olmalı. - - - StartRecord {0} değeri geçersiz; değer >= 0 olmalı. - - - Fill: SourceTable adı için boş olmayan bir dize bekleniyor. - - - Sıradüzensel bölüm sütunları AutoIncrement bir DataColumn'la eşleşmelidir. - - - DataReader.GetFieldType({0}) null döndürdü. - - - startRecords veya maxRecords için sıfır dışında değerler kullandığınızda, dataTables dizisinde yalnızca bir öğe belirtin. - - - TableMapping['{0}'] veya DataTable '{0}' Update tarafından bulunamadı. - - - Update: boş olmayan bir SourceTable adı bekleniyor. - - - TableMapping.DataSetTable='{0}' iken TableMapping eksik. - - - Update, komut kopyasının geçerli olmasını gerektirir. - - - Update sırasında Otomatik SQL üretimi geçerli bir SelectCommand gerektirir. - - - Update, kendisine yeni satırlar bulunan DataRow koleksiyonu geçirildiğinde geçerli bir InsertCommand gerektirir. - - - Update, kendisine değiştirilmiş satırları olan DataRow koleksiyonu geçirildiğinde geçerli bir UpdateCommand gerektirir. - - - Update, kendisine silinmiş satırlar bulunan DataRow koleksiyonu geçirildiğinde geçerli bir DeleteCommand gerektirir. - - - DataRow[{0}], DataRow[0] ile aynı DataTable'a ait değil. - - - RowUpdatedEvent: Hata oluştu; kullanılabilir ek bilgi yok. - - - RowUpdatingEvent: Hata oluştu; kullanılabilir ek bilgi yok. - - - Toplu işlem yapılırken, komutun UpdatedRowSource özelliği değeri UpdateRowSource.FirstReturnedRecord veya UpdateRowSource.Both geçersizdi. - - - Tutarlılık ihlali: UpdateCommand, beklenen {1} kayıttan {0} kaydı etkiledi. - - - Tutarlılık ihlali: DeleteCommand, beklenen {1} kayıttan {0} kaydı etkiledi. - - - Tutarlılık ihlali: toplu işleme alınan komut beklenen {1} kayıttan {0} kaydı etkiledi. + + Aynı yürütme işlemi için '{0}' metodu birden fazla kez çağrılamaz. CommandTimeout değeri {0} geçersiz; değer >= 0 olmalı. - - {1}[{0}]: Size özelliğinin 0 olan boyutu geçersiz. - - - {0}.Prepare metodu, tüm parametrelerin açıkça ayarlanmış türe sahip olmalarını gerektirir. - - - {0}.Prepare metodu, değişken uzunluktaki tüm parametrelerin açıkça ayarlanmış, sıfırı olmayan bir Size sahibi olmalarını gerektirir. - - - {0}.Prepare metodu, '{1}' türü parametrelerin açıkça ayarlanmış Precision ve Scale sahibi olmasını gerektirir. - - - asyncResult için eşleşmeyen bitiş metot çağrısı. {0} çağrısı bekleniyordu ancak bunun yerine {1} arandı. - - - Geçersiz işlem. Bağlantı kapalı. - - - Bağlantı devre dışı bırakılmış. - - - Bağlantı üzerinde devam eden yerel bir işlem olduğundan işleme kaydedilemiyor. Yerel işlemi bitirip yeniden deneyin. - - - Bağlantıda şu anda kayıtlı işlem var. Geçerli işlemi bitirip yeniden deneyin. - - - Veritabanı null, boş dize veya boşluk karakterlerinden oluşan dize olamaz. - - - Bağımsız değişken çok uzun. - - - Geçersiz 'Connect Timeout' değeri; >= 0 bir tamsayı olmalı. - - - Geçersiz kaynak arabellek (boyutu {0}) uzaklığı: {1}. - - - Geçersiz hedef arabellek (boyutu {0}) uzaklığı: {1} - - - Sayı değeri 96 bit ondalık basamağa sığmayacak kadar büyük. - - - Akış kapalıyken geçersiz {0} girişimi. - - - Belirtilen SeekOrigin değeri geçersiz. - - - Birden çok temel tabloda dinamik SQL üretimi desteklenmiyor. - - - Hiç temel tablo bilgisi döndürmeyen bir SelectCommand'de dinamik SQL üretimi desteklenmiyor. - - - Anahtar sütunu bilgileri döndürmeyen bir SelectCommand'e karşılık kullanılan DeleteCommand için dinamik SQL üretimi desteklenmiyor. - - - Anahtar sütunu bilgileri döndürmeyen bir SelectCommand'e karşılık kullanılan UpdateCommand için dinamik SQL üretimi desteklenmiyor. - - - Satır sürümü sütunu içermeyen bir SelectCommand'e karşılık kullanılan DeleteCommand için dinamik SQL üretimi desteklenmiyor. - - - Satır sürümü sütunu içermeyen bir SelectCommand'e karşılık kullanılan UpdateCommand için dinamik SQL üretimi desteklenmiyor. - - - '{1}' QuotePrefix veya QuoteSuffix karakterini içeren '{0}' tablo adlarında dinamik SQL üretimi desteklenmiyor. - - - '{0}' sütun sıra değerini okuma girişimi geçersiz. CommandBehavior.SequentialAccess ile yalnızca '{1}' veya daha büyük bir sıra değerini okuyabilirsiniz. - - - '{0}' veri türü, tarih saat sayıları geçersiz olduğundan sabit değer olarak biçimlendirilemiyor. - - - Değer, istenen türde bir sabit değer olarak biçimlendirilemiyor. - - - '{0}' veri türü, geçersiz bir en yüksek ölçeğe sahip olduğundan sabit değer olarak biçimlendirilemiyor. - - - Sağlanan sabit değer, '{0}' veri türü için geçerli bir sabit değer değil. - - - '{0}': Sabit değerin uzunluğu çift sayı olmalıdır. - - - '{0}': Sabit değer onaltılık sayılardan oluşan bir dize olmalıdır - - - {0}, tırnak işareti öneki ayarlanmadığında açık bağlantı gerektirir. - - - Doğru boole sabit değerleri belirlenemiyor. Boole sabit değerleri oluşturulamıyor. - - - '{0}' veri türüyle ilişkili yerel veri türü sabit değerleri desteklenmiyor. - - - {0} parametre veri türü geçersiz. - - - {0} nesne türüyle, bilinen bir yönetilen sağlayıcı yerel türü arasında eşleme yok. - - - {1} Türü tarafından döndürülen bilinmeyen bir {0} TypeCode'u işlenemiyor. - - - {0} DbType'tan bilinen bir {1} öğesine herhangi bir eşleme yok. - - - Kullanılan SQL Server sürümü '{0}' veri türünü desteklemiyor. - - - '{0}' parametre değeri aralık dışı. - - - Belirtilen parametre adı '{0}' geçerli değil. - - - Birden çok dönüş değeri parametresi desteklenmiyor. - - - Parametre Size değeri '{0}' geçersiz. Değer 0'dan büyük veya eşit olmalıdır. - - - '{0}' bağımsız değişkeni için değer geçersiz. Değer 0'dan büyük veya eşit olmalıdır. - - - Bu meta verileri için geçersiz değer. - - - Meta veriler SqlDbType.Row olmalı - - - {0} olan parametre değeri {1} değerine dönüştürülemedi. - - - {0}, paralel işlemleri desteklemiyor. - - - Bu {0} tamamlandı; artık kullanılabilir değil. - - - Kayıt salt okunur olduğunda '{0}' çağrılamaz. - - - DbDataRecord salt okunur olduğunda '{0}' çağrılamaz. - - - '{0}' nesne türünün '{1}' veri türüne örtülü olarak dönüştürülmesi desteklenmiyor. - - - Arabellek uzaklığı '{1}' artı kullanılabilir bayt sayısı '{0}', geçirilen arabelleğin uzunluğundan büyük. - - - Veri uzunluğu '{0}' 0'dan küçük. - - - Belirtilen uzunluk '{0}', aralık dışında. - - - dataIndex '{0}' için geçersiz {2} denemesi. CommandBehavior.SequentialAccess ile yalnızca '{1}' veya daha büyük bir dataIndex değerini okuyabilirsiniz. - - - Uzaklık, değerin içinde bir konumu göstermeli. - - - "ODBC tarafından yönetilen sağlayıcı, TABLE_NAME kısıtlamasının belirtilmesini ve GetSchema dizinleri koleksiyonu için null olmamasını gerektirir. - - - '{0}' metodu için geçersiz bağımsız değişken değeri. - - - ODBC sağlayıcısı SQLGETTYPEINFO'dan sonuç döndürmedi. - - - Beklenmeyen null DataTable bağımsız değişkeni - - - Beklenmeyen null DataSet bağımsız değişkeni. - - - Bağlantı dizesi izin verilen en fazla uzunluk olan {0} değerini aşıyor. - - - {0} - tür eşlenemiyor. - - - Bilinmeyen SQL türü - {0}. - - - Bilinmeyen URT türü - {0}. - - - Geçersiz negatif bağımsız değişken! - - - Özellik, açık bir bağlantıya ayarlanamaz. - - - SQL_TRANSACTION '{0}' için bir System.Data.IsolationLevel numaralandırma değerine yapılan geçerli bir eşleme yok. - - - {0} - bağlantı havuzu etkinleştirilemiyor... - - - {0} - ortam işleyici ayrılamıyor. - - - {0} - tanımlayıcı işleyicisi alınamıyor. - - - İşlem içinde değil - - - Geçersiz OdbcType numaralandırma value={0}. - - - DBNull.Value verisi bekleniyorsa IsDBNull kullanın. - - - {0} [{1}] {2} - - - {0} - kullanılabilir hata bilgisi yok - - - Bağlantı kapalı. - - - Bir dahili bağlantının sahibi yok. - - - NET Framework Odbc Veri Sağlayıcısı, Microsoft Veri Erişim Bileşenleri (MDAC) sürüm 2.6 veya üstünü gerektirir. {0} sürümü şu anda yüklü olarak bulundu. - - - NET Framework OleDb Veri Sağlayıcısı, Microsoft Veri Erişim Bileşenleri (MDAC) sürüm 2.6 veya üstünü gerektirir. {0} sürümü şu anda yüklü olarak bulundu. - - - '{0}’ arabirimi '{1}' sağlayıcısı tarafından desteklenmiyor. GetOleDbSchemaTable geçerli sağlayıcı ile kullanılamıyor. - - - '{0}' hata mesajı olmadan başarısız oldu, sonuç kodu: {1}. - - - Hata mesajı yok, sonuç kodu: {0}. - - - .NET Framework Veri Sağlayıcıları, Microsoft Veri Erişim Bileşenleri (MDAC) gerektirir. Lütfen Microsoft Data Access Components (MDAC) sürüm 2.6 veya sonraki sürümünü yükleyin. - - - OLEDB için .NET Framework Veri Sağlayıcısı (Microsoft.Data.OleDb), ODBC Sürücüleri için Microsoft OLE DB Sağlayıcısını (MSDASQL) desteklemez. ODBC için .NET Framework Veri Sağlayıcısı'nı (System.Data.Odbc) kullanın. - - - OLEDB için .NET Framework Veri Sağlayıcısı, OLE DB Sağlayıcısının etkileşimli olmayan bir ortamda kullanıcıdan bilgi istemesine izin vermez. - - - '{0}' sağlayıcısı yerel makinede kayıtlı değil. - - - ICommandText arabirimi, ‘{0}’ sağlayıcısı tarafından desteklenmiyor. Bunun yerine CommandType.TableDirect kullanın. - - - ITransactionLocal arabirimi, '{0}' sağlayıcısı tarafından desteklenmiyor. Geçerli sağlayıcıda yerel işlemler kullanılamıyor. - - - Başlatma dizesinin biçimi OLE DB belirtimine uygun değil. Bağlantı dizesinde char[{0}] etrafında başlar. - - - 'Asynchronous Processing' desteklenen bir .NET Framework Veri OLE DB sağlayıcısı (Microsoft.Data.OleDb) özelliği değil. - - - ConnectionString'de bir OLE DB Sağlayıcısı belirtilmedi. Örnek olarak 'Provider=SQLOLEDB;' verilebilir. - - - ConnectionString içinde belirtilen OLE DB Sağlayıcısı çok uzun. - - - DbInfoKeywords OleDbSchemaGuid için kısıtlama beklenmiyor. - - - DbInfoLiterals OleDbSchemaGuid için hiçbir kısıtlama beklenmiyor. - - - OleDbSchemaGuid şema guid'si için kısıtlama beklenmiyor. - - - {0} OleDbSchemaGuid, '{1}' sağlayıcısı tarafından desteklenen bir şema değil. - - - '{0}' yapılandırma ayarındaki değer sayısı hatalı. - - - '{0}' yapılandırma ayarında belirtilen XML dosyası yüklenemiyor. - - - Komut parametresi[{0}] '{1}' geçersiz. - - - Komut parametresi[{0}] '{1}' veri değeri, işaret uyuşmazlığı veya veri taşması dışındaki nedenlerle dönüştürülemedi. - - - Veri değeri işaretli ve sağlayıcı tarafından kullanılan tür işaretsiz olduğu için [{0} ] '{1}' komut parametresi için dönüştürme başarısız oldu. - - - Veri değeri, sağlayıcı tarafından kullanılan türü aştığı için [{0}] '{1}' komut parametresi için dönüştürme başarısız oldu. - - - Sağlayıcı, komut parametresi[{0}] '{1}' değerini gönderirken bir hatayla karşılaştı ve işlemi durdurdu. - - - [{0}] '{1}' parametresinin varsayılan değeri yok. - - - [{0}] parametresinde bir hata oluştu: {1}. - - - Microsoft.Data.OleDb.OleDbDataAdapter iç hatası: geçersiz parametre erişimcisi: {0} {1}. - - - Parametre[{0}]: OleDbType özelliği başlatılmamış: OleDbType. {1}. - - - ICommandWithParameters arayüzü '{0}' sağlayıcısı tarafından desteklenmemektedir. Komut parametreleri geçerli sağlayıcıda desteklenmiyor. - - - Yordam parametre bilgilerinin alınması '{0}' sağlayıcısı tarafından desteklenmiyor. - - - Sunucunun ondalık ayırıcısı belirlenemiyor. Tamsayı olmayan sayısal sabit değerler oluşturulamaz. - - - Nesne bir ADODB.RecordSet veya ADODB.Record değil. - - - ADODB.RecordSet nesnesinden '{0}' arabirimi alınamıyor. + + {1}[{0}]: Size özelliğinin 0 olan boyutu geçersiz. - - ADODB.Record nesnesinde IRow arabirimi alınamıyor. + + {0}.Prepare metodu, tüm parametrelerin açıkça ayarlanmış türe sahip olmalarını gerektirir. - - Tür, OLE DB arabirimi ISourcesRowset'i desteklemiyor + + {0}.Prepare metodu, değişken uzunluktaki tüm parametrelerin açıkça ayarlanmış, sıfırı olmayan bir Size sahibi olmalarını gerektirir. - - Sağlayıcı IDBInfo'yu desteklemediğinden, ReservedWords şema koleksiyonu oluşturulamıyor. + + {0}.Prepare metodu, '{1}' türü parametrelerin açıkça ayarlanmış Precision ve Scale sahibi olmasını gerektirir. - - Sağlayıcı '{0}' özelliğini desteklemediği veya tüketici, Başlatma özellik grubunda olmayan özelliklerin değerlerini almaya veya ayarlamaya çalıştığından ve veri kaynağı nesnesi başlatılmadığından özelliğin değeri ayarlanamadı. + + asyncResult için eşleşmeyen bitiş metot çağrısı. {0} çağrısı bekleniyordu ancak bunun yerine {1} arandı. - - ‘{0}’ özelliği, şu nedenlerden biri yüzünden başlatılamadı: - Değer veri türü, özelliğin veri türü değildi veya null değildi. Örneğin, özellik, veri türü Int32 olan DBPROP_MEMORYUSAGE idi ve veri türü Int64 idi. - Değer, geçerli bir değer değildi. Örneğin, özellik DBPROP_MEMORYUSAGE idi ve değer negatifti. - Değer, özellik için geçerli bir değerdi ve sağlayıcı özelliği ayarlanabilir bir özellik olarak destekliyordu ancak sağlayıcı belirtilen değeri desteklemiyordu. Bu, değerin sağlayıcı yazıldıktan sonra OLE DB'deki özelliğe eklendiği durumu da içerir. + + Geçersiz işlem. Bağlantı kapalı. - - Options değeri geçersizdi. + + Bağlantı üzerinde devam eden yerel bir işlem olduğundan işleme kaydedilemiyor. Yerel işlemi bitirip yeniden deneyin. - - ColumnID öğesi geçersizdi. + + Bağlantıda şu anda kayıtlı işlem var. Geçerli işlemi bitirip yeniden deneyin. - - Tüm sütunlara uygulanmak üzere bir '{0}' özelliği belirtildi ancak bir veya daha fazlasına uygulanamadı. + + Veritabanı null, boş dize veya boşluk karakterlerinden oluşan dize olamaz. - - '{0}' özelliği salt okunurdu veya tüketici, veri kaynağı nesnesi başlatıldıktan sonra Başlatma özellik grubundaki özelliklerin değerlerini ayarlamaya çalıştı. Tüketiciler, salt okunur bir özelliğin değerini geçerli değerine ayarlayabilir. Bu durum, belirli bir sütun için ayarlanabilir bir sütun özelliği ayarlanamadığında da döndürülür. + + Geçersiz kaynak arabellek (boyutu {0}) uzaklığı: {1}. - - İsteğe bağlı '{0}' özelliğinin değeri belirtilen değere ayarlanmadı ve özelliği belirtilen değere ayarlamak mümkün olmadı. + + Geçersiz hedef arabellek (boyutu {0}) uzaklığı: {1} - - '{0}' özelliğini ayarlamak, var olan bir özellikle çakışmaya neden olabileceğinden bu özellik ayarlanmadı. + + Akış kapalıyken geçersiz {0} girişimi. - - (Ayrılmış). + + Belirtilen SeekOrigin değeri geçersiz. - - Sağlayıcı, bilinmeyen bir DBPROPSTATUS_ value '{0}' döndürdü. + + '{0}' sütun sıra değerini okuma girişimi geçersiz. CommandBehavior.SequentialAccess ile yalnızca '{1}' veya daha büyük bir sıra değerini okuyabilirsiniz. - - Accessor doğrulaması ertelendi ve metot veri döndürürken gerçekleşti. Bu sütun veya parametre için bağlama geçersizdi. + + {0} parametre veri türü geçersiz. - - OleDbDataAdapter iç hatası: geçersiz satır kümesi erişeni: Ordinal={0} Status={1}. + + {0} nesne türüyle, bilinen bir yönetilen sağlayıcı yerel türü arasında eşleme yok. - - Veri değeri, işaret uyuşmazlığı veya veri taşması dışındaki nedenlerden dolayı dönüştürülemedi. Örneğin, veri deposundaki veriler bozulmuş ancak satır hala alınabilir durumdaydı. + + {1} Türü tarafından döndürülen bilinmeyen bir {0} TypeCode'u işlenemiyor. - - Sağlayıcı, {0} verilerini döndürmek için bellek ayıramadı. + + {0} DbType'tan bilinen bir {1} öğesine herhangi bir eşleme yok. - - {0} veri değeri, tüketicinin arabelleğinde {0} değeri kısmı için belirtilen türe göre taştığından dönüştürme başarısız oldu. + + Kullanılan SQL Server sürümü '{0}' veri türünü desteklemiyor. - - OleDbDataAdapter iç hatası: [get] Bilinmeyen OLE DB veri türü: 0x{0} ({1}). + + '{0}' parametre değeri aralık dışı. - - {0} veri değeri işaretli ancak tüketicinin arabelleğinde {0} değeri kısmı için belirtilen tür işaretsiz olduğundan dönüştürme başarısız oldu. + + Belirtilen parametre adı '{0}' geçerli değil. - - OleDbDataAdapter iç hatası: [set] Bilinmeyen OLE DB veri türü: 0x{0} ({1}). + + Parametre Size değeri '{0}' geçersiz. Değer 0'dan büyük veya eşit olmalıdır. - - Sağlayıcı, {0} değerini belirleyemedi. Örneğin, satır yeni oluşturuldu, {0} sütunu için varsayılan değer mevcut değil ve tüketici henüz yeni bir {0} değer belirlemedi. + + '{0}' bağımsız değişkeni için değer geçersiz. Değer 0'dan büyük veya eşit olmalıdır. - - OLE DB Sağlayıcısı beklenmeyen bir durum değeri döndürdü: {0} + + Bu meta verileri için geçersiz değer. - - OleDbDataReader.Read, iş parçacığının ApartmentState'i ApartmentState.MTA değilse söz konusu iş parçacığından kullanılmalıdır. + + {0} olan parametre değeri {1} değerine dönüştürülemedi. - - Belirtilmemiş hata: {0} + + {0}, paralel işlemleri desteklemiyor. - - IErrorInfo.GetDescription, {0} ile başarısız oldu. + + Bu {0} tamamlandı; artık kullanılabilir değil. - - IErrorInfo.GetSource {0} ile başarısız oldu. + + Belirtilen uzunluk '{0}', aralık dışında. - - DBTYPE_VECTOR verileri .NET Framework Data OLE DB Provider (Microsoft.Data.OleDb) tarafından desteklenmiyor. + + dataIndex '{0}' için geçersiz {2} denemesi. CommandBehavior.SequentialAccess ile yalnızca '{1}' veya daha büyük bir dataIndex değerini okuyabilirsiniz. Geçersiz en küçük ve en büyük havuz boyutu değerleri, en küçük havuz boyutu, en büyük havuz boyutundan büyük olamaz. - - '{0}' anahtar sözcüğü artık kullanılmıyor. Bunun yerine '{1}' kullanın. - Sunucu için dağıtılmış işlem düzenleyicisinin adresi sunucudan alınamıyor. DTC sunucuda etkin mi? - - <{0}> bağlantı parametresi değerinin uzunluğu izin verilen 65535 karakterlik en fazla uzunluğu aşıyor. - Geçersiz 'Paket Boyutu'. Değer >= 512 ve <= 32768 bir tamsayı olmalıdır. Geçersiz işlem veya işlem içinde kaydedilecek nokta için geçersiz ad. - - {0} numaralandırma değeri {1}, SQL Server 7.0 veya SQL Server 2000 tarafından desteklenmiyor. - Kullanıcı Örneği ve Yük Devretme uyumlu seçenekler değil. Lütfen bağlantı dizesinde ikisinden yalnızca birini seçin. @@ -2499,33 +540,21 @@ Bağlantı dizesinde 'Authentication=Active Directory Integrated' belirtildiyse, Credential özelliği ayarlanamaz. - - Sertifika kimlik doğrulaması ile belirtilen kimlik doğrulama parametreleri hatalı. - Bağlanmaya çalıştığınız SQL Server örneği şifreleme gerektiriyor, ancak bu makine şifrelemeyi desteklemiyor. Bağlanmaya çalıştığınız SQL Server örneği şifrelemeyi desteklemiyor. - - Bağlanmaya çalıştığınız SQL Server örneği CTAIP'yi desteklemiyor. - Desteklenmeyen SQL Server sürümü. .NET Framework SqlClient Veri Sağlayıcısı yalnızca SQL Server sürüm 7.0 ve üstü ile kullanılabilir. Zaman uyumsuz işlem yürütülürken {0} değiştirilemez. - - 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. - Örnek hatası. @@ -2556,27 +585,18 @@ Hata kodu 0x{0} - - ChangePassword, SQL Server 9.0 veya sonraki sürümünü gerektirir. - SSE Örneği yeniden yönlendirmesi, yerel olmayan kullanıcı örnekleri için desteklenmiyor. '{0}' kimlik doğrulaması desteklenmiyor. - - SQL kimlik doğrulaması metodu '{0}' desteklenmiyor. - '{0}' için '{1}' türüne sahip bir kimlik doğrulama sağlayıcısı örneği oluşturulamadı. '{0}' türüne sahip bir SqlAuthenticationInitializer örneği oluşturulamadı. - - ‘{0}’ adlı sağlayıcı başlatılırken bir özel durum oluşturdu. - '{0}' adlı sağlayıcı, '{1}' kimlik doğrulamasını desteklemiyor. @@ -2586,24 +606,15 @@ '{0}' için kimlik doğrulaması sağlayıcısı bulunamıyor. Active Directory (Entra ID) kimlik doğrulaması yöntemlerini kullanmak için ‘Microsoft.Data.SqlClient.Extensions.Azure’ NuGet paketini (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) yükleyin. - - Kimlik doğrulama sağlayıcıları için yapılandırma bölümü okunamadı. - '{0}' parametresi null veya boş olamaz. - - Zaman uyumsuz işlem zaten tamamlandı. - Beklemedeki zaman uyumsuz bir işlem sürdüğünden, komutun yürütülmesine devam edilemiyor. ExecuteXmlReader'a geçersiz komut gönderildi. Komut bir Xml sonucu döndürmelidir. - - Bildirimler, SQL Server 9.0 veya sonraki sürümünü gerektirir. - UdtTypeName için geçersiz 3 parçalı ad biçimi. @@ -2697,9 +708,6 @@ SSPI paketi başlatılamıyor. - - Zaman aşımı süresi sona erdi. İşlem tamamlanmadan zaman aşımı süresi doldu veya sunucu yanıt vermiyor. - Bağlantı Zaman Aşımı Süresi Doldu. Ön oturum açma aşaması başında zaman aşımı süresi doldu. Bunun nedeni bağlantı zaman aşımı için sağlanan sürenin yetersiz olması olabilir. @@ -2745,12 +753,6 @@ Bağlantı dizesinde kullanıcı örneği istendi, ancak belirtilen sunucu bu seçeneği desteklemiyor. - - Sunucu sertifikası uygulama doğrulamasında başarısız oldu. - - - Belirtilen veri uzunluğu {0}, izin verilen en fazla veri uzunluğunu {1} aşıyor. - Hiç veri yokken okuma girişimi geçersiz. @@ -2775,9 +777,6 @@ Veri uzunluğu '{0}' 0'dan küçük. - - SqlDataRecord salt okunur olduğunda '{0}' çağrılamaz. - Eşlenen koleksiyon kullanımda ve şu anda erişilemez; @@ -2847,21 +846,6 @@ Microsoft.Data.SqlClient kullanarak bağlam bağlantısına bağlanma desteklenmiyor. - - İç içe TransactionScopes desteklenmiyor. - - - Beklenmeyen sunucu olayı: {0}. - - - ‘{0}’ uzunluğu, ‘{1}’ uzunluğu ile aynı olmalıdır. - - - SqlDbType '{0}' {1} için geçersiz. Yalnızca {2} desteklenir. - - - Çok fazla değer. - Stream yazmayı desteklemiyor. @@ -2886,192 +870,36 @@ Null - - İleti - - - Aritmetik Taşma. - - - Sıfıra bölme hatasıyla karşılaşıldı. - - - Veri Null. Bu metot veya özellik Null değerler için çağrılamaz. - - - Sayısal matematiksel işlem kesmeye neden oldu. - SqlDateTime taşması. 1/1/1753 00:00:00 ile 31/12/9999 23:59:59 arasında olmalıdır. - - Bitiştirilecek iki dizenin alfabe düzeni farklı. - - - Karşılaştırılacak iki dizenin alfabe düzeni farklı. - - - Geçersiz bayrak değeri. - - - SqlDecimal'den Decimal'e dönüştürmede taşma. - Dönüştürmede taşma. - - Geçersiz SqlDateTime. - - - Bir saat dilimi belirtildi. SqlDateTime saat dilimlerini desteklemiyor. - Geçersiz dizi boyutu. Geçersiz sayısal kesinlik/ölçek. - - Girdi doğru biçimde değildi. - - - SqlType hatası. - - - SqlBytes ve SqlChars, bu sürümde 2GB'den fazla uzunluğu desteklemiyor. - Okuma sırasında hata oluştu. - - Stream kapatıldı veya kaldırıldı. - - - Arabellek yok. Okuma veya yazma işlemi başarısız oldu. - - - Geçerli değer Null olduğundan, sıfır olmayan bir uzunluğa ayarlanamıyor. - - - Arabellek yetersiz. Okuma veya yazma işlemi başarısız oldu. - - - Geçerli değer Null olduğundan, sıfır olmayan uzaklığa yazdırılamıyor. - - - Geçerli uzunluktan daha büyük bir kaydırmadan yazılamıyor. Arabellekte başlatılmamış veri bırakır. - Döndürülen veri boyutu 2 Gb'den büyük. Tüm verileri almak için SequentialAccess komut davranışını kullanın. - - GetDayOfWeek bağımsız değişkeni 1 ile 7 arasında bir tamsayı olmalı. - - - SQL Type'a veriler yüklenmemiş. - - - SQL Type'a veriler önceden yüklenmiş. - - - Kapalı bir XmlReader'a erişme denemesi geçersiz. - - - Akış kapalıyken geçersiz bir {0} çağırma denemesi. - - - Akış yazılabilir değilken, geçersiz bir {0} çağırma denemesi. - - - Akış okunabilir değilken, geçersiz bir {0} çağırma denemesi. - - - Akış aranabilir değilken, geçersiz bir {0} çağırma denemesi. - - - Alt sınıf gerekli metodu geçersiz kılamadı. - '{0}' için normalleştirici oluşturulamıyor. İç Hata - - Komut parametresinin null ve boş olmayan bir komut metni olmalı. - - - MetaData parametre dizisinin uzunluğu, ParameterDirection dizisi bağımsız değişkenine eşit olmalı. - - - Veri bağdaştırıcısı eşleme hatası. - - - Veri bağdaştırıcı hatası. - - - Veritabanı tutarlılık ihlali. - İşlem durduruldu. İşlem özel bir durum nedeniyle durduruldu (ayrıntılar için bkz: InnerException). - - Fill metodunun DataRow.AcceptChanges'i çağırıp çağırmayacağı. - - - Update metodunun DataRow.AcceptChanges değerini çağırıp çağırmayacağı. - - - Update olayları RowUpdating ve RowUpdated, Status değeri UpdateStatus.ErrorsOccurred olduğunda, sonraki DataRow'a devam edilip edilmeyeceği. - - - Bağdaştırıcının DataTable tablosunu DataReader okuyucusundan doldurma şekli. - - - TableMappings içindeki bir tablo veya sütun eksik olduğunda gerçekleştirilen eylem. - - - DataSet içindeki bir tablo veya sütun eksik olduğunda gerçekleştirilen eylem. - - - Kaynak tabloyu DataSet tablosuna eşleme. - - - Fill sırasında kurtarılabilir bir hata oluştuğunda harekete geçirilen olay. - - - Fill metodunun, sağlayıcıya özel değerler mi yoksa ortak CLSCompliant değerleri mi döndüreceği. - - - DataColumn.ColumnName - - - Kaynak sütun adı - büyük/küçük harf duyarlı. - - - Koleksiyondaki öğe sayısı - - - Belirtilen DataColumnMapping nesnesi. - - - Bu tablo eşlemesi sağlandığında tek tek sütun eşlemeleri. - - - DataTable.TableName - - - DataTableMapping kaynak tablo adı. Bu ad büyük/küçük harfe duyarlıdır. - - - Koleksiyondaki öğe sayısı - - - Belirtilen DataTableMapping nesnesi - DataSet'teki silinen satırlara yönelik Update işleminde kullanılır. @@ -3090,63 +918,9 @@ Update sırasında her DataRow'dan sonra harekete geçirilen olay. - - Veri kaynağına karşı yürütülmeden önce bir araya getirilecek satır sayısı. - - - Select/Insert/Update/DeleteCommands bir bağlantıya sahip olmadığında kullanılan bağlantı. - - - DataTable'da silinen satırlara yönelik Update işleminde kullanılır. - - - DataTable'daki yeni satırlara yönelik Update işleminde kullanılır. - - - Fill işleminde kullanılır. - - - DataTable'da değiştirilen satırlara yönelik Update işleminde kullanılır. - - - Fill metodunun, sağlayıcıya özel değerler mi yoksa ortak CLSCompliant değerleri mi döndüreceği. - - - Kaynak tablosu ile DataTable'ı eşleme şekli. - - - Kullanıcı tarafından ayarlanmadığında Insert/Update/DeleteCommands'in nasıl üretildiği. - - - Veri kaynağına karşı yürütülmeden önce bir araya getirilecek satır sayısı. - - - Veri Kaynağına bağlanmak için kullanılan bağlantı dizesi. - - - Data Source veri kaynağına bağlanırken kullanılacak ODBC Sürücüsünün adı. - - - Data Source veri kaynağına bağlanırken kullanılacak DSN. - - - Doğru olduğunda, yönetilen bağlantı havuzunun kullanılması gerektiğini gösterir. - - - Data Source veri kaynağına bağlanırken kullanılacak UDL dosyası. - - - OleDb Provider ile etkinleştirilecek veya devre dışı bırakılacak OLE DB Services'i belirtir. - - - Data Source veri kaynağına bağlanırken kullanılacak OLE DB Sağlayıcısı adı. - Uygulamanın adı. - - Doğru olduğunda, .Net Framework Veri Sağlayıcısı içinde Zaman Uyumsuz işlevinin kullanımını etkinleştirir. - Bağlanabilen veritabanının, tam yol adını içerecek şekilde belirtilen birincil dosya adı. @@ -3156,9 +930,6 @@ Doğru olduğunda, havuzdan çıkarıldığında bağlantı durumunun sıfırlandığını gösterir. - - Doğru olduğunda, bağlantının Sql Server bağlamından olması gerektiğini gösterir. Yalnızca Sql Server işlemi çalıştırılırken kullanılabilir. - SQL Server Dili kayıt adı. @@ -3252,54 +1023,9 @@ SQL Server ile kimlik doğrulama metodunu belirtir. - - SQL Server ile kimlik doğrulaması için belirtilen istemci sertifikası. - - - Veri Kaynağına bağlanmak için kullanılan bilgiler. - - - Geçerli bağlantı zaman aşımı değeri, ConnectionString içinde ayarlanabilir değil. - - - Geçerli veri kaynağı katalog değeri, bağlantı dizesindeki 'Database=X'. - - - Geçerli veri kaynağı, bağlantı dizesindeki 'Server=X'. - - - Geçerli ODBC sürücüsü. - - - ODBC Sürücü tarafından erişilen ürünün sürümü. - - - Veri Kaynağına bağlanmak için kullanılan bilgiler. - - - Geçerli bağlantı zaman aşımı süresi, ConnectionString’de 'Connect Timeout=X'. - - - Geçerli veri kaynağı katalog değeri, bağlantı dizesindeki 'Initial Catalog=X'. - - - Geçerli veri kaynağı, bağlantı dizesindeki 'Data Source=X'. - - - Geçerli OLE DB sağlayıcısı ProgID, bağlantı dizesindeki 'Provider=X'. - - - OLE DB Sağlayıcısı tarafından erişilen ürünün sürümü. - Kimlik doğrulama için kullanılacak erişim belirteci. - - Bağlantı durumu, zaman uyumlu veya zaman uyumsuz. Bağlantı dizesinde “Asynchronous Processing=x”. - - - Çoğaltmaya bağlanmak için kullanılan bilgiler. - Bir DataSource'a bağlanmak için kullanılan bilgiler, 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI' gibi. @@ -3324,9 +1050,6 @@ Bu bağlantı için istatistik topla. - - Özel sütun şifreleme anahtarı deposu sağlayıcıları. - Fiziksel bağlantıyı temsil eden bir guid. @@ -3363,99 +1086,9 @@ Bildirimlerde Microsoft SQL Server tarafından kullanılan otomatik kayıt. - - Where yan tümcesinin, kullanıcı tarafından belirtilmediği durumlarda Update ve Delete komutları için otomatik olarak üretilme şekli. - - - Metin komutunda, nitelenmiş bir tablo adındaki katalog adının konumunu gösterir. - - - Metin komutunda, katalog adını tanımlayıcının geri kalanından ayıran karakter. - - - Metin komutunda, şema adını tanımlayıcının geri kalanından ayıran karakter. - - - sql nesnelerini çevreleyen önek dizesi. - - - sql nesnelerini çevreleyen sonek dizesi. - - - Komutların otomatik olarak oluşturulacağı DataAdapter. - - - DataTable veya SelectCommand'deki şemayı kullan. - - - Set yan tümcesinin, kullanıcı tarafından belirtilmediği durumlarda Update komutu için otomatik olarak üretilme şekli. - - - Kendisi için otomatik olarak OdbcCommands üretilecek DataAdapter - - - Bir metin komutunda, özel karakterler içeren tanımlayıcıları tırnak içine almak için açılış tırnak işareti olarak kullanılan karakter. - - - Bir metin komutunda, özel karakterler içeren tanımlayıcıları tırnak içine almak için kapanış tırnak işareti olarak kullanılan karakter. - - - Kendisi için otomatik olarak OleDbCommands üretilecek DataAdapter - - - Sayı sabit değerlerinde kullanılan ondalık ayırıcı. - - - sql nesnelerini çevreleyen önek dizesi - - - sql nesnelerini çevreleyen sonek dizesi - Kendisi için otomatik olarak SqlCommands üretilecek DataAdapter - - Sayı sabit değerlerinde kullanılan ondalık ayırıcı. - - - Bir metin komutunda, özel karakterler içeren tanımlayıcıları tırnak içine almak için açılış tırnak işareti olarak kullanılan karakter. - - - Bir metin komutunda, özel karakterler içeren tanımlayıcıları tırnak içine almak için kapanış tırnak işareti olarak kullanılan karakter. - - - Yalnızca, ondalık ve sayı parametrelerinin Prepare, FillSchema ve CommandBuilder senaryoları ile kullanıldığı durumlar için ayarlanması gerekir. - - - Yalnızca, ondalık ve sayı parametrelerinin Prepare, FillSchema ve CommandBuilder senaryoları ile kullanıldığı durumlar için ayarlanması gerekir. - - - Yerel parametre türü. - - - Yerel parametre türü. - - - Parametrenin adı, '@p1' gibi - - - Yerel parametre türü. - - - Tür için sunucu adı. - - - Değişken uzunlukta veri türlerinde uzaklık. - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - '{1}' parametresi için belirtilen ParameterDirection '{0}' desteklenmiyor. Tablo değerli parametreler yalnızca ParameterDirection.Input'u destekler. @@ -3468,9 +1101,6 @@ '{0}' parametresi için TypeName belirtildi. TypeName yalnızca Yapılandırılmış parametreler için ayarlanmalıdır. - - SqlDbType.Structured türü yalnızca çok değer alabilen türler için desteklenir. - Şema tablosundaki '{0}' alanı için DateType sütunu null. DataType null olmamalıdır. @@ -3483,18 +1113,9 @@ '{0}' kaydındaki alanların sayısı, özgün kayıttaki sayıyla eşleşmiyor. - - aralık: 0-8000 - SqlClient veri sağlayıcısında beklenmedik bir hatayla karşılaşıldı.{0} - - {0},{1} için Tür Bilgisi alınamıyor - - - Sağlayıcı şu derlemeyi yükleyemedi: {0} - UdtTypeName özelliği UDT parametreleri için ayarlanmalı. @@ -3504,72 +1125,9 @@ Belirtilen tür, hedef sunucuya kayıtlı değil.{0}. - - UDT parametrelerinin, birincil anahtarın parçası olmadıkları sürece where yan tümcesinde kullanılmasına izin verilmez. - - - '{0}' geçersiz bir kullanıcı tanımlı tür, neden: {1}. - - - hem bellek içi, hem de kullanıcı tanımlı biçimleri destekler - - - Yerel biçim desteklenemiyor. - - - IBinarySerialize uygulanmıyor - - - seri hale getirilemez - - - ortak oluşturucu yok - - - INullable uygulanamıyor - - - ortak oluşturucusu yok - UDT özniteliği yok - - Eşleme olmadan serileştirme işlemi henüz desteklenmiyor. - - - 'public static x Parse(System.Data.SqlTypes.SqlString)' metodu eksik - - - 'public override string ToString()' metodu eksik - - - 'public static x Null { get; }' metodu eksik - - - Yerel biçim (doğrudan veya başka bir alan aracılığıyla) '{0}' türündeki alanları desteklemez - - - Tür ortak değil - - - Native UDT, '{0}' türü nedeniyle sıralı düzen değil - - - Native UDT en yüksek byte boyutu belirtiyor - - - '{0}' alanı seri hale getirilmemiş olarak işaretli - - - '{0}' alanı türü, Native biçimde izin verilmeyen açık düzen olarak işaretli - - - Birden çok değeri olan bütünleştirilmiş kod başvurularının sıfır olmayan Bütünleştirilmiş Kod Tanımlayıcısı olmalıdır. - - - Çok değer alabilen Structured türleri yalnızca parametreler için kullanılabilir ve başka bir türün içine yerleştirilemez. - Yol adı geçerli değil. @@ -3585,9 +1143,6 @@ İşlem yükseltilmeye çalışılırken hata oluştu. - - Sorgu bildirimleri almak için kullanılan bağımlılık nesnesi. - Bu bağımlılığın geçersiz olup olmadığını gösteren özellik. @@ -3600,9 +1155,6 @@ Var olan bağımlılık nesnesine komut eklemek için. - - Komut zaten başka bir bağımlılık nesnesiyle ilişkilendirilmiş. Üzerine yazılamaz. - Bu SqlCommand nesnesi, zaten başka bir SqlDependency nesnesiyle ilişkilendirildi. @@ -3630,12 +1182,6 @@ Belirtilen zaman aşımı geçersiz. Zaman aşımı < 0 olamaz. - - Bildirim Hatası. Tür={0}, Bilgi={1}, Kaynak={2}. - - - GetMetaData bu SqlDbType için geçersiz. - dbType {0} bu oluşturucu için geçersiz. @@ -4104,9 +1650,6 @@ İç hata. Düz metin değeri null olamaz. - - Şifrelenemiyor. Şifreleme sonucunda {0} bayt şifreli metin elde edildi ve bu da izin verilen maksimum sınır olan {1} baytı aşıyor. Belirtilen düz metin değeri büyük olasılıkla çok büyük (düz metin boyutu: {2} bayt). - İç hata. Şifreli metin değeri null olamaz. @@ -4224,9 +1767,6 @@ Kanıtlama bilgileri SQL Server tarafından döndürülmedi. Kapanım türü '{0}' ve kapanım kanıtlama URL'si '{1}'. - - Güvenli kanal kurulamadı. Hata İletisi: {0} - İç Hata. '{1}' türünde bir nesne oluşturulurken '{0}' null bağımsız değişkeni belirtildi. '{0}' null olamaz. @@ -4242,15 +1782,6 @@ Always Encrypted kullanılarak şifrelenen veritabanı sütunlarındaki hesaplamalar için SQL Server örneğinde yapılandırılan kapanımın geçerli ve güvenli olup olmadığını doğrulamak amacıyla kullanılacak bir kapanım kanıtlama hizmeti uç noktası belirtir. - - Kapanım sağlayıcıları için yapılandırma bölümü okunamadı. Uygulama yapılandırma dosyanızda bölümün doğru biçimlendirildiğinden emin olun. Hata İletisi: {0} - - - '{0}' adı için '{1}' türüne sahip kapanım sağlayıcısının örneği oluşturulamadı. Hata iletisi: {2} - - - İç Hata. SqlColumnEncryptionEnclaveProviderName null veya boş olamaz. - '{0}' sonuç kümesi okunurken hata oluştu. Kanıtlama URL'si bağlantı dizesinde belirtilmemiş, ancak sorgu kapanım hesaplamaları gerektiriyor. Kapanım Türü '{1}'. @@ -4260,9 +1791,6 @@ İç Hata. Kapanım hesaplamaları gerektiren bir sorgu yürütülürken SQL Server'dan alınan kapanım türü null veya boş. - - Bir sorgunun yürütülmesi kapanım hesaplamaları gerektiriyor ancak uygulama yapılandırmasında kapanım sağlayıcısı bölümü eksik. - Kapanım türü '{0}' ve kanıtlama protokolü '{1}' için kapanım sağlayıcısı bulunamadı. Lütfen bağlantı dizesinde doğru kanıtlama protokolünü belirtin. @@ -4320,9 +1848,6 @@ Bağlantıdaki tüm komutlar için varsayılan sütun şifreleme ayarı. - - Gizli veriler sunucuya gönderilmeden önce parametrenin şifrelenmesini gerektirir. - SQL Server adlarını ve bunların güvenilen sütun ana anahtar yollarını içeren sözlük nesnesi. @@ -4332,9 +1857,6 @@ ApplicationIntent ReadOnly bağlantı seçeneğiyle yansıtılmış bir SQL Server örneğine bağlanma desteklenmiyor. - - Beklenmeyen yönlendirme bilgisi alındı. - Geçersiz yönlendirme bilgisi alındı. @@ -4485,9 +2007,6 @@ Bu bağlamda belirtilen desteklenmeyen kimlik doğrulama: {0} - - Active Directory Etkileşimli kimlik doğrulaması zaman aşımına uğradı. Kullanıcının kimlik doğrulama isteğine yanıt vermesi çok uzun sürdü. - Kimlik Bilgisi özelliği ayarlanmışsa 'Authentication=Active Directory Interactive' kullanılamaz. @@ -4497,9 +2016,6 @@ Etkin bağlantının Sunucu İşlem Kimliği (SPID). - - Active Directory Cihaz Kodu Akış kimlik doğrulaması zaman aşımına uğradı. Kullanıcının kimlik doğrulama isteğine yanıt vermesi çok uzun sürdü. - Bağlantı dizesinde 'Authentication=Active Directory Device Code Flow' belirtildiyse, Kimlik Bilgisi özelliği ayarlanamaz. @@ -4533,9 +2049,6 @@ Yalnızca tek boyutlu diziler, istenen eylem için destekleniyor. - - Belirtilen öğe belirtilen Koleksiyonda bulunamadığı için kaldırılamıyor. - Negatif olmayan sayı gerekiyor. @@ -4563,87 +2076,36 @@ Bağlantı dizesinde 'Authentication=Active Directory Default' belirtilmişse AccessTokenCallback özelliği ayarlanamaz. - - {0} {1} döndürdü. - - - Protokol hatası: Alınan ileti geçerli bir imza içeriyor ancak geçerli Koruma Düzeyinin gerektirdiği gibi şifrelenmemiş. - - - {0}, {1} hatasıyla başarısız oldu. - Bu metot, bu sınıf tarafından uygulanmıyor. - - İstenen güvenlik paketi desteklenmiyor. - Yanlış physicalConnection türü. AppContext Switch '{0}'={1} ayarlanırken özel durum oluştu. - - {0} türü, bu platformda desteklenmiyor. - Küreselleşme Değişmez Modu desteklenmiyor. - - Kerberos kullanılarak kimlik doğrulanamıyor. Kerberos kimlik doğrulaması yapılabilmesi için Kerberos'un istemcide 'kinit' ile başlatılmış olduğundan ve SQL Server’da Hizmet Asıl Adı'nın kaydedildiğinden emin olun. - 'Ağ Kitaplığı' anahtar sözcüğü bu platformda desteklenmiyor. Bunun yerine istenen protokolle 'Veri Kaynağı' ön eki ekleyin (TCP bağlantısı için 'tcp:', Adlandırılmış Kanal bağlantısı için 'np:'). Yuva, '{1}' hata kodunu vererek beklenen '{0}' durumunu oluşturmadı. - - Sunucu, bu platformda desteklenmeyen bir özelliği kullanmaya çalışıyor. - '{0}' anahtar sözcüğü, bu platformda desteklenmiyor. Şu anda yüklü olan System.Transactions.dll, Genel İşlemleri desteklemiyor. - - Sunucudan veri okunurken desteklenmeyen bir '{0}' belirteci alındı. - - - '{0}', desteklenen bir tanıtıcı türü değil. - SqlFileStream, bu platformda desteklenmiyor. LocalDB, bu platformda desteklenmiyor. - - Yetersiz arabellek alanı. Gerekli:{0}: Olan: {1}. - - - GSSAPI işlemi şu durumla başarısız oldu: {0} (İkincil durum: {1}). - - - GSSAPI işlemi, {0} ({1}) hatasıyla başarısız oldu. - - - Windows dışındaki işletim sistemlerinde kanal bağlama desteği bulunmuyor. - - - Varsayılan kimlik bilgileri geçilirse hedef adı boş olmamalıdır. - - - İstenen koruma düzeyi, şu anda yüklü olan GSSAPI uygulamasıyla desteklenmiyor. - - - Sunucu uygulaması desteklenmiyor. - - - Bu platformda, NTLM kimlik doğrulaması varsayılan kimlik bilgileriyle mümkün değildir. - DataAdapter.Update tarafından kullanıldığında, parametre değeri DBNull.Value değerinden (Int32)1 değerine veya null değilse, (Int32)0 değerine değiştirilir. @@ -4668,9 +2130,6 @@ Arabellek boyut üst sınırı aşıldı: {0} - - SourceColumn '{2}' için DataColumn '{0}' eksik. - SqlBatchCommand listesi başlatılmadı. 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 f4f6ebd924..8f881105c9 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 升序 - 此 {1} 的 Count={2} 的索引 {0} 无效。 @@ -132,18 +129,12 @@ 另一个 {1} 中已包含 {0}。 - - 此 {1} 中已包含 {0}。 - {0} 只接受非空的 {1} 类型对象。 试图移除此 {1} 不包含的 {0}。 - - {0}.{1} 必须是唯一的,“{2}”在集合中已存在。 - 连接未关闭。 {0} @@ -171,12 +162,6 @@ 阅读器关闭时尝试调用 {0} 无效。 - - 无法登记在事务中,因为该连接是委派事务或提升事务的主要连接。 - - - 降序 - 应为“{0}”参数的非空字符串。 @@ -204,9 +189,6 @@ ConnectionString 属性尚未初始化。 - - 捕获到非 CLS Exception。 - 给定的安全元素不是权限元素。 @@ -252,9 +234,6 @@ 数据 - - StateChange - 更新 @@ -264,48 +243,12 @@ ConnectionState,指示连接是打开的还是关闭的。 - - 当连接更改状态时触发的事件。 - - - 参数一般类型。 - - - 输入、输出或双向参数。 - - - 用于生成强类型代码的设计时属性。 - - - 可变长度数据类型中的偏移量。 - - - 参数的名称。 - - - 可变长度数据类型(字符串和数组)。 - - - 在由 DataAdapter.Update 使用时,是用于在 ColumnMappings 中查找 DataSetColumn 名称的源列名。这将在参数与数据行之间复制值。 - - - 由 DataAdapter.Update (仅 UpdateCommand)使用时,用于更新数据源的 DataRow 值的版本。 - - - 在 DataAdapter.Update 使用时,参数值由 DBNull.Value 更改为(Int32)1 或(Int32)0 (如果非空)。 - - - 参数值。 - 因为执行 SQL 查询失败,所以无法生成“{0}”集合。有关详细信息,请参见内部异常。 除必需的架构(“{0}”)支持外,还提供了更多的限制。 - - 对于“{0}”架构集合的“{1}”限制,“{2}”不是有效值。 - 请求的集合({0})未定义。 @@ -315,15 +258,6 @@ 此版本的提供程序不支持请求的集合 ({0})。 - - 缺少必需的 DataSourceInformation 表列之一。 - - - DataSourceInformation 表必须只包含一行。 - - - 限制集合中缺少一个或多个必选列。 - 限制集合中存在没有匹配行的限制。 @@ -342,42 +276,21 @@ 元数据 XML 中缺少集合“{0}”。 - - 元数据 XML 无效。 - - - 元数据 XML 无效。{0} 集合必须包含 {1} 列,并且必须为字符串列。 - 元数据 XML 无效。{0} 集合的 {1} 列必须包含非空字符串。 - - 操作 - - - 行为 - 填充 InfoMessage - - 映射 - StatementCompleted - - UDT - 通知 - - 架构 - XML @@ -393,9 +306,6 @@ 池 - - 命名 ConnectionString - 安全性 @@ -408,2073 +318,204 @@ 连接复原能力 - - 保留自定义用户信息的集合。 - - - 指示在 DataSet 内比较字符串是否区分大小写。 - - - 该 DataSet 的名称。 - - - 指示 DataSet 中所包含数据的自定义“视图”。此视图允许通过自定义数据视图进行筛选、搜索和导航。 - - - 指示是否遵循约束规则。 - - - 指示 DataSet 有错误。 - - - 指示在 DataSet 中比较字符串时所用的区域设置。 - - - 指示此 DataSet 指向的根元素的 XML URI 命名空间。 - - - 指示用于此 DataSet 的命名空间的前缀。 - - - 保存此 DataSet 的关系的集合。 - - - 保存此 DataSet 的表的集合。 - - - 当无法合并具有相同名称的两个表的架构时发生。 - - - 在 Initialization 完成之后发生。 - - - 表示数据在内存中的缓存。 - - - 指示在表中比较字符串是否区分大小写。 - - - 返回此表的子关系。 - - - 保留该表的列的集合。 - - - 保留该表的约束的集合。 - - - 指示此表所属的 DataSet。 - - - 这是该表的默认 DataView。 - - - 用于计算此行的数据绑定值的表达式。 - - - 返回该表是否有错误。 - - - 指示在表中比较字符串时所用的区域设置。 - - - 指示此表最初的起始大小。 - - - 指示此表中所包含元素的 XML URI 命名空间。 - - - 指示 XML 表示形式中用于此表的命名空间的前缀。 - - - 返回此表的父关系。 - - - 指示表示此表主键的列。 - - - 指示保留此表的数据行的集合。 - - - 指示在 DataSet 的 Tables 集合中查找此表时所用的表名称。 - - - 在表中某一行被成功编辑后发生。 - - - 当更改该行以便事件处理程序能够修改或取消所作更改时发生。用户能够修改该行中的值并应能够引发异常来取消编辑。 - - - 在表中某一行被成功删除后发生。 - - - 当表中某行被标记为删除时发生。引发异常以取消删除。 - - - 当已为该列提交值时发生。用户可以修改建议的值并且应当引发异常来取消编辑。 - - - 当已为该列更改值时发生。 - - - 发生在从表中清除所有行之前。 - - - 发生在表中的所有行成功地被清除之后。 - - - 发生在实例化新的 DataRow 之后。 - - - 在 DataSet 的 Relations 集合中查找此关系时所用的名称。 - - - 指示此关系的子列。 - - - 指示此关系的父列。 - - - 指示关系是否嵌套。 - - - 对于删除,指示通过此关系应发生哪种类型的级联。 - - - 对于更新,指示通过此关系应发生哪种类型的级联。 - - - 对于接受和拒绝更改,指示通过此关系应发生哪种类型的级联。 - - - 指示此约束的子列。 - - - 指示此约束的父列。 - - - 指示此约束的子表。 - - - 指示此约束的列。 - - - 指示此约束是否是主键。 - - - 指示此约束的名称。 - - - 指示此约束的表。 - - - 指示此列中是否允许包含 null 值。 - - - 指示对于添加到该表的新行,该列是否自动递增。该列的类型必须是 Int16、Int32 或 Int64。 - - - 指示 AutoIncrement 列的起始值。 - - - 指示 AutoIncrement 列所使用的增量。 - - - 指示该列的默认用户界面标题。 - - - 指示用于在 DataTable 的列集合中查找此列的名称。 - - - 返回此列所属的 DataTable。 - - - 指示在此列中存储的数据的类型。 - - - 指示向表中添加新行时所使用的默认列值。 - - - 指示该列根据其他列(而不是采用用户输入)为每行计算的值。 - - - 指示该列如何保持在 XML 中: 作为特性、元素、简单文本节点还是无定义。 - - - 指示此列中存储的元素或特性的 XML URI。 - - - 指示 xml 表示形式中用于此 DataColumn 的 Prefix。 - - - 指示 Columns 集合中此列的索引。 - - - 指示向表中添加一行后此列是否允许更改。 - - - 指示此列是否应将其在表行中的值限制为唯一的。 - - - 指示此列所允许的值的最大长度。 - - - 指示此 DataColumn 的 DateTimeMode。 - - - 指示此 DataView 以及与之关联的用户界面是否允许删除。 - - - 指示此 DataView 以及与之关联的用户界面是否允许编辑。 - - - 指示此 DataView 以及与之关联的用户界面是否允许添加新行。 - - - 返回此视图中当前的项数。 - - - 这使指针返回到拥有此 DataSet 的 DataViewManager (如果有)。 - - - 指示此视图是否处于打开状态。 - - - 指示用于筛选此 DataView 返回的数据的表达式。 - - - 指示此 DataView 返回的数据的版本。 - - - 指示列的名称和此 DataView 返回数据时所采用的顺序。 - - - 指示没有设置 Sort 属性时是否使用默认排序。 - - - 指示此 DataView 用来获取数据的表。 - - - 指示此 DataView 返回的数据被以某种方式进行了更改。 - - - 指示此 DataViewManager 的数据源。 - - - 指示相应 DataSet 中任意表的排序/筛选/状态设置。 - - - DataSet 不支持“union”或“list”作为 simpleType。 - - - 无效的 {0} 语法: 缺少必需的“{1}”特性。 - - - 值“{1}”对于特性“{0}”无效。 - - - 特性“{0}”的值应为“{1}”或“{2}”。 - - - 找不到 ElementType name='{0}'。 - - - 在关系“{0}”中缺少父表名称。 - - - 在关系“{0}”中缺少子表名称。 - - - 在关系“{0}”中缺少父表键。 - - - 在关系“{0}”中缺少子表键。 - - - 未定义的数据类型:“{0}”。 - - - 未定义数据类型。 - - - 字段节点内的 XPath 选择无效。未找到: {0}。 - - - 选择器节点中的 XPath 选择无效: {0}。 - - - 名为 {0} 的约束中的“Key”节点无效。 - - - 约束名称 {0} 已用于架构中。 - - - 无法将“{0}”转换为类型“{1}”。 - - - 在名为“{2}”的“{1}”约束中缺少“{0}”部分。 - - - 无效的 Relation 定义: 不同长度的键。 - - - DataSet 不允许在名为“{0}”的 ComplexType 中使用循环引用。 - - - DataSet 无法为节点 {0} 实例化抽象 ComplexType。 - - - 多目标转换器在编写 Xml Schema 时出错。有关详细信息,请参见内部异常。 - - - 多目标转换器在编写 Xml Schema 时出错。返回了 null 或空字符串。 - - - 重复的声明“{0}”。 - - - 无法加载 diffGram。目标 DataSet 中缺少表“{0}”。 - - - 无法加载 diffGram。缺少“sql”节点。 - - - 列名“{0}”是为不同的映射类型定义的。 - - - 前缀“{0}”包含特殊字符,因此是无效的。 - - - 自我嵌套表“{0}”中的循环引用。 - - - DataSet 无法扩展实体。使用 XmlValidatingReader 并相应地设置 EntityHandling 属性。 - - - 类型“{0}”未实现 IXmlSerializable 接口,因此不能继续序列化。 - - - 无法处理反序列化。数据不实现 IXMLSerializable,因此不支持多态性。 - - - DataTable 不支持来自 Xml 的架构推断。 - - - 无法继续序列化 DataTable“{0}”。它包含的 DataRow 在同一外键上有多项父行。 - - - 输入 Schema 中缺少 IsDataSet 特性。 - - - 无法确定 DataSet 元素。IsDataSet 属性出现多次。 - - - DataSet 将不对实现 IDynamicMetaObjectProvider 但不实现 IXmlSerializable 的类型进行序列化。 - - - 未实现此功能。 {0}。 - - - 语法错误:“{0}”运算符后缺少操作数。 - - - 表达式“{0}”中类型不匹配。 - - - 表达式太复杂。 - - - 未找到列 [{0}]。 - - - 表达式包含无效的字符串常量: {0}。 - - - 表达式包含未定义的函数调用 {0}()。 - - - 表达式中有语法错误。 - - - 无效的参数数目: 函数 {0}()。 - - - 表达式缺少右括号。 - - - 无法解释位置 {1} 的标记“{0}”。 - - - 应为 {0},但位置 {2} 的实际标记是 {1}。 - - - 无法从 {0} 转换为 {1}。 - - - 无法将值 ‘{0}’ 转换为类型: {1}。 - - - 无效列名称 [{0}]。 - - - 表达式包含无效的日期常量“{0}”。 - - - 在 IN 运算符的表达式列表中只允许常量表达式。 - - - Like 运算符中出错: 字符串模式“{0}”无效。 - - - 语法错误: IN 关键字后面的项必须用逗号分隔,并且必须用括号括起来。 - - - 函数参数中的类型不匹配: {0}(),参数 {1},应为 {2}。 - - - 函数参数中的类型不匹配: {0}(),参数 {1},应为一种 Integer 类型。 - - - 无法在 {1} 和 {2} 上执行“{0}”操作。 - - - 运算符“{0}”对于类型“{1}”和“{2}”的操作数具有多义性。不能将有符号和无符号的类型混合起来。请使用显式 Convert() 函数。 - - - 语法错误: IN 关键字后面必须是由逗号分隔的非空表达式列表,并且必须用括号括起来。 - - - 表达式包含不支持的运算符“{0}”。 - - - 表达式包含无效名称:“{0}”。 - - - 语法错误:“{0}”运算符前缺少操作数。 - - - 表达式中右括号过多。 - - - 表 [{0}] 涉及不只一个关系。您必须在表达式“{1}”中明确提出一个关系名称。 - - - 聚合参数中的语法错误: 需要具有可能的“Child”限定符的单个列参数。 - - - 聚合表达式“{0}”中的未绑定引用。 - - - 没有当前行就无法计算非常数表达式。 - - - 表达式“{0}”中的未绑定引用。 - - - 无法计算。表达式“{0}”不是聚合。 - - - 筛选表达式 ‘{0}’ 的计算结果不是布尔项。 - - - 无效的类型名称“{0}”。 - - - 查找表达式中的语法错误: 关键字“Parent”之后应当是具有可能的关系限定符的单个列参数: Parent[(<relation_name>)].<column_name>。 - - - 需要一行或一个表来调用 DataFilter。 - - - {0}()参数超出范围。 - - - 语法错误:“Is”运算符的用法无效。正确语法是: <expression> Is [Not] Null。 + + 偏移量和长度超出数组的界限,或者计数大于从索引到源集合结尾处的元素数量。 - - 值对于类型“{0}”而言或者太大或者太小。 + + 无法将类型为“{0}”的对象转换成类型为“{1}”的对象。 - - 出现被零除错误。 + + “{0}”可接受的唯一值是“{1}”。 - - 找不到父关系“{0}”。 + + 属性“{0}”可以接受的值为“{1}”或“{2}”。 - - “hours”参数超出范围。其值必须在 -14 到 +14 之间。 + + 指定的 QuotePrefix 和 QuoteSuffix 值不匹配。 - - “minutes”参数超出范围。其值必须在 -59 到 +59 之间。 + + 参数“{0}”的长度超出其“{1}”的限制。 - - 为时区提供的范围超出了合计的 14 小时。 + + 键“{0}”的值无效。 - - 所提供的 DateTime 参数的 Kind 属性与“hours”和“minutes”参数不匹配。 + + 使用键“{0}”要求存在键“{1}”。 - - 类型为“{0}”的 DataColumn 不支持表达式。 + + 密钥“{0}”值的长度超出其限制 {1}。 - - 未能启用约束。一行或多行中包含违反非空、唯一或外键约束的值。 + + 不支持的关键字:“{0}”。 - - 集合本身是不可修改的。 + + 内部 .NET Framework 数据提供程序错误 {0}。 - - 给定的名称“{0}”至少与集合对象中的两个名称匹配,但大小写不同,并且不与其中任何一个的大小写匹配。 + + {0}“{1}”。 - - 给定的名称“{0}”至少与集合对象中具有不同命名空间的两个名称相匹配。 + + {0} "{1}",引号的用法不正确。 - - 偏移量和长度超出数组的界限,或者计数大于从索引到源集合结尾处的元素数量。 + + {0} "{1}",当前对“{2}”的限制不够。 - - “{0}”参数超出范围。 + + SqlBulkCopy.WriteToServer 失败,因为 SqlBulkCopy.DestinationTableName 是无效的多部分名称 - - “{0}”参数不能为空。 + + 因为无效的多部分名称,处理来自 SQL Server 的结果失败 - - “{0}”参数包含 Null 值。 + + SqlParameter.UdtTypeName 是无效的多部分名称 - - 无法找到列 {0}。 + + SqlParameter.TypeName 是无效的多部分名称 - - 列“{0}”已属于此 DataTable。 + + SqlCommand.DeriveParameters 失败,因为 SqlCommand.CommandText 属性值是无效的多部分名称 - - 列“{0}”已属于另一个 DataTable。 + + 不支持使用 MultiSubnetFailover 连接选项连接到镜像的 SQL Server 实例。 - - 不能在 DataTable 中包含一个以上的 SimpleContent 列。 + + {2} 方法不支持 {0} 枚举值 {1}。 - - 无法将 SimpleContent 列添加到包含元素列或嵌套关系的表中。 + + .NET Framework SqlClient 数据提供程序不支持 {0} 枚举值 {1}。 - - 名为“{0}”的列已属于此 DataTable。 + + {0}: CommandText 属性尚未初始化 - - 无法添加名为“{0}”的列: 此 DataTable 中已存在同名的嵌套表。 + + {0}: Connection 属性尚未初始化。 - - 此 DataTable 中已经有一个名为“{0}”的列: 无法将嵌套表的名称设置为相同的名称。 + + {0} 要求已打开且可用的 Connection。{1} - - 不能移除不属于此表的列。 + + 存储过程“{0}”不存在。 - - 此列是主键的一部分,因此不能移除。 + + 事务要么与当前连接无关联,要么已完成。 - - 此列是关系 {0} 的父键的一部分,因此不能移除。 + + 与当前连接相关联的事务已经完成,但尚未释放。必须先释放该事务,然后才能使用该连接来执行 SQL 语句。 - - 此列是表 {1} 的约束 {0} 的一部分,因此不能移除。 + + 如果分配给命令的连接位于本地挂起事务中,{0} 要求命令拥有事务。命令的 Transaction 属性尚未初始化。 - - 此列是表达式 {0} = {1} 的一部分,因此不能移除。 + + 已有打开的与此 {0} 相关联的 DataReader,必须首先将它关闭。 - - 无法为计算所得的列设置 AutoIncrement 属性。 + + {0} DeriveParameters 仅支持 CommandType.StoredProcedure,不支持 CommandType.{1}。 - - 无法为已设置 DefaultValue 的列设置 AutoIncrement 属性。 - - - 在 AutoIncrement 列上不能设置 DefaultValue。 - - - AutoIncrementStep 必须是非零值。 - - - 当 ColumnName 作为 DataTable 的一部分时是需要的。 - - - 在列包含数据的情况下不能更改其 DataType。 - - - 列要求有效的 DataType。 - - - {0} 列的 DefaultValue 的类型为 {1},并且不能被转换为 {2}。 - - - 该列的 DefaultValue 的类型为 {0},并且不能被转换为 {1}。 - - - {0} 列的 DefaultValue 的类型为 {1},但该列的类型为 {2}。 - - - 不能更改表达式列的 ReadOnly 属性。 - - - 不能更改表达式列的 Unique 属性。 - - - 在具有 AutoIncrement 或 Unique 的列上不能创建表达式。 - - - 不能设置表达式,因为列不能被设置为 ReadOnly。 - - - 不能在 {0} 列上设置 Expression 属性,因为它是约束的一部分。 - - - 不能基于 Expression 列 {0} 创建约束。 - - - 不能设置 Expression 属性,因为表达式中存在循环引用。 - - - 列“{0}”中包含 null 值。 - - - 列“{0}”不允许 nulls。 - - - 列“{0}”为只读。 - - - 列“{0}”包含不唯一的值。 - - - 列“{0}”不属于表 {1}。 - - - 列必须属于某个表。 - - - 不能在 {1} 列中存储 <{0}>。所需类型是 {2}。 - - - 不能将 Column“{0}”设置为 null。请改用 DBNull。 - - - 无法设置列“{0}”。此值违反了此列的 MaxLength 限制。 - - - MaxLength 仅适用于字符串数据类型。您不能将 Column“{0}”的属性 MaxLength 设置为非负数字。 - - - 无法将 Column“{0}”的属性 MaxLength 设置为“{1}”。表中至少存在一个长度超过新的限制的字符串。 - - - 无法设置 Column“{0}”的属性 MaxLength。该列为 SimpleContent。 - - - 无法将 Column“{0}”的属性 DataType 设置为 {1}。该列为 SimpleContent。 - - - 无法将 Column“{0}”的属性 MappingType 设置为 SimpleContent。Column DataType 为 {1}。 - - - Column“{0}”超过 MaxLength 限制。 - - - 列“{0}”不允许 DBNull.Value。 - - - 无法更改 Column“{0}”的 Namespace 属性。该列为 SimpleContent。 - - - 类型为“{0}”的 DataColumn 一旦拥有数据,就无法更改其 AutoIncrement。 - - - 列“{0}”不属于基础表“{1}”。 - - - 类型“{0}”的 DataColumn 是 complexType。无法将复杂类型的值序列化为 Attribute - - - 只可在 DateTime 类型的 DataColumns 上设置 DateTimeMode。 - - - “{0}”是无效的 DataSetDateTime 值。 - - - 一旦表具有数据,就无法将 DateTimeMode 由“{0}”更改为“{1}”。 - - - Type“{0}”不包含 static Null 属性或字段。 - - - 类型“{0}”不实现 IRevertibleChangeTracking;因此不能使用 RejectChanges() 继续。 - - - 只能在具有 Unchanged DataRowState 的 DataRows 上调用 SetAdded 和 SetModified 。 - - - 序数 ‘{0}’ 超出最大数字。 - - - DataSet 不支持 System.Nullable<>。 - - - 当约束处于 ConstraintCollection 中时,不能将其名称更改为空字符串。 - - - 不能在约束 {0} 上强制约束。 - - - 列“{0}”被约束为是唯一的。值“{1}”已存在。 - - - 约束“{0}”不属于此 DataTable。 - - - 无法找到约束 {0}。 - - - 约束与集合中已有的名为 {0} 的约束匹配。 - - - 名为“{0}”的 Constraint 已属于此 DataTable。 - - - 这些列当前不具有唯一值。 - - - 这些列不指向此表。 - - - 不能启用此约束,因为不是所有的值都具有相应的父值。 - - - 不能添加此约束,因为 ForeignKey 不属于表 {0}。 - - - 不能移除不属于该表的约束。 - - - 无法删除唯一约束“{0}”。首先删除外键约束“{1}”。 - - - 不能删除此行,原因是约束在关系 {0} 上是强制的,并且删除此行将影响子行。 - - - 不能进行此更改,原因是约束在关系 {0} 上是强制的,并且更改此值将影响子行。 - - - 不能清除表 {0},原因是 ForeignKeyConstraint {1} 强制约束,并且在 {2} 中有子行。 - - - ForeignKeyConstraint {0} 要求在父表中存在子键值({1})。 - - - 无法访问属性,因为“{0}”。 - - - 不能移除此行,原因是它具有子行,并且关系 {0} 上的约束是强制的。 - - - 已经为该表设置了主键,因此无法添加主键约束。 - - - 不能将约束添加到作为两个嵌套关系中的子表的 DataTable“{0}”。 - - - 不能根据属于多个不同表的列来创建键。 - - - 不能没有列。 - - - 包含的列不能超过 {0} 个。 - - - 当同一个列被列出不只一次时,不能创建键:“{0}” - - - 唯一约束是表的主键,因此不能移除。 - - - 唯一约束是表 {0} 的主键,因此不能移除。 - - - 父列和子列不具有类型匹配的列。 - - - ParentKey 和 ChildKey 完全相同。 - - - ParentColumns 和 ChildColumns 的长度应当相同。 - - - ParentColumns 和 ChildColumns 的长度不能为零。 - - - 该行不属于与此关系相同的 DataSet。 - - - 当 RelationName 是 DataSet 的一部分时,它是必需的。 - - - GetChildRows 要求表为 {0} 的行,但指定行的 Table 是 {1}。 - - - 此关系应连接 DataSet 中的两个表,以使该关系添加到该 DataSet 中。 - - - GetParentRow 要求表为 {0} 的行,但指定行的表是 {1}。 - - - SetParentRow 要求表为 {0} 的子行,但指定行的表是 {1}。 - - - 在不同 DataSet 中的表之间不能建立关系。 - - - 不能在不同 DataSet 的各表之间建立关系。 - - - 这些子列之间已经存在某种关系。 - - - 该关系不属于此关系集合。 - - - 该关系已经属于另一个 DataSet。 - - - 该关系已经属于此 DataSet。 - - - 名为“{0}”的 Relation 已经属于此 DataSet。 - - - Relation {0} 不属于此 DataSet。 - - - 无法找到关系 {0}。 - - - 不能在 null 表上创建集合。 - - - 此集合为其显示关系的表已经从其 DataSet 中移除。 - - - 如果此表在它的 ParentRelation 集合中不是子表,则不能将关系添加到该集合中。 - - - 如果此表在它的 ChildRelation 集合中不是父表,则不能将关系添加到该集合中。 - - - 不能将此关系的“Nested”属性设置为 false。 - - - 同样的表“{0}”不能作为两个嵌套关系中的子表。 - - - 表({0})在嵌套关系中不能是其自身的子表。 - - - 无法添加在父表和子表之间具有不同 Locale 或 CaseSensitive 设置的 DataRelation 或 Constraint 。 - - - 如果父列或子列不在 DataSet 中,则无法创建 DataRelation。 - - - 嵌套表“{0}”的命名空间是继承而来的,不能在不同的命名空间中拥有多个父表。 - - - 具有空命名空间的嵌套表“{0}”不能在不同命名空间中具有多个父表。 - - - 该行不属于与此关系相同的 DataSet。 - - - 不能对该表中没有的行执行此操作。 - - - 此关系和子行不属于同一个 DataSet。 - - - 不能在 RowChanging 事件中更改建议的值。 - - - 不能在 OnRowChanging 事件内部调用 EndEdit()。 - - - 不能在 RowChanging 事件内部调用 BeginEdit()。 - - - 不能在 OnRowChanging 事件内部调用 CancelEdit()。引发 exception 以取消此更新。 - - - 不能在 OnRowDeleting 事件内部调用 Delete。引发 exception 以取消此删除。 - - - 输入阵列的长度超过了此表中的列数。 - - - 没有要访问的 Current 数据。 - - - 没有要访问的 Original 数据。 - - - 没有要访问的 Proposed 数据。 - - - 此行已从表中移除并且没有任何数据。BeginEdit() 将允许在此行中创建新数据 - - - 不能通过已删除的行访问该行的信息。 - - - 版本必须是“Original”、“Current”或“Proposed”。 - - - 在位置 {0} 处没有任何行。 - - - 行插入位置 {0} 无效。 - - - 对于名为“{1}”的表,找到了两次 rowOrder value={0}。 - - - 表“{0}”的 rowOrder 序列中缺少值。 - - - 给定的 DataRow 不在当前的 DataRowCollection 中。 - - - 该行已经属于另一个表。 - - - 该行已经属于此表。 - - - 此行已被删除,因此不能删除。 - - - 此行是空行。 - - - 不能移除已被移除的行。 - - - 子行具有多个父行。 - - - 未识别的行状态位模式。 - - - 不能将此 DataSet 的名称更改为空字符串。 - - - 名称“{0}”无效。DataSet 不能与 DataTable 同名。 - - - 该架构命名空间无效。请改用此命名空间: {0}。 - - - 无法更改 CaseSensitive 或 Locale 属性。该更改可能导致至少一个 DataRelation 或 Constraint 在其相关表之间具有不同的 Locale 或 CaseSensitive 设置。 - - - SchemaSerializationMode 属性只有在被派生的 DataSet 覆盖时才可以设置。 - - - PrimaryKey 列不属于此表。 - - - 不能将嵌套关系或元素列添加到包含 SimpleContent 列的表中。 - - - 当 TableName 是 DataSet 的一部分时,它是必需的。 - - - DataTable 已有简单的内容列。 - - - 表没有主键。 - - - {0} 不是有效的 Sort 字符串项。 - - - 无法序列化 DataTable。一个或多个 DataColumn 表达式中使用的 DataTable 不是当前 DataTable 的后代。 - - - 此 DataTable 只能远程作为 DataSet 的一部分。一个或多个表达式列引用了其他数据表。 - - - 不能对 DataSet 和 DataTable 使用不同的远程格式属性值。 - - - 无法序列化 DataTable。未设置 DataTable 名称。 - - - 名为“{0}”的 DataTable 已属于此 DataSet。 - - - 具有同一 Namespace“{1}”、名为“{0}”的 DataTable 已属于此 DataSet。 - - - 在嵌套关系中,表({0})不能是自身的子表: DataSet 名称与表名称冲突。 - - - 名称“{0}”无效。DataTable 不能与 DataSet 同名。 - - - DataTable 已属于另一个 DataSet。 - - - DataTable 已属于此 DataSet。 - - - 表 {0} 不属于此 DataSet。 - - - 无法找到表 {0}。 - - - 不能移除包含现有关系的表。请首先移除关系。 - - - 不能移除表 {0},原因是它在 ForeignKeyConstraint {1} 中被引用。请首先移除该约束。 - - - DataTable“{0}”与源中的任意 DataTable 都不匹配。 - - - 目标 DataSet 缺少 {0} 的定义。 - - - 目标 DataSet 缺少 {0} {1}。 - - - <target>.{0} 和 <source>.{0} 的属性冲突: DataType 属性不匹配。 - - - <target>.PrimaryKey 和 <source>.PrimaryKey 的 Length 不相同。 - - - PrimaryKey 中的列不匹配 : <target>.{0} 与 <source>.{1} 不匹配。 - - - 无法合并关系 {0},因为键的列不匹配。 - - - 目标表 {0} 缺少列 {1} 的定义。 - - - 源表中不存在 PrimaryKey 列 {0}。 - - - RowStates 参数必须设置为 DataViewRowState 枚举值的有效组合。 - - - 查找基于排序顺序来查找行,但未指定任何排序顺序。 - - - 被编制索引的键需要 {0} 个值,但收到了 {1} 个值。 - - - 无效的聚合函数 {0}()和类型 {1} 的用法。 - - - 无效的存储类型: {0}。 - - - DataSet Xml 持久性不支持值“{0}”为 Char 值,请改用 Byte 存储。 - - - 值类型与列类型不匹配 - - - 类型“{0}”不实现 IComparable 接口。无法进行比较。 - - - 不能设置 {0}。 - - - 不能在已成为 DataSet 默认视图的 DataViewManager 上更改 DataSet。 - - - RowStateFilter 不能同时显示 ModifiedOriginals 和 ModifiedCurrents。 - - - 不能在 DefaultView 或来自 DataViewManager 的 DataView 上更改 Table 属性。 - - - DataSet 属性一旦设置便无法更改。 - - - 在使用 DataViewManager 前必须设置 DataSet。 - - - Table 属性一旦设置便无法更改。 - - - 在使用 DataView 前必须设置 DataTable。 - - - 无法绑定到没有名称的 DataTable。 - - - 不能将一个对象设置到此列表中。 - - - 不能对 AllowNew 为假的 DataView 调用 AddNew。 - - - DataView 未打开。 - - - 该关系不是此 DataView 指向的表的父关系。 - - - 不能对 AllowDelete 为 false 的 DataSource 执行删除。 - - - 不能对 AllowEdit 为 false 的 DataSource 进行编辑。 - - - 索引 {0} 不是为负数,就是大于行数。 - - - 不能将外部对象添加到此列表。 - - - 不能清除此列表。 - - - 不能将外部对象插入此列表。 - - - 不能移除列表中没有的对象。 - - - {0} 既不是表 {1} 的 DataColumn 也不是 DataRelation。 - - - 在 Range 对象中,Min ({0})必须小于或等于 max ({1})。 - - - 这是 null 范围。 - - - MinimumCapacity 必须是非负的。 - - - 无法为名称“{0}”生成标识符。 - - - 存在多个具有相同名称“{0}”的表(虽然命名空间不同)。 - - - 列“{0}”: 类型“{1}”不能为 null。 - - - 列“{0}”: 类型“{1}”没有无参数构造函数。 - - - 列“{0}”: 类型“{1}”没有带字符串参数的构造函数。 - - - 无法将类型为“{0}”的对象转换成类型为“{1}”的对象。 - - - Data Exception。 - - - Constraint Exception。 - - - 已删除的行不可访问。 - - - 不允许重名。 - - - RowChanging 事件中不支持操作。 - - - 无效的约束。 - - - 缺少主键。 - - - 不允许 Null。 - - - 列被标记为只读。 - - - 在表中找不到行。 - - - 找不到版本。 - - - 修改了 ReadOnly 数据。 - - - 当前 DataTable“{0}”的 DataTableReader 无效。 - - - DataTableReader 中的当前 DataTable“{0}”的架构已更改,DataTableReader 无效。 - - - 无法创建 DataTableReader。DataSet 中没有 DataTable。 - - - 无法创建 DataTableReader。参数为空。 - - - 无法创建 DataTableReader。参数包含 null 值。 - - - 当前 DataRow 的状态不是为 Deleted 就是为 Detached。 - - - 当前 DataTable“{0}”为空。DataTable 中没有 DataRow。 - - - DataTable 内部索引已损坏:“{0}”。 - - - 集合已修改;枚举操作可能无法执行。 - - - 已使用不同的 ‘{1}’ 声明简单类型 ‘{0}’。 - - - 无效的编页。 - - - 只要相关的 DataSet 被映射到加载的 XML 文档中,就不能更改该表名称。 - - - 只要相关的 DataSet 被映射到加载的 XML 文档中,就不能更改该表命名空间。 - - - 只要相关的 DataSet 被映射到加载的 XML 文档中,就不能更改该列名称。 - - - 只要相关的 DataSet 被映射到加载的 XML 文档中,就不能更改列命名空间。 - - - 关联的 DataSet 映射到已加载的 XML 文档后,就不能更改 ColumnMapping 属性。 - - - 只要 DataSet 被映射到加载的 XML 文档中,就不能在该表中添加或移除列。 - - - 只要 DataSet 被映射到加载的 XML 文档中,就不能在该 DataSet 中添加或移除表。 - - - 只要 DataSet 被映射到加载的 XML 文档中,就不能在该 DataSet 中添加、移除或更改嵌套的关系。 - - - 该 DataSet 参数无效。它不能为 null。 - - - 只要 DataSet 被映射到加载的 XML 文档中,就不能更改 DataSet 的名称。 - - - 此节点类型不能克隆: {0}。 - - - 如果 XmlDataDocument 已经包含数据,则无法加载。请使用新的 XmlDataDocument。 - - - DataSet 最多可与一个 XmlDataDocument 关联。不能将 DataSet 与当前的 XmlDataDocument 关联,因为该 DataSet 已与另一个 XmlDataDocument 关联。 - - - 在尝试使用 XML 操作编辑 XmlDataDocument 之前请设置 DataSet.EnforceConstraints == false。 - - - DataDocument 上不支持 GetElementById()。 - - - 不能在 DataDocument 上创建实体引用。 - - - XmlDataDocument 不支持在 DateSet 和 DataTable 上使用 Clear 函数。 - - - 不能移除列,因为它内置于此 dataSet 中。 - - - 无法移除关系,因为它内置于此 dataSet 中。 - - - 每当更改该控件的属性时发生。 - - - 每当此集合的成员更改时发生。 - - - 无法获取值,因为它是 DBNull。 - - - “{0}”属性需要 Microsoft WindowsNT 或基于 WindowsNT 的操作系统。 - - - 找不到请求的 .NET Framework 数据提供程序。它可能没有安装。 - - - 请求的 .NET Framework 数据提供程序实现不包含具有 System.Data.Common.DbProviderFactory 派生类型的 Instance 字段。 - - - 找不到或无法加载已注册的 .NET Framework 数据提供程序。 - - - 缺少必需的 .NET Framework 数据提供程序程序集限定名称。 - - - 只允许元素。 - - - 不允许子节点。 - - - 无法识别的特性“{0}”。 - - - 无法识别的元素。 - - - 每个配置文件中只能出现一次“{0}”节。 - - - 找不到所需的特性“{0}”。 - - - 必需的特性“{0}”不能为空。 - - - 应为“{0}”参数的非空数组。 - - - “{0}”可接受的唯一值是“{1}”。 - - - 属性“{0}”可以接受的值为“{1}”或“{2}”。 - - - 指定的 QuotePrefix 和 QuoteSuffix 值不匹配。 - - - 参数“{0}”的长度超出其“{1}”的限制。 - - - 应为 {1} 类型的参数,而收到的是 {0} 类型。 - - - 键“{0}”的值无效。 - - - 使用键“{0}”要求存在键“{1}”。 - - - 密钥“{0}”值的长度超出其限制 {1}。 - - - 不支持的关键字:“{0}”。 - - - 无法加载 UDL 文件。 - - - 无效的 UDL 文件。 - - - 内部 .NET Framework 数据提供程序错误 {0}。 - - - 生成了 Insert、Update 或 Delete 命令后,QuotePrefix 和 QuoteSuffix 属性就无法更改。 - - - DataAdapter.SelectCommand 属性需要初始化。 - - - DataAdapter.SelectCommand.Connection 属性需要初始化; - - - {0}“{1}”。 - - - {0} "{1}",引号的用法不正确。 - - - {0} "{1}",当前对“{2}”的限制不够。 - - - SqlBulkCopy.WriteToServer 失败,因为 SqlBulkCopy.DestinationTableName 是无效的多部分名称 - - - 因为无效的多部分名称,处理来自 SQL Server 的结果失败 - - - SqlParameter.UdtTypeName 是无效的多部分名称 - - - SqlParameter.TypeName 是无效的多部分名称 - - - SqlCommand.DeriveParameters 失败,因为 SqlCommand.CommandText 属性值是无效的多部分名称 - - - OdbcCommandBuilder.DeriveParameters 失败,因为 OdbcCommand.CommandText 属性值是无效的多部分名称 - - - OleDbCommandBuilder.DeriveParameters 失败,因为 OleDbCommandBuilder.CommandText 属性值是无效的多部分名称 - - - 不支持使用 MultiSubnetFailover 连接选项连接到镜像的 SQL Server 实例。 - - - 来自 SourceColumn“{0}”的列映射失败,原因在于 DataColumn“{1}”是计算所得的列。 - - - {1} 的 SourceColumn“{0}”和 {3} 的 DataColumn“{2}”之间无法转换的类型不匹配。 - - - DataTable“{1}”中缺少 SourceColumn“{2}”的 DataColumn“{0}”。 - - - SourceColumn 应为非空字符串。 - - - 缺少“{0}”的 SourceColumn 映射。 - - - {2} 方法不支持 {0} 枚举值 {1}。 - - - .NET Framework Odbc 数据提供程序不支持 {0} 枚举值 {1}。 - - - .NET Framework OleDb 数据提供程序不支持 {0} 枚举值 {1}。 - - - .NET Framework SqlClient 数据提供程序不支持 {0} 枚举值 {1}。 - - - 无法检索 ComputerNameDnsFullyQualified {0}。 - - - 缺少“{1}”SourceTable 的“{0}”DataTable。 - - - SourceTable 应为非空字符串 - - - 缺少 SourceTable 映射:“{0}” - - - {0}: CommandText 属性尚未初始化 - - - {0}: Connection 属性尚未初始化。 - - - {0} 要求已打开且可用的 Connection。{1} - - - Fill: SelectCommand.Connection 属性尚未初始化。 - - - FillPage: SelectCommand.Connection 属性尚未初始化。 - - - FillSchema: SelectCommand.Connection 属性尚未初始化。 - - - 更新要求 InsertCommand 具有连接对象。InsertCommand 的 Connection 属性尚未初始化。 - - - 更新要求 UpdateCommand 命令具有连接对象。UpdateCommand 的 Connection 属性尚未初始化。 - - - 更新要求 DeleteCommand 具有连接对象。该 DeleteCommand 的 Connection 属性尚未初始化。 - - - 更新要求连接对象。Connection 属性尚未初始化。 - - - 更新要求命令克隆具有连接对象。该命令克隆的 Connection 属性尚未初始化。 - - - 更新需要连接。 - - - Update 要求 {0}Command 具有打开的连接对象。{1} - - - Update 要求 {0}Command 具有打开的连接对象。{1} - - - Update 要求 {0}Command 具有打开的连接对象。{1} - - - Update 要求更新命令具有打开的连接对象。{1} - - - 存储过程“{0}”不存在。 - - - 分配给该命令的事务必须是最深层嵌套的本地挂起事务。 - - - 事务要么与当前连接无关联,要么已完成。 - - - 与当前连接相关联的事务已经完成,但尚未释放。必须先释放该事务,然后才能使用该连接来执行 SQL 语句。 - - - 如果分配给命令的连接位于本地挂起事务中,{0} 要求命令拥有事务。命令的 Transaction 属性尚未初始化。 - - - 已有一个打开的 SqlResultSet 与必须先关闭的此命令相关联。 - - - 已有打开的与此 {0} 相关联的 DataReader,必须首先将它关闭。 - - - {0} DeriveParameters 仅支持 CommandType.StoredProcedure,不支持 CommandType.{1}。 - - - 同一次执行不能多次调用方法“{0}”。 - - - 异步结果不正确。 - - - 在调用“{0}”前,SelectCommand 属性尚未初始化。 - - - 此处不应为 StatementType {0}。 - - - FillSchema: 应当为 SourceTable 名称输入一个非空字符串。 - - - {0} 的 MaxRecords 值无效;该值必须大于等于 0。 - - - {0} 的 StartRecord 值无效;该值必须大于等于 0。 - - - Fill: 应当为 SourceTable 名称输入一个非空字符串。 - - - 分层章节列必须映射到 AutoIncrement DataColumn。 - - - DataReader.GetFieldType({0}) 返回了 null。 - - - 对 startRecords 或 maxRecords 使用非零值时,在 dataTables 数组中只指定一项。 - - - Update 无法找到 TableMapping['{0}'] 或 DataTable“{0}”。 - - - Update: 应输入非空的 SourceTable 名称。 - - - 当 TableMapping.DataSetTable=“{0}”时缺少 TableMapping。 - - - Update 要求命令复本是有效的。 - - - 在 Update 期间自动 SQL 生成要求有效的 SelectCommand。 - - - 当传递具有新行的 DataRow 集合时,Update 要求有效的 InsertCommand。 - - - 当传递具有已修改行的 DataRow 集合时,更新要求有效的 UpdateCommand。 - - - 当传递具有已删除行的 DataRow 集合时,Update 要求有效的 DeleteCommand。 - - - DataRow[{0}] 和 DataRow[0] 来自不同的数据表。 - - - RowUpdatedEvent: 发生错误;未提供更多的信息。 - - - RowUpdatingEvent: 发生错误;未提供更多的信息。 - - - 进行批处理时,命令的 UpdatedRowSource 属性值 UpdateRowSource.FirstReturnedRecord 或 UpdateRowSource.Both 无效。 - - - 违反并发性: UpdateCommand 影响了预期 {1} 条记录中的 {0} 条。 - - - 违反并发性: DeleteCommand 影响了预期 {1} 条记录中的 {0} 条。 - - - 违反并发性: 成批命令影响了预期 {1} 条记录中的 {0} 条。 + + 同一次执行不能多次调用方法“{0}”。 无效的 CommandTimeout 值 {0};该值必须大于等于 0。 - - {1}[{0}]: Size 属性具有无效大小值 0。 - - - {0}.Prepare 方法要求所有参数具有显式设置的类型。 - - - {0}.Prepare 方法要求所有可变长度参数具有显式设置的非零“Size”。 - - - {0}.Prepare 方法要求“{1}”类型的参数具有显式设置的“Precision”和“Scale”。 - - - 为 asyncResult 调用了不匹配的 end 方法。应调用 {0},但实际调用的是 {1}。 - - - 无效操作。连接被关闭。 - - - 连接已被禁用。 - - - 无法登记在事务中,因为连接上正在执行某个本地事务。请完成本地事务,然后重试。 - - - 连接当前已登记事务。请完成当前事务,然后重试。 - - - 数据库不能为空、空字符串或仅由空白组成的字符串。 - - - 参数太长。 - - - “Connect Timeout”值无效,该值必须是大于等于 0 的整数。 - - - 无效的源缓冲区(大小为 {0})偏移量: {1} - - - 无效的目标缓冲区(大小为 {0})偏移量: {1} - - - 枚举值太大,不能适应 96 位十进制。 - - - 流关闭时 {0} 的尝试无效。 - - - 指定的 SeekOrigin 值无效。 - - - 对于多个基表不支持动态 SQL 生成。 - - - 对于不返回任何基表信息的 SelectCommand 不支持动态 SQL 生成。 - - - 对于不返回任何键列信息的 SelectCommand,不支持 DeleteCommand 的动态 SQL 生成。 - - - 对于不返回任何键列信息的 SelectCommand,不支持 UpdateCommand 的动态 SQL 生成。 - - - 对于没有行版本列的 SelectCommand,不支持 DeleteCommand 的动态 SQL 生成。 - - - 对于没有行版本列的 SelectCommand,不支持 UpdateCommand 的动态 SQL 生成。 - - - 对于包含 QuotePrefix 或 QuoteSuffix 字符“{1}”的表名称“{0}”,不支持动态 SQL 生成。 - - - 读取列序号“{0}”的尝试无效。通过 CommandBehavior.SequentialAccess 只能读取“{1}”或更大的列序号。 - - - 数据类型“{0}”包含无效的日期时间数字,不能格式化为文本。 - - - 该值无法格式化为请求类型的文本。 - - - 数据类型“{0}”包含无效的最大小数位数,不能格式化为文本。 - - - 提供的文本值不是数据类型“{0}”的有效文本。 - - - “{0}”: 文本值的长度必须为偶数。 - - - “{0}”: 文本值必须是十六进制数字的字符串 - - - 未设置引号前缀时,{0} 要求已打开的连接。 - - - 无法确定正确的布尔文本值。无法创建 Boolean 文本。 - - - 不支持与数据类型“{0}”关联的本地数据类型文本。 - - - {0} 的参数数据类型无效。 - - - 不存在从对象类型 {0} 到已知的托管提供程序本机类型的映射。 - - - 无法处理由类型 {1} 返回的未知 TypeCode {0}。 - - - 没有从 DbType {0} 到已知 {1} 的映射。 - - - 使用的 SQL Server 版本不支持数据类型“{0}”。 - - - 参数值“{0}”超出范围。 - - - 指定的参数名“{0}”无效。 - - - 不支持多个返回值参数。 - - - 无效的 Size 参数值“{0}”。该值必须大于或等于 0。 - - - 参数“{0}”的值无效。该值必须大于或等于 0。 - - - 此元数据的值无效。 - - - 元数据必须为 SqlDbType.Row - - - 将参数值从 {0} 转换到 {1} 失败。 - - - {0} 不支持并行事务。 - - - 此 {0} 已完成;它再也无法使用。 - - - 记录为只读时不能调用“{0}”。 - - - DbDataRecord 为只读时不能调用“{0}”。 - - - 不支持对象类型“{0}”到数据类型“{1}”的隐式转换。 - - - 缓冲区偏移量“{1}”加上“{0}”可用字节数大于在缓冲区中传递的长度。 - - - 数据长度“{0}”小于 0。 - - - 指定长度“{0}”超出了范围。 - - - 在 dataIndex“{0}”处的 {2} 尝试无效。通过 CommandBehavior.SequentialAccess 只能从“{1}”或更大的 dataIndex 处读取。 - - - 偏移量必须指向值内的某个位置。 - - - "ODBC 托管提供程序要求为 GetSchema 索引集合指定 TABLE_NAME 限制并且不能是 Null。 - - - 方法“{0}”的参数值无效。 - - - ODBC 访问接口未从 SQLGETTYPEINFO 返回结果。 - - - 意外的 null DataTable 参数 - - - 意外的 null DataSet 参数。 - - - 连接字符串超出最大允许长度 {0}。 - - - {0} - 无法映射类型。 - - - 未知 SQL 类型 - {0}。 - - - 未知 URT 类型 - {0}。 - - - 无效的负参数! - - - 无法对打开的连接设置属性。 - - - 没有 SQL_TRANSACTION“{0}”到 System.Data.IsolationLevel 枚举值的有效映射。 - - - {0} - 无法启用连接池... - - - {0} - 无法分配环境句柄。 - - - {0} – 无法获取说明符句柄。 - - - 不在事务中 - - - OdbcType 枚举 value={0} 无效。 - - - 如果预期为 DBNull.Value 数据,请使用 IsDBNull。 - - - {0} [{1}] {2} - - - {0} - 没有可用的错误信息 - - - 连接已关闭。 - - - 内部连接没有所有者。 - - - .NET Framework Odbc 数据提供程序需要 Microsoft 数据访问组件(MDAC)版本 2.6 或更高版本。当前已安装版本为 {0}。 - - - .NET Framework OleDb 数据提供程序需要 Microsoft 数据访问组件(MDAC)版本 2.6 或更高版本。当前已安装版本为 {0}。 - - - “{1}”提供程序不支持“{0}”接口。GetOleDbSchemaTable 不可用于当前提供程序。 - - - “{0}”失败,没有可用的错误消息,结果代码: {1}。 - - - 没有可用的错误消息,结果代码: {0}。 - - - .NET Framework 数据提供程序需要 Microsoft 数据访问组件(MDAC)。请安装 Microsoft 数据访问组件(MDAC)版本 2.6 或更高版本。 - - - 用于 OLEDB 的 .NET Framework 数据提供程序(Microsoft.Data.OleDb)不支持用于 ODBC 驱动程序的 Microsoft OLE DB 提供程序(MSDASQL)。请使用用于 ODBC 的 .Net Framework 数据提供程序(System.Data.Odbc)。 - - - 用于 OLEDB 的 .NET Framework 数据提供程序禁止 OLE DB 提供程序在非交互式环境中提示用户。 - - - 未在本地计算机上注册“{0}”提供程序。 - - - “{0}”提供程序不支持 ICommandText 接口。请改用 CommandType.TableDirect。 - - - “{0}”提供程序不支持 ITransactionLocal 接口。本地事务不可用于当前提供程序。 - - - 初始化字符串的格式不符合 OLE DB 规范。在连接字符串中开始于 char[{0}] 周围。 - - - .NET Framework OLE DB 数据提供程序(Microsoft.Data.OleDb)不支持“异步处理”功能。 - - - 在 ConnectionString 中未指定 OLE DB 提供程序。示例为:“Provider=SQLOLEDB;”。 - - - ConnectionString 中指定的 OLE DB 提供程序太长。 - - - DbInfoKeywords OleDbSchemaGuid 不应有任何限制。 - - - DbInfoLiterals OleDbSchemaGuid 不需要限制。 - - - 架构 guid OleDbSchemaGuid 不应有任何限制。 - - - {0} OleDbSchemaGuid 是一个不受“{1}”提供程序支持的架构。 - - - “{0}”配置设置包含不正确数目的值。 - - - 无法加载在配置设置“{0}”中指定的 XML 文件。 - - - 命令参数[{0}] '{1}' 无效。 - - - 由于符号不匹配或数据溢出以外的其他原因,无法转换命令参数[{0}]“{1}”数据值。 - - - 因为数据值是有符号的,而提供程序使用的类型是无符号的,所以命令参数[{0}] '{1}' 的转换失败。 - - - 因为数据值溢出了提供程序所使用的类型,所以命令参数[{0}] '{1}' 的转换失败。 - - - 提供程序在发送命令参数[{0}] '{1}' 值时遇到错误,已停止处理。 - - - 参数[{0}] '{1}' 没有默认值。 - - - 参数[{0}] 出现错误: {1}。 - - - Microsoft.Data.OleDb.OleDbDataAdapter 内部错误:参数访问器 {0} {1} 无效。 - - - Parameter[{0}]: 未初始化 OleDbType 属性: OleDbType.{1}。 - - - “{0}”提供程序不支持 ICommandWithParameters 接口。当前提供程序不支持命令参数。 - - - “{0}”提供程序不支持检索过程参数信息。 - - - 无法确定服务器的十进制分隔符。不能创建非整数文本。 - - - 对象不是 ADODB.RecordSet 或 ADODB.Record。 - - - 无法从 ADODB.RecordSet 对象中检索“{0}”接口。 + + {1}[{0}]: Size 属性具有无效大小值 0。 - - 无法从 ADODB.Record 对象中检索 IRow 接口。 + + {0}.Prepare 方法要求所有参数具有显式设置的类型。 - - 类型不支持 OLE DB 接口 ISourcesRowset + + {0}.Prepare 方法要求所有可变长度参数具有显式设置的非零“Size”。 - - 因为提供程序不支持 IDBInfo,所以不能构造 ReservedWords 架构集合。 + + {0}.Prepare 方法要求“{1}”类型的参数具有显式设置的“Precision”和“Scale”。 - - 未设置属性的值,因为访问接口不支持“{0}”属性,或使用者尝试获取或设置的值所属的属性不在 Initialization 属性组中,并且未初始化数据源对象。 + + 为 asyncResult 调用了不匹配的 end 方法。应调用 {0},但实际调用的是 {1}。 - - 由于下列原因之一,未能初始化“{0}”属性: - 值数据类型不是属性的数据类型或者为非空。例如,属性为 DBPROP_MEMORYUSAGE,其数据类型为 Int32,而值数据类型为 Int64。 - 值不是有效值。例如,属性为 DBPROP_MEMORYUSAGE 而值为负值。 - 值为属性的有效值并且提供程序支持属性作为可设置的属性,但提供程序不支持指定的值。这包括在写入提供程序之后将值添加至 OLE DB 的属性中的情况。 + + 无效操作。连接被关闭。 - - Options 值无效。 + + 无法登记在事务中,因为连接上正在执行某个本地事务。请完成本地事务,然后重试。 - - ColumnID 元素无效。 + + 连接当前已登记事务。请完成当前事务,然后重试。 - - “{0}”属性被指定为应用到所有列,但实际上无法应用到一个或多个列。 + + 数据库不能为空、空字符串或仅由空白组成的字符串。 - - “{0}”属性是只读的,或者使用者试图在初始化数据源对象之后,在初始化属性组中设置属性值。使用者可以将只读属性的值设置为其当前值。如果不能将特定的列设置为可设置的列属性,则也会返回此状态。 + + 无效的源缓冲区(大小为 {0})偏移量: {1} - - 可选的“{0}”属性值未设置为指定值,而且不可能将属性设置为指定值。 + + 无效的目标缓冲区(大小为 {0})偏移量: {1} - - 未设置“{0}”属性值,因为这样做将与现有属性冲突。 + + 流关闭时 {0} 的尝试无效。 - - (已保留)。 + + 指定的 SeekOrigin 值无效。 - - 提供程序返回未知的 DBPROPSTATUS_ 值“{0}”。 + + 读取列序号“{0}”的尝试无效。通过 CommandBehavior.SequentialAccess 只能读取“{1}”或更大的列序号。 - - 访问器验证被推迟,并在该方法返回数据时已执行。绑定对于此列或参数无效。 + + {0} 的参数数据类型无效。 - - OleDbDataAdapter 内部错误: 无效的行集访问器: Ordinal={0} Status={1}。 + + 不存在从对象类型 {0} 到已知的托管提供程序本机类型的映射。 - - 由于符号不匹配或数据溢出以外的其他原因,未能转换数据值。例如,数据在数据存储区中已损坏,但该行仍可以检索。 + + 无法处理由类型 {1} 返回的未知 TypeCode {0}。 - - 提供程序无法分配返回 {0} 数据的内存。 + + 没有从 DbType {0} 到已知 {1} 的映射。 - - 转换失败,因为 {0} 数据值溢出了为使用者缓冲区中 {0} 值部分指定的类型。 + + 使用的 SQL Server 版本不支持数据类型“{0}”。 - - OleDbDataAdapter 内部错误: [get] 未知的 OLE DB 数据类型: 0x{0} ({1})。 + + 参数值“{0}”超出范围。 - - 转换失败,因为 {0} 数据值带有正负号,而为使用者缓冲区中 {0} 值部分指定的类型不带符号。 + + 指定的参数名“{0}”无效。 - - OleDbDataAdapter 内部错误: [set] 未知的 OLE DB 数据类型: 0x{0} ({1})。 + + 无效的 Size 参数值“{0}”。该值必须大于或等于 0。 - - 提供程序无法确定 {0} 值。例如,该行刚刚创建,未提供 {0} 列的默认值,并且使用者尚未设置新 {0} 值。 + + 参数“{0}”的值无效。该值必须大于或等于 0。 - - OLE DB 提供程序返回意外状态值 {0}。 + + 此元数据的值无效。 - - 如果创建 OleDbDataReader.Read 的线程的 ApartmentState 不是 ApartmentState.MTA,则必须从该线程中使用 OleDbDataReader.Read。 + + 将参数值从 {0} 转换到 {1} 失败。 - - 未指定的错误: {0} + + {0} 不支持并行事务。 - - IErrorInfo.GetDescription 因 {0} 而失败。 + + 此 {0} 已完成;它再也无法使用。 - - IErrorInfo.GetSource 因 {0} 而失败。 + + 指定长度“{0}”超出了范围。 - - .NET Framework OLE DB 数据提供程序(Microsoft.Data.OleDb)不支持 DBTYPE_VECTOR 数据。 + + 在 dataIndex“{0}”处的 {2} 尝试无效。通过 CommandBehavior.SequentialAccess 只能从“{1}”或更大的 dataIndex 处读取。 最小或最大池大小值无效,最小池大小不能大于最大池大小。 - - “{0}”关键字已过时。请使用“{1}”来代替。 - 无法从服务器获取服务器的分布式事务协调程序的地址。服务器上是否已启用 DTC? - - 连接参数 <{0}> 的值的长度超出了最多 65535 个字符的限制。 - “Packet Size”值无效。此值必须是大于等于 512 且小于等于 32768 的整数。 事务中要进行保存的点的事务或名称无效。 - - SQL Server 7.0 或 SQL Server 2000 不支持 {0} 枚举值 {1}。 - User Instance 和 Failover 是不兼容的选项。请在连接字符串中只选择其中一个。 @@ -2499,33 +540,21 @@ 如果在连接字符串中指定了“Authentication=Active Directory Integrated”,则无法设置 Credential 属性。 - - 使用证书身份验证指定的身份验证参数不正确。 - 您试图连接的 SQL Server 实例要求加密,但是此计算机不支持。 您试图连接的 SQL Server 实例不支持加密。 - - 你试图连接的 SQL Server 实例不支持 CTAIP。 - SQL Server 版本不受支持。.NET Framework SqlClient 数据提供程序只能用于 SQL Server 版本 7.0 及更高版本。 在进行异步操作时无法更改 {0}。 - - 此命令要求异步连接。请在连接字符串中设置“Asynchronous Processing=true”。 - 连接尝试超时。 - - 超时时间已到。因此,连接被中断。 - 实例失败。 @@ -2556,27 +585,18 @@ 错误代码 0x{0} - - ChangePassword 要求 SQL Server 9.0 或更高版本。 - 非本地用户实例不支持 SSE 实例重定向。 不支持身份验证“{0}”。 - - 不支持 SQL 身份验证方法“{0}”。 - 未能实例化“{0}”的类型为“{1}”的身份验证提供程序。 未能实例化类型为“{0}”的 SqlAuthenticationInitializer。 - - 初始化时提供程序“{0}”引发了一个异常。 - 提供程序“{0}”不支持身份验证“{1}”。 @@ -2586,24 +606,15 @@ 找不到 ‘{0}’ 的身份验证提供程序。安装 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 包(https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure)以使用 Active Directory (Entra ID)身份验证方法。 - - 未能读取身份验证提供程序的配置节。 - 参数“{0}”不能为 null 或为空。 - - 异步操作已完成。 - 命令执行无法继续,因为正在处理一个挂起的异步操作。 发送到 ExecuteXmlReader 的命令无效。该命令必须返回 Xml 结果。 - - 通知要求 SQL Server 9.0 或更高版本。 - 用于 UdtTypeName 的 3 部分名称格式无效。 @@ -2697,9 +708,6 @@ 无法初始化 SSPI 软件包。 - - Timeout 时间已到。在操作完成之前超时时间已过或服务器未响应。 - 连接超时时间已到。在预登录开始阶段超过了此超时时间。这可能是因为为连接超时提供的时间不够。 @@ -2745,12 +753,6 @@ 在连接字符串中请求了用户实例,但是指定的服务器不支持此选项。 - - 服务器证书无法通过应用程序验证。 - - - 指定的数据长度 {0} 超出了允许的最大长度 {1}。 - 没有数据时尝试进行无效的读操作。 @@ -2775,9 +777,6 @@ 数据长度“{0}”小于 0。 - - SqlDataRecord 为只读时,不能调用“{0}”。 - 映射集合正在使用中,此时无法访问; @@ -2847,21 +846,6 @@ 不支持使用 Microsoft.Data.SqlClient 连接到上下文连接。 - - 不支持嵌套的 TransactionScopes。 - - - 意外的服务器事件: {0}。 - - - “{0}”的长度必须与“{1}”的长度匹配。 - - - SqlDbType“{0}”对于 {1} 无效。只支持 {2}。 - - - 值太多。 - Stream 不支持写入。 @@ -2886,192 +870,36 @@ Null - - 消息 - - - 算术溢出。 - - - 出现被零除错误。 - - - 数据为空。不能对 Null 值调用此方法或属性。 - - - 数值算法导致截断。 - SqlDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间。 - - 要连接的两个字符串具有不同的排序规则。 - - - 要比较的两个字符串具有不同的排序规则。 - - - 无效的标志值。 - - - 从 SqlDecimal 到 Decimal 的转换溢出。 - 转换溢出。 - - 无效的 SqlDateTime。 - - - 已指定了时区。SqlDateTime 不支持时区。 - 无效的数组大小。 数值精度/小数位数无效。 - - 输入采用的格式不正确。 - - - SqlType 错误。 - - - 此版本中的 SqlBytes 和 SqlChars 不支持超过 2GB 的长度。 - 读取时出错。 - - Stream 已关闭或已处理。 - - - 没有缓冲区。读或写操作失败。 - - - 无法设置非零长度,因为当前值为 Null。 - - - 缓冲区不足。读或写操作失败。 - - - 无法写入非零偏移量,因为当前值为 Null。 - - - 无法从超出当前长度的偏移位置执行写操作。这将在缓冲区中留下未初始化的数据。 - 返回的数据大小超出了 2Gb。请使用 SequentialAccess command behavior,以获取所有数据。 - - GetDayOfWeek 的参数必须是介于 1 与 7 之间的整数。 - - - SQL Type 尚未与数据一起加载。 - - - SQL Type 已与数据一起加载。 - - - 访问已关闭的 XmlReader 的尝试无效。 - - - 流关闭时,调用 {0} 的尝试无效。 - - - 流不可写时,调用 {0} 的尝试无效。 - - - 流不可读时,调用 {0} 的尝试无效。 - - - 流不可查找时,调用 {0} 的尝试无效。 - - - 子类未覆盖所需方法。 - 无法为“{0}”创建标准化程序。 内部错误 - - 命令参数必须有一个非 Null 和非空的命令文本。 - - - MetaData 参数数组的长度必须等于 ParameterDirection 数组参数。 - - - 数据适配器映射错误。 - - - 数据适配器错误。 - - - DB 并发性违规。 - 操作已中止。 由于异常,操作已中止(有关详细信息,请参阅 InnerException)。 - - Fill 是否调用 DataRow.AcceptChanges。 - - - Update 将是否调用 DataRow.AcceptChanges。 - - - 当 Update 事件(RowUpdating 和 RowUpdated)的 Status 是 UpdateStatus.ErrorsOccurred 时,是否继续到下一个 DataRow。 - - - 适配器从 DataReader 填充 DataTable 的方式。 - - - 当 TableMappings 中缺少表或列时将采取的措施。 - - - 当 DataSet 中缺少表或列时将采取的措施。 - - - 如何将源表映射到 DataSet 表。 - - - Fill 期间发生可恢复错误时将触发的事件。 - - - Fill 应返回提供程序特定值还是普通 CLSCompliant 值。 - - - DataColumn.ColumnName - - - 源列名称 - 区分大小写。 - - - 集合中项的数目 - - - 指定的 DataColumnMapping 对象。 - - - 匹配此表映射时的各个列映射。 - - - DataTable.TableName - - - DataTableMapping 源表名称。该名称区分大小写。 - - - 集合中项的数目 - - - 指定的 DataTableMapping 对象 - Update 期间用于 DataSet 中的已删除行。 @@ -3090,63 +918,9 @@ Update 期间在每个 DataRow 之后触发的事件。 - - 在数据源上执行之前要一起组成批的行数。 - - - Select/Insert/Update/DeleteCommands 尚未拥有连接时将使用的连接。 - - - Update 期间用于 DataTable 中已删除的行。 - - - Update 期间用于 DataTable 中的新行。 - - - Fill 期间使用。 - - - Update 期间用于 DataTable 中已修改的行。 - - - Fill 应返回提供程序特定值还是普通 CLSCompliant 值。 - - - 如何将源表映射到 DataTable。 - - - 用户未设置时生成 Insert/Update/DeleteCommands 的方式。 - - - 在数据源上执行之前要一起组成批的行数。 - - - 用于连接 Data Source 的连接字符串。 - - - 连接到 Data Source 时要使用的 ODBC 驱动程序的名称。 - - - 连接到 Data Source 时要使用的 DSN。 - - - 如果为 True,则指示应使用托管连接池。 - - - 连接到 Data Source 时使用的 UDL 文件。 - - - 指定使用 OleDb Provider 时启用或禁用的 OLE DB Services。 - - - 连接到 Data Source 时要使用的 OLE DB 提供程序名。 - 应用程序的名称。 - - 如果为 True,则启用 .NET Framework 数据提供程序中的异步功能。 - 主文件名称,包括可附加数据库的完整路径名。 @@ -3156,9 +930,6 @@ 如果为 True,则指示从池中删除后,连接状态为重置。 - - 如果为 true,指示连接应来自 Sql Server 上下文。只有在 Sql Server 进程中运行时才可用。 - SQL Server 语言记录名。 @@ -3252,54 +1023,9 @@ 指定对 SQL Server 进行身份验证的方法。 - - 指定的客户端证书,用于通过 SQL Server 进行身份验证。 - - - 用于连接到数据源的信息。 - - - 当前连接超时值,不可在 ConnectionString 中设置。 - - - 当前数据源目录值,连接字符串中的“Database=X”。 - - - 当前数据源,连接字符串中的“Server=X”。 - - - 当前 ODBC 驱动程序。 - - - ODBC 驱动程序访问的产品的版本。 - - - 用于连接到数据源的信息。 - - - 当前连接超时值,ConnectionString 中的“Connect Timeout=X”。 - - - 当前数据源目录值,连接字符串中的“Initial Catalog=X”。 - - - 当前数据源,连接字符串中的“Data Source=X”。 - - - 连接字符串中,当前 OLE DB 提供程序的 ProgID “Provider=X”。 - - - OLE DB 提供程序访问的产品的版本。 - 要用于身份验证的访问令牌。 - - 连接状态、同步或异步。连接字符串中“Asynchronous Processing=x”。 - - - 用于连接进行复制的信息。 - 连接到 DataSource 时所用的信息,例如“Data Source=x;Initial Catalog=x;Integrated Security=SSPI”。 @@ -3324,9 +1050,6 @@ 收集此连接的统计信息。 - - 自定义列加密密钥存储提供程序。 - 一个用于表示物理连接的 GUID。 @@ -3363,99 +1086,9 @@ 自动登记在 Microsoft SQL Server 使用的通知中。 - - 在用户未指定时,如何为 Update 和 Delete 命令自动生成 where 子句。 - - - 指示文本命令中目录名在合法表名中的位置。 - - - 文本命令中分隔目录名与其余标识符的字符串。 - - - 文本命令中分隔架构名与其余标识符的字符串。 - - - 环绕 sql 对象的前缀字符串。 - - - 环绕 sql 对象的后缀字符串。 - - - 自动为其生成命令的 DataAdapter。 - - - 使用来自 DataTable 或 SelectCommand 的架构。 - - - 用户未指定的情况下,如何自动生成 Update 命令的 Set 子语。 - - - 自动为其生成 OdbcCommands 的 DataAdapter - - - 在文本命令中用作左引号的字符,用于将包含特殊字符的标识符括起来。 - - - 在文本命令中用作右引号的字符,用于将包含特殊字符的标识符括起来。 - - - 要为其自动生成 OleDbCommands 的 DataAdapter - - - 用于十进制文本的十进制分隔符。 - - - 环绕 sql 对象的前缀字符串 - - - 环绕 sql 对象的后缀字符串 - 自动为其生成 SqlCommands 的 DataAdapter - - 用于十进制文本的十进制分隔符。 - - - 在文本命令中用作左引号的字符,用于将包含特殊字符的标识符括起来。 - - - 在文本命令中用作右引号的字符,用于将包含特殊字符的标识符括起来。 - - - 只有在与 Prepare、FillSchema 和 CommandBuilder 方案一起使用时,才需要设置小数和数值参数。 - - - 只有在与 Prepare、FillSchema 和 CommandBuilder 方案一起使用时,才需要设置小数和数值参数。 - - - 参数本机类型。 - - - 参数本机类型。 - - - 参数的名称,如“@p1” - - - 参数本机类型。 - - - 类型的服务器名称。 - - - 可变长度数据类型中的偏移量。 - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - 不支持为参数“{1}”指定的 ParameterDirection“{0}”。Table-valued parameters 只支持 ParameterDirection.Input。 @@ -3468,9 +1101,6 @@ 为参数“{0}”指定了 TypeName。只能为 Structured 参数设置 TypeName。 - - 仅对多值类型支持 SqlDbType.Structured 类型。 - 架构表中的字段“{0}”的 DateType 列为 null。DataType 必须为非 null。 @@ -3483,18 +1113,9 @@ 记录“{0}”中的字段数与原始记录中字段数不符。 - - 范围: 0-8000 - SqlClient 数据提供程序遇到意外错误。{0} - - 无法获取 {0}、{1} 的 Type Info - - - 提供程序加载以下程序集失败: {0} - 必须为 UDT 参数设置 UdtTypeName 属性。 @@ -3504,72 +1125,9 @@ 指定类型未在目标服务器上注册。{0}。 - - 除主键的一部分外,Where 子句中不允许 UDT 参数。 - - - “{0}”是无效的用户定义类型,原因: {1}。 - - - 同时支持内存中格式和用户定义的格式 - - - 不支持本地格式。 - - - 未实现 IBinarySerialize - - - 不可序列化 - - - 没有公共构造函数 - - - 没有实现 INullable - - - 没有公共构造函数 - 没有 UDT 特性 - - 尚不支持没有映射的序列化。 - - - 缺少“public static x Parse(System.Data.SqlTypes.SqlString)”方法 - - - 缺少“public override string ToString()”方法 - - - 缺少“public static x Null { get; }”方法 - - - Native 格式不支持“{0}”类型的字段(直接或通过另一字段) - - - 不是公共类型 - - - 因为类型为“{0}”,所以 Native UDT 不是序列布局 - - - Native UDT 指定最大 byte 大小 - - - 字段“{0}”被标记为非序列化 - - - 字段 ‘{0}’ 的类型标记为显式布局,这在本机格式中是不允许的 - - - 有多个值的程序集引用必须有一个非零程序集 ID。 - - - 可以对参数使用结构化的、多值的类型,且不能将这些类型嵌套在其他类型中。 - 路径名无效。 @@ -3585,9 +1143,6 @@ 试图提升事务时失败。 - - 用于接收查询通知的相关性对象。 - 用于指示此相关性是否无效的属性。 @@ -3600,9 +1155,6 @@ 向现有的相关性对象中添加命令。 - - Command 已与另一个相关性对象建立关联。不能覆盖。 - 此 SqlCommand 对象已与另一 SqlDependency 对象关联。 @@ -3630,12 +1182,6 @@ 指定的 Timeout 无效。Timeout 值不能小于 0。 - - 通知错误。Type={0},Info={1},Source={2}。 - - - GetMetaData 对 SqlDbType 无效。 - dbType {0} 对于此构造函数无效。 @@ -4104,9 +1650,6 @@ 内部错误。纯文本值不能为 null。 - - 无法加密。加密生成了 {0} 字节的已加密文本,该大小超出允许的最大限制({1} 字节)。指定纯文本值可能太大(纯文本大小是: {2} 字节)。 - 内部错误。已加密文本值不能为 null。 @@ -4224,9 +1767,6 @@ SQL Server 未返回证明信息。Enclave 类型为“{0}”,enclave 证明 URL 为“{1}”。 - - 无法建立安全通道。错误消息: {0} - 出现内部错误。构造类型“{1}”的对象时指定了 null 参数“{0}”。“{0}”不能为 null。 @@ -4242,15 +1782,6 @@ 指定 enclave 证明服务的终结点,以用于验证在 SQL Server 实例中配置的用于在使用“始终加密”加密的数据库列上执行计算的 enclave 是否安全有效。 - - 未能读取 enclave 提供程序的配置节。请确保该节在应用程序配置文件中的格式正确。错误消息: {0} - - - 未能实例化类型为“{1}”,名称“{0}”为的 enclave 提供程序。错误消息: {2} - - - 出现内部错误。SqlColumnEncryptionEnclaveProviderName 不能为 null 或为空。 - 读取“{0}”结果集时出错。尚未在连接字符串中指定证明 URL,但查询需要 enclave 计算。enclave 类型为“{1}”。 @@ -4260,9 +1791,6 @@ 出现内部错误。执行需要 enclave 计算的查询时,接收自 SQL Server 的 enclave 类型为 null 或为空。 - - 需要 enclave 计算才可执行查询,但应用程序配置缺少 enclave 提供程序节。 - 未找到 enclave 类型“{0}”和证明协议“{1}”的 enclave 提供程序。请在连接字符串中指定正确的证明协议。 @@ -4320,9 +1848,6 @@ 用于连接上的所有命令的默认列加密设置。 - - 在向服务器发送敏感数据前强制对参数进行加密。 - 包含 SQL Server 名称的字典对象及其信任的列的主密钥路径。 @@ -4332,9 +1857,6 @@ 不支持使用 ApplicationIntent ReadOnly 连接选项连接到镜像的 SQL Server 实例。 - - 收到了意外的路由信息。 - 收到了无效的路由信息。 @@ -4485,9 +2007,6 @@ 在此上下文中指定的身份验证不受支持: {0} - - Active Directory 交互式身份验证超时。用户响应身份验证请求的时间过长。 - 如果设置了 Credential 属性,则无法使用 "Authentication=Active Directory Interactive"。 @@ -4497,9 +2016,6 @@ 活动连接的服务器进程 ID (SPID)。 - - Active Directory 设备代码流身份验证超时。用户响应身份验证请求的时间过长。 - 如果在连接字符串中指定了 "Authentication=Active Directory Device Code Flow",则无法设置 Credential 属性。 @@ -4533,9 +2049,6 @@ 所请求的操作仅支持一维数组。 - - 未在指定的集合中找到指定的项,因此无法移除该项。 - 需要提供非负数。 @@ -4563,87 +2076,36 @@ 如果在连接字符串中指定了 "Authentication=Active Directory Default",则无法设置 AccessTokenCallback 属性。 - - {0} 返回了 {1}。 - - - 协议错误: 接收的消息中包含有效的签名,但是没有按照有效的保护级别的要求对消息进行加密。 - - - {0} 失败,出现错误 {1}。 - 此类未实现此方法。 - - 不支持请求的安全包。 - physicalConnection 类型不正确。 尝试设置 AppContext 交换机“{0}”={1} 时发生异常。 - - 此平台不支持类型 {0}。 - 不支持全球化固定模式。 - - 无法使用 Kerberos 进行身份验证。确保已在客户端上使用“kinit”初始化 Kerberos,并且已为 SQL Server 注册了服务主体名称以允许 Kerberos 身份验证。 - 此平台不支持关键字“网络库”,而是将“数据源”作为所需协议的前缀(对于 TCP 连接为“tcp:”或对于命名管道连接为“np:”)。 套接字未引发预期的“{0}”,错误代码为“{1}”。 - - 服务器正在尝试使用此平台不支持的功能。 - 此平台不支持关键字“{0}”。 当前加载的 System.Transactions.dll 不支持全局事务。 - - 从服务器读取数据时收到不受支持的令牌“{0}”。 - - - “{0}”是不受支持的句柄类型。 - 此平台不支持 SqlFileStream。 此平台不支持 LocalDB。 - - 缓冲区空间不足。必需: {0} 实际: {1}。 - - - GSSAPI 操作失败,状态为: {0} (次要状态: {1})。 - - - GSSAPI 操作失败,出现错误 - {0} ({1})。 - - - 不支持在除 Windows 以外的操作系统上进行通道绑定。 - - - 如果传递了默认凭据,则目标名称应为非空。 - - - 当前安装的 GSSAPI 实现不支持请求的保护级别。 - - - 不支持服务器实现。 - - - 无法在此平台上使用默认凭据进行 NTLM 身份验证。 - 在 DataAdapter.Update 使用时,参数值由 DBNull.Value 更改为 (Int32)1 或 (Int32)0 (如果非空)。 @@ -4668,9 +2130,6 @@ 超出缓冲区最大大小: {0} - - 缺少 SourceColumn“{2}”的 DataColumn“{0}”。 - SqlBatchCommand 列表尚未初始化。 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 47d06afa4b..2081722fb7 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 遞增 - 此具有 Count={2} 的 {1} 有無效的索引 {0}。 @@ -132,18 +129,12 @@ 另一個 {1} 已經包含 {0}。 - - 此 {1} 已經包含 {0}。 - {0} 只接受非 Null 的 {1} 型別物件。 此 {1} 嘗試移除不包含的 {0}。 - - {0}.{1} 必須是唯一的,'{2}' 以存在於集合中。 - 連接未關閉。{0} @@ -171,12 +162,6 @@ 當讀取器關閉時嘗試呼叫 {0} 無效。 - - 無法編列至交易,因為連接已優先用於委派或提示交易。 - - - 遞減 - '{0}' 參數必須是非空字串。 @@ -204,9 +189,6 @@ 尚未初始化 ConnectionString 屬性。 - - 已找到 Non CLS Exception。 - 指定的安全性元素不是權限元素。 @@ -252,9 +234,6 @@ 資料 - - StateChange - 更新 @@ -264,48 +243,12 @@ ConnectionState 表示連接為開啟或關閉。 - - 當連接狀態變更時觸發的事件。 - - - 參數的泛用型別。 - - - 輸入、輸出或雙向參數。 - - - 對強型別的程式碼產生使用了設計階段的屬性。 - - - 可變長度資料型別中的位移。 - - - 參數的名稱。 - - - 可變長度資料類型 (字串與陣列) 的大小。(& ) - - - 由 DataAdapter.Update 使用時,用以在 ColumnMappings 中尋找 DataSetColumn 名稱的來源資料行名稱。此值由參數與資料列之間複製而來。 - - - 由 DataAdapter.Update (只有 UpdateCommand) 使用時,用以更新資料來源的 DataRow 值版本。 - - - 由 DataAdapter.Update 使用時,參數值會由 DBNull.Value 變更為 (Int32)1 或 (Int32)0 (若為非 Null)。 - - - 參數值。 - 無法建置 '{0}' 集合,因為 SQL 查詢執行失敗。請參閱內部例外狀況以取得詳細資訊。 提供的限制超過必要的結構描述 ('{0}') 所能支援的。 - - '{2}' 不是 '{0}' 結構描述集合中 '{1}' 限制的有效值。 - 未定義必要的集合 ({0})。 @@ -315,15 +258,6 @@ 提供者的版本不支援要求的集合 ({0})。 - - 其中一個 DataSourceInformation tables 資料行遺失。 - - - DataSourceInformation 資料表必須至少包含一個資料列。 - - - 一個或多個限制集合的資料庫遺失。 - 有一項限制為限制集合中沒有相符的資料列。 @@ -342,42 +276,21 @@ 集合 '{0}' 已從中繼資料 XML 中遺失。 - - 中繼資料 XML 無效。 - - - 中繼資料 XML 無效。{0} 集合必須包含 {1} 資料行,且必須包含字串資料行。 - 中繼資料 XML 無效。{0} 集合的 {1} 資料行必須包含非空的字串。 - - 動作 - - - 行為 - 填滿 InfoMessage - - 對應 - StatementCompleted - - UDT - 通知 - - 結構描述 - XML @@ -393,9 +306,6 @@ 共用 - - 具名 ConnectionString - 安全性 @@ -408,2073 +318,204 @@ 連線復原 - - 保存自訂使用者資訊的集合。 - - - 指定在資料集 (DataSet) 中比較字串時是否要區分大小寫。 - - - 這個 DataSet 的名稱。 - - - 表示 DataSet 中包含資料的自訂檢視表。這個檢視表允許透過自訂資料檢視表執行篩選、搜尋和瀏覽。 - - - 指定是否要遵循條件約束規則。 - - - 表示 DataSet 有錯誤。 - - - 表示在 DataSet 中比較字串時是否要使用地區設定。 - - - 表示這個 DataSet 所指向根元素的 XML URI 命名空間。 - - - 表示這個 DataSet 所使用命名空間的前置詞。 - - - 保存這個 DataSet 關聯的集合。 - - - 保存這個 DataSet 資料表的集合。 - - - 發生於不可能合併擁有相同名稱的兩個資料表的結構描述時。 - - - 在完成 Initialization 之後發生。 - - - 代表資料的記憶體中快取。 - - - 表示在資料表中比較字串時是否區分大小寫。 - - - 傳回這個資料表的子關聯。 - - - 保存這個資料表中資料行的集合。 - - - 保存這個資料表中條件約束的集合。 - - - 表示這個 DataSet 所屬的資料集。 - - - 這是資料表的預設 DataView。 - - - 用來計算這個資料列資料繫結值的運算式。 - - - 傳回資料表是否有錯誤。 - - - 指定在資料表中比較字串時的使用地區設定。 - - - 表示這個資料表的初始開始大小。 - - - 表示這個資料表中所包含元素的 XML URI 命名空間。 - - - 代表在 XML 表示中這個資料表所使用命名空間的前置詞。 - - - 傳回這個資料表的父關聯。 - - - 表示提供這個資料表主索引鍵的資料行。 - - - 表示儲存這個資料表內資料的資料列的集合。 - - - 表示用來在 DataSet 的 Tables 集合中查詢這個資料表的名稱。 - - - 發生在資料表中的資料列被成功編輯之後。 - - - 發生於資料列被變更時,如此事件處理常式可以修改或取消變更。使用者可以修改資料列中的值,並且應該傳回例外狀況以取消編輯。 - - - 發生在資料表中的資料列被成功刪除之後。 - - - 當資料表中的資料列已被標示為刪除時發生。擲出取消刪除的一個例外狀況。 - - - 當值已經送給這個資料行時發生。使用者可以修改建議值,並且應該擲回例外狀況以取消編輯。 - - - 當這個資料行的值已經變更時發生。 - - - 當從資料表中清除所有資料列前會發生。 - - - 當成功清除資料表中的所有資料列後會發生。 - - - 當新的 DataRow 具現化後會發生。 - - - 用來在 DataSet 的 Relations 集合中查詢這個關聯的名稱。 - - - 表示這個關聯的子資料行。 - - - 表示這個關聯的父資料行。 - - - 表示關聯是否為巢狀。 - - - 針對刪除,表示何種串聯必須跨這個關聯發生。 - - - 針對更新,表示何種串聯必須跨這個關聯發生。 - - - 針對接受或拒絕變更,表示何種串聯必須跨這個關聯發生。 - - - 表示這個條件約束的子資料行。 - - - 表示這個條件約束的父資料行。 - - - 為此條件約束的子資料表。 - - - 表示這個條件約束的資料行。 - - - 表示此條件約束是否為主索引鍵。 - - - 表示此條件約束的名稱。 - - - 表示此條件約束的表格。 - - - 指示這個資料行中是否允許 null 值。 - - - 表示針對要加入資料表的資料列,資料行是否自動累加本身。這個資料行的型別必須為 Int16、Int32 或 Int64。 - - - 表示 AutoIncrement 資料行的起始值。 - - - 表示 AutoIncrement 資料行所使用的累加方式。 - - - 表示這個資料行的預設使用者介面標題。 - - - 表示在 DataTable 的 Columns 集合中用來查詢這個資料行的名稱。 - - - 傳回至這個資料行所屬的 DataTable。 - - - 表示儲存在這個資料行中的資料類型。 - - - 表示當加入新資料列至資料表時所使用的預設資料行值。 - - - 表示這個資料行會根據其他的資料行來計算其每一個資料列的值,而不使用使用者的輸入。 - - - 表示這個資料行保存在 XML 中的方法: 當作屬性、元素、簡單內容節點,或無。 - - - 表示這個資料行中所儲存元素或屬性的 XML URI。 - - - 表示這個 DataColumn 在 xml 表示法中所使用的 Prefix。 - - - 表示在 Columns 集合中這個資料行的索引。 - - - 表示一旦將資料列加入資料表後,這個資料行是否允許變更。 - - - 表示這個資料行是否應在資料表的資料行中限制其值必須為唯一。 - - - 表示這個資料行允許長度的最大值。 - - - 表示此 DataColumn 的 DateTimeMode。 - - - 表示這個 DataView 和它關聯的使用者介面是否允許刪除。 - - - 表示這個 DataView 和它關聯的使用者介面是否允許編輯。 - - - 表示這個 DataView 和它關聯的使用者介面是否允許加入新資料列。 - - - 傳回目前在這個檢視表中的項目數目。 - - - 這會傳回一個指標到擁有這個 DataSet (如果有的話) 的 DataViewManager。 - - - 表示檢視表是否為開啟。 - - - 表示用來篩選這個 DataView 傳回資料的運算式。 - - - 表示由這個 DataView 傳回的資料版本。 - - - 表示由這個 DataView 傳回資料的資料行名稱與排列順序。 - - - 指定若未設定 Sort 屬性,是否使用預設的排序。 - - - 表示這個 DataView 用來取得資料的資料表。 - - - 表示由這個 DataView 傳回的資料已經變更。 - - - 表示這個 DataViewManager 的資料來源。 - - - 表示在對應 DataSet 中任何資料表的排序/篩選/狀態設定。 - - - DataSet 並不支援 'union' 或 'list' 當作 simpleType 來使用。 - - - 無效的 {0} 語法: 遺漏必要的 '{1}' 屬性。 - - - 屬性 '{0}' 的值 '{1}' 無效。 - - - 屬性 '{0}' 的值必須是 '{1}' 或 '{2}'。 - - - 找不到 ElementType name='{0}'。 - - - 關聯 '{0}' 中遺漏父資料表名稱。 - - - 關聯 '{0}' 中遺漏子資料表名稱。 - - - 關聯 '{0}' 中遺漏父資料表索引鍵。 - - - 關聯 '{0}' 中遺漏子資料表索引鍵。 - - - 未定義的資料型別: '{0}'。 - - - 未定義的資料型別。 - - - 欄位節點內 XPath 選取無效。找不到: {0}。 - - - 選取器節點內 XPath 選取無效: {0}。 - - - 名稱為 {0} 條件約束內部的無效 'Key' 節點。 - - - 結構描述中已經使用條件約束名稱 {0}。 - - - 無法將 '{0} 轉換成型別 '{1}'。 - - - 在名稱為 '{2}' 的 '{1}' 條件約束中遺漏 '{0}' 部分。 - - - 無效的 Relation 定義: 不同的長度索引鍵。 - - - DataSet 不允許名為 '{0}' 的 ComplexType 中有循環參考。 - - - DataSet 無法具現化抽象的節點 {0} 的 ComplexType。 - - - 寫入 Xml Schema 時,多目標轉換器發生錯誤。詳細資訊請參閱內部例外狀況。 - - - 寫入 Xml Schema 時,多目標轉換器發生錯誤。傳回的字串是 null 或空的。 - - - 重複的宣告 '{0}'。 - - - 無法載入 diffGram。目的資料集中遺漏資料表 '{0}'。 - - - 無法載入 diffGram。遺漏 'sql' 節點。 - - - 資料行名稱 '{0}' 已經定義為不同的對應型別。 - - - 前置字元 '{0}' 無效,因為其中含有特殊字元。 - - - 自我巢狀資料表 '{0}' 中的循環參考。 - - - DataSet 無法展開實體。請使用 XmlValidatingReader 並適當地設定 EntityHandling 屬性。 - - - 型別 '{0}' 未實作 IXmlSerializable 介面,因此無法繼續序列化。 - - - 無法繼續還原序列化。資料未實作 IXMLSerializable,因此不支援多型。 - - - DataTable 不支援來自 Xml 的結構描述介面。 - - - 無法繼續序列化 DataTable '{0}'。它包含在相同的 Foreign Key 上有多個父資料列的 DataRow。 - - - 輸入的 Schema 中遺漏了 IsDataSet 屬性。 - - - 無法判斷 DataSet 元素。IsDataSet 屬性存在一次以上。 - - - DataSet 不會序列化實作 IDynamicMetaObjectProvider 但未同時實作 IXmlSerializable 的型別。 - - - 功能未實作。{0}。 - - - 語法錯誤: '{0}' 運算子之後遺漏運算元。 - - - 運算式 '{0}' 中類型不符。 - - - 運算式太複雜。 - - - 找不到資料行 [{0}]。 - - - 運算式包含無效的字串常數: {0}。 - - - 運算式包含未定義的函式呼叫 {0}()。 - - - 運算式中語法錯誤。 - - - 無效的引數數目: 函式 {0}()。 - - - 運算式遺漏右括號。 - - - 無法於位置 {1} 轉譯語彙基元 '{0}'。 - - - 必須是 {0},但於位置 {2} 的實際語彙基元是 {1}。 - - - 無法從 '{0} 轉換為 '{1}'。 - - - 無法將 '{0}' 值轉換為類型 '{1}'。 - - - 無效的資料行名稱 [{0}]。 - - - 運算式包含無效的日期常數 '{0}'。 - - - 在 IN 運算子的運算式清單中只允許常數運算式。 - - - Like 運算子中發生錯誤: 字串模式 '{0}' 無效。 - - - 語法錯誤: IN 關鍵字之後的項目必須以逗號分隔,而且必須位於括號中。 - - - 函式引數中類型不符: {0}(),引數 {1},必須是 {2}。 - - - 函式引數中類型不符: {0}(),引數 {1},必須是 Integer 型別其中之一。 - - - 無法在 {1} 和 {2} 上執行 '{0}' 作業。 - - - 運算子 '{0}' 在型別 '{1}' 和 '{2}' 的運算元上模稜兩可。無法混合 signed 和 unsigned 型別。請使用明確的 Convert() 函式。 - - - 語法錯誤: IN 關鍵字之後必須跟隨運算式以逗號分隔的非空白清單,而且必須包含在括號中。 - - - 運算式包含未支援的運算子 '{0}'。 - - - 運算式包含無效的名稱: '{0}'。 - - - 語法錯誤: '{0}' 運算子之前遺漏運算元。 - - - 運算式有太多右括號。 - - - 資料表 [{0}] 已經包含在多個關聯中。您必須在運算式 '{1}' 明確指定關聯名稱。 - - - 彙總引數中語法錯誤: 必須是擁有可能 'Child' 限定詞的單一資料行引數。 - - - 彙總運算式 '{0}' 中未繫結的參考。 - - - 無法評估沒有目前資料列的非常數運算式。 - - - 運算式 '{0}' 中未繫結的參考。 - - - 無法評估。運算式 '{0}' 不是彙總。 - - - 篩選條件運算式 '{0}' 不會評估成 Boolean 詞彙。 - - - 無效的型別名稱 '{0}'。 - - - 查詢運算式中語法錯誤: 必須是關鍵字 'Parent' 之後跟隨擁有可能關聯限定詞的單一資料行引數: Parent[(<relation_name>)].<column_name>。 - - - 需要資料列或資料表以叫用 DataFilter。 - - - {0}() 引數超過範圍。 - - - 語法錯誤: 'Is' 運算子的用法無效。正確語法: <expression> Is [Not] Null。 + + 位移和長度超過陣列的界限或計數大於從索引至來源集合尾端的項目數。 - - 型別 '{0}' 的值太大或太小。 + + 無法將類型 '{0}' 的物件轉換為類型 '{1}' 的物件。 - - 發生除以零的錯誤。 + + 屬性 '{0}' 僅可接受的值是 '{1}'。 - - 找不到父關聯 '{0}'。 + + 屬性 '{0}' 可接受的值為 '{1}' 或 '{2}'。 - - 'hours' 引數超出範圍。值必須介於 -14 到 +14 之間。 + + 指定的 QuotePrefix 與 QuoteSuffix 值不符。 - - 'minutes' 引數超出範圍。值必須介於 -59 到 +59 之間。 + + 引數 '{0}' 的長度超過其上限 '{1}'。 - - 對時間一提供的範圍超過 14 小時的總數。 + + 索引鍵 '{0}' 的值無效。 - - 提供的 DateTime 引數的 Kind 屬性,不符合 'hours' 和 'minutes' 引數。 + + 必須要顯示索引鍵 '{1}' 才能使用索引鍵 '{0}'。 - - 型別 '{0}' 的 DataColumn 不支援運算式。 + + 索引鍵 '{0}' 的值長度超過其上限 '{1}'。 - - 無法啟用條件約束。一或多個資料列的值違反非 Null、唯一或外部索引鍵條件約束。 + + 不支援關鍵字: '{0}'。 - - 集合本身無法修改。 + + 內部 .NET Framework Data Provider 錯誤 {0}。 - - 指定名稱 '{0}' 至少與集合物件中的兩個物件名稱相符,但大小寫不完全相同。 + + {0} "{1}"。 - - 指定的名稱 '{0}' 與集合物件中至少兩個不同命名空間的名稱相符。 + + {0} "{1}",引號的用法不正確。 - - 位移和長度超過陣列的界限或計數大於從索引至來源集合尾端的項目數。 + + {0} "{1}","{2}" 目前的限制不足。 - - '{0}' 引數超過範圍。 + + SqlBulkCopy.WriteToServer 已經失敗,因為 SqlBulkCopy.DestinationTableName 是無效的多部名稱 - - '{0}' 引數不可是 Null。 + + 由於某個無效的多部名稱,故無法處理來自 SQL Server 的結果 - - '{0}' 引數包含空值。 + + SqlParameter.UdtTypeName 是無效的多部名稱 - - 找不到資料行 {0}。 + + SqlParameter.TypeName 是無效的多部名稱 - - 資料行 '{0}' 已經屬於這個 DataTable。 + + SqlCommand.DeriveParameters 已失敗,因為 SqlCommand.CommandText 屬性值為無效的多部名稱。 - - 資料行 '{0}' 已經屬於其他 DataTable。 + + 不支援使用 MultiSubnetFailover 連接選項連接到鏡像的 SQL Server 執行個體。 - - 在 DataTable 中不可以有一個以上的 SimpleContent 資料行。 + + {2} 方法不支援 {0} 的列舉值 {1}。 - - 無法將 SimpleContent 資料行加入至含有元素資料行或巢狀關聯的資料表。 + + .NET Framework SqlClient Data Provider 不支援 {0} 列舉值 {1}。 - - 這個 DataTable 已經有名稱為 '{0}' 的資料行。 + + {0}: CommandText 屬性尚未初始化 - - 無法加入名為 '{0}' 的資料行: 這個 DataTable 已經有同名的巢狀資料表。 + + {0}: Connection 屬性尚未初始化。 - - 這個 DataTable 已經有名為 '{0}' 的資料行: 無法將巢狀資料表名稱設定為相同的名稱。 + + {0} 必須有開啟與可用的 Connection。{1} - - 無法移除不屬於這個資料表的資料行。 + + 預存程序 '{0}' 不存在。 - - 無法移除這個資料行,因為它是主索引鍵的一部分。 + + 交易可能與目前連接無關,或是已完成。 - - 無法移除這個資料行,因為它是關聯性 {0} 父索引鍵的一部分。 + + 與目前連接關聯的交易已完成,但尚未處置。必須先處置交易,才能使用連接來執行 SQL 陳述式。 - - 無法移除這個資料行,因為它是資料表 {1} 條件約束 {0} 中的一部分。 + + 當指定給命令的連接為擱置的本機交易時,{0} 需要連接以交易。命令的 Transaction 屬性尚未初始化。 - - 無法移除這個資料行,因為它是運算式的一部分: {0} = {1}。 + + 已經開啟與這個 {0} 建立關聯的 DataReader,必須先將它關閉。 - - 無法設定計算資料行的 AutoIncrement 屬性。 + + {0} DeriveParameters 只支援 CommandType.StoredProcedure,不支援 CommandType.{1}。 - - 無法設定擁有 DefaultValue 值的資料行的 AutoIncrement 屬性。 - - - 無法在 AutoIncrement 資料行上設定 DefaultValue。 - - - AutoIncrementStep 必須是不為零的值。 - - - 當 ColumnName 是 DataTable 的一部分時,它是必要的。 - - - 當資料行擁有資料後就無法變更 DataType。 - - - 資料行需要有效的 DataType。 - - - 資料行 {0} 的 DefaultValue 是屬於型別 {1},並且無法轉換成 {2}。 - - - 資料行的 DefaultValue 是屬於型別 {0},並且無法轉換成 {1}。 - - - 資料行 {0} 的 DefaultValue 是屬於型別 {1},但資料行是屬於型別 {2}。 - - - 無法變更運算式資料行的 ReadOnly 屬性。 - - - 無法變更運算式資料行的 Unique 屬性。 - - - 無法在擁有 AutoIncrement 或 Unique 的資料行上建立運算式。 - - - 無法設定運算式,因為無法將資料行設定成 ReadOnly。 - - - 無法在資料行 {0} 上設定 Expression 屬性,因為它是條件約束的一部分。 - - - 無法根據 Expression 資料行 {0} 建立條件約束。 - - - 無法設定 Expression 屬性,因為在運算式中有循環參考。 - - - 資料行 '{0}' 中擁有 null 值。 - - - 資料行 '{0}' 不允許 nulls。 - - - 資料行 '{0}' 是唯讀。 - - - Column '{0}' 包含非唯一的值。 - - - 資料行 '{0}' 不屬於資料表 {1}。 - - - 資料行必須屬於資料表。 - - - 無法在 {1} 資料行中儲存 <{0}>。需要的型別是 {2}。 - - - 無法將 Column '{0}' 設定為 null,請改用 DBNull。 - - - 無法設定資料行 '{0}'。該值違反了這個資料行的 MaxLength 限制。 - - - MaxLength 只適用於字串資料型別。您無法將 Column '{0}' 的 MaxLength 屬性設定成非負數。 - - - 無法將 Column '{0}' 的 MaxLength 屬性設定成 '{1}'。資料表中至少有一個字串的長度超過新的限制。 - - - 無法設定 Column '{0}' 的 MaxLength 屬性。Column 是 SimpleContent。 - - - 無法將 Column '{0}' 的 DataType 屬性設定成 {1}。Column 是 SimpleContent。 - - - 無法將 Column '{0}' 的 MappingType 屬性設定成 SimpleContent。Column 的 DataType 是 {1}。 - - - Column '{0}' 超過 MaxLength 的限制。 - - - 資料行 '{0}' 不允許 DBNull.Value。 - - - 無法變更 Column '{0}' 的 Namespace 屬性。Column 是 SimpleContent。 - - - 一旦 DataColumn 的 AutoIncrement 包含資料,就無法變更為型別 '{0}'。 - - - 資料行 '{0}' 不屬於下列的資料表 '{1}'。 - - - 資料型別 '{0}' 的 DataColumn 是一個 complexType。無法將複雜型別的值序列化為 Attribute。 - - - 僅可在型別 DateTime 的 DataColumns 中設定 DateTimeMode。 - - - '{0}' 不是有效的 DataSetDateTime 值。 - - - 一旦 DateTimeMode 表格中有資料時,就無法將 '{0}' 變更為 '{1}'。 - - - Type '{0}' 不包含 static Null 屬性或欄位。 - - - 型別 '{0}' 不實作 IRevertibleChangeTracking;因此無法進行 RejectChanges()。 - - - 僅可使用未變更 DataRowState 的 DataRows 呼叫 SetAdded 與 SetModified。 - - - 序數 '{0}' 超過數目上限。 - - - DataSet 不支援 System.Nullable<>。 - - - 當條件約束在 ConstraintCollection 中時,無法將它的名稱變更成空白字串。 - - - 無法在條件約束 {0} 上強制使用條件約束。 - - - 資料行 '{0}' 被限制為唯一。值 '{1}' 已經存在。 - - - 條件約束 '{0}' 不屬於這個 DataTable。 - - - 找不到條件約束 {0}。 - - - 條件約束符合集合中名為 {0} 的條件約束。 - - - 名稱為 '{0}' 的 Constraint 已經屬於這個 DataTable。 - - - 這些資料行目前沒有唯一的值。 - - - 這些資料行沒有指向這個資料表。 - - - 無法啟用這個條件約束,因為不是所有值都有相對應的父值。 - - - 無法加入這個條件約束,因為 ForeignKey 不屬於資料表 {0}。 - - - 無法移除不屬於這個資料表的條件約束。 - - - 無法移除唯一的條件約束 '{0}'。請先移除外部索引鍵條件約束 '{1}'。 - - - 無法刪除這個資料列,因為在關聯 {0} 上強制使用條件約束,而且刪除這個資料列將影響子資料列。 - - - 無法進行這項變更,因為在關聯 {0} 上強制使用條件約束,而且變更這個值將影響子資料列。 - - - 無法清除資料表 {0},因為 ForeignKeyConstraint {1} 強制使用條件約束,而且在 {2} 中有子資料列。 - - - ForeignKeyConstraint {0} 需要子索引鍵值 ({1}) 存在於父資料表。 - - - 無法存取屬性,因為 '{0}'。 - - - 無法移除這個資料列,因為它擁有子資料列,而且強制使用關聯 {0} 上的條件約束。 - - - 由於已設定該資料表的主索引鍵,所以無法再加入主索引鍵條件約束。 - - - 無法對 DataTable '{0}' 新增約束,它是巢狀關聯的子資料表。 - - - 無法從屬於不同資料表的資料行建立索引鍵。 - - - 無法擁有 0 個資料行。 - - - 無法擁有超過 {0} 個資料行。 - - - 當相同資料行列出多次時,無法建立索引鍵: '{0}' - - - 無法移除唯一的條件約束,因為它是資料表的主索引鍵。 - - - 無法移除唯一的條件約束,因為它是資料表 {0} 的主索引鍵。 - - - 父資料行和子資料行沒有型別相符的資料行。 - - - ParentKey 和 ChildKey 相同。 - - - ParentColumns 和 ChildColumns 的長度必須相同。 - - - ParentColumns 和 ChildColumns 的長度不可為零。 - - - 資料列和這個關聯不屬於相同的 DataSet。 - - - 當 RelationName 是 DataSet 的一部分時,它是必要的。 - - - GetChildRows 需要 Table 為 {0} 的資料列,但指定資料列的 Table 為 {1}。 - - - 這個關聯必須連接這個 DataSet 中的兩個資料表以加入至這個 DataSet。 - - - GetParentRow 需要資料表為 {0} 的資料列,但指定資料列的資料表為 {1}。 - - - SetParentRow 需要資料表為 {0} 的子資料列,但指定資料列的資料表為 {1}。 - - - 不同 DataSet 的資料表之間不可以有關聯性。 - - - 無法在不同 DataSet 的資料表之間建立關聯。 - - - 已經存在這些子資料行的關聯。 - - - 這個關聯不屬於這個關聯集合。 - - - 這個關聯已經屬於其他 DataSet。 - - - 這個關聯已經屬於這個 DataSet。 - - - 名稱為 '{0}' 的 Relation 已經屬於這個 DataSet。 - - - Relation {0} 不屬於這個 DataSet。 - - - 找不到關聯 {0}。 - - - 無法在 null 資料表上建立集合。 - - - 這個集合所顯示關聯的資料表已經從它的 DataSet 中移除。 - - - 無法加入關聯至這個資料表的 ParentRelation 集合,在該集合中這個資料表不是子資料表。 - - - 無法加入關聯至這個資料表的 ChildRelation 集合,在該集合中這個資料表不是父資料表。 - - - 無法將這個關聯的 'Nested' 屬性設定為 false。 - - - 在兩個巢狀關聯中,相同資料表 '{0}' 不可以是子資料表。 - - - 在巢狀關聯中資料表 ({0}) 不可是自己的子資料表。 - - - 加入 DataRelation 或 Constraint 時,若其父和子資料表之間有不同的 Locale 或 CaseSensitive 設定,就無法加入。 - - - 若 Parent 或 Child Columns 不在 DataSet 中時,無法建立 DataRelation。 - - - 繼承其命名空間的巢狀資料表 '{0}' 無法具有多個不同命名空間的父資料表。 - - - 附空白命名空間的巢狀資料表 '{0}' 無法具有多個不同命名空間的父資料表。 - - - 資料列和這個關聯不屬於相同的 DataSet。 - - - 無法對不存在於資料表中的資料列上執行這項作業。 - - - 這個關聯和子資料列不屬於相同的 DataSet。 - - - 無法變更 RowChanging 事件中的建議值。 - - - 無法在 OnRowChanging 事件內部呼叫 EndEdit。 - - - 無法在 RowChanging 事件內呼叫 BeginEdit()。 - - - 無法在 OnRowChanging 事件內部呼叫 CancelEdit()。擲回 exception 以取消這個更新。 - - - 無法在 OnRowDeleting 事件內部呼叫 Delete。擲回 exception 以取消這個刪除。 - - - 輸入陣列的長度大於這個資料表中的資料行數目。 - - - 沒有可存取的 Current 資料。 - - - 沒有可存取的 Original 資料。 - - - 沒有可存取的 Proposed 資料。 - - - 這個資料列已經從資料表中移除,而且沒有任何資料。BeginEdit() 將允許在這個資料列中建立新資料。 - - - 無法經由資料列存取已刪除的資料列資訊。 - - - 版本必須是 Original、Current 或 Proposed。 - - - 位置 {0} 沒有資料列。 - - - 無效的資料列插入位置 {0}。 - - - 名稱為 '{1}' 的資料表中有兩個 rowOrder value={0}。 - - - 遺漏了資料表 '{0}' 的 rowOrder 順序中的值。 - - - 指定的 DataRow 不在目前的 DataRowCollection 中。 - - - 這個資料列已經屬於其他資料表。 - - - 這個資料列已經屬於這個資料表。 - - - 無法刪除這個資料列,因為它之前已經被刪除了。 - - - 這個資料列是空的。 - - - 無法移除已移除的資料列。 - - - 子資料列擁有多個父資料列。 - - - 無法識別資料列位元模式。 - - - 無法將 DataSet 的名稱變更為空白字串。 - - - 名稱 '{0}' 無效。DataSet 不可以和 DataTable 同名。 - - - 結構描述命名空間無效,請改用這個命名空間: {0}。 - - - 無法變更 CaseSensitive 或 Locale 屬性。這項變更將會導致至少一個 DataRelation 或 Constraint 在它的相關資料表之間,擁有不同的 Locale 或 CaseSensitive 設定。 - - - SchemaSerializationMode 屬性只有在被衍生 DataSet 忽略時才能設定。 - - - PrimaryKey 資料行不屬於個資料表。 - - - 無法加入巢狀關聯或元素資料行至包含 SimpleContent 資料行的資料表。 - - - 當 TableName 是 DataSet 的一部分時,它是必要的。 - - - DataTable 已具有簡單內容資料行。 - - - 資料表沒有主索引鍵。 - - - {0} 不是有效的 Sort 字串項目。 - - - 無法序列化 DataTable。用於一或多個 DataColumn 運算式的 DataTable 並非目前 DataTable 的下階。 - - - 此 DataTable 僅可被遠端控制為 DataSet 的一部分。一或多個 Expression Columns 已參考至其他 DataTable。 - - - DataSet 與 DataTable 無法具有不同的遠端格式屬性值。 - - - 無法序列化 DataTable。未設定 DataTable 名稱。 - - - 名為 '{0}' 的 DataTable 已經屬於這個 DataSet。 - - - 名為 '{0}' 且同為 Namespace 同為 '{1}' 的 DataTable 已屬於此 DataSet。 - - - 資料表 ({0}) 在巢狀關聯中不可作為本身的子資料表: DataSet 名稱與資料表名稱發生衝突。 - - - 名稱 '{0}' 無效。DataTable 不可以和 DataSet 同名。 - - - DataTable 已經屬於其他 DataSet。 - - - DataTable 已經屬於這個 DataSet。 - - - 資料表 {0} 不屬於這個 DataSet。 - - - 找不到資料表 {0}。 - - - 無法移除擁有已存在關聯的資料表。請先移除關聯。 - - - 無法移除資料表 {0},因為它在 ForeignKeyConstraint {1} 中被參考。請先移除條件約束。 - - - DataTable '{0}' 與來源中的任何 DataTable 不相符。 - - - 目標 DataSet 遺漏 {0} 的定義。 - - - 目標 DataSet 遺漏 {0} {1}。 - - - <target>.{0} 和 <source>.{0} 擁有衝突的屬性: DataType 屬性不符。 - - - <target>.PrimaryKey 和 <source>.PrimaryKey 擁有不同的 Length。 - - - 不相符的資料行在 PrimaryKey 中: <target>.{0} 和 <source>.{1}。 - - - 關係 {0} 無法合併,因為索引鍵擁有不相符的資料行。 - - - 目標資料表 {0} 遺漏資料行 {1} 的定義。 - - - PrimaryKey 資料欄 {0} 不存在於來源 Table 中。 - - - RowStates 參數必須被設定為來自 DataViewRowState 列舉的有效值結合。 - - - 尋找會依排序順序尋找資料列,而未指定任何排序順序。 - - - 所檢索的索引鍵需要 {0} 個值,但接收到 {1} 個值。 - - - 彙總函式 {0}() 和型別的無效用法: {1}。 - - - 無效的儲存型別: {0}。 - - - DataSet Xml 永續性不支援值 '{0}' 當作 Char 值,請改用 Byte 存放區。 - - - 值型別與資料行類型不符 - - - 型別 '{0}' 無法實作 IComparable 介面。無法完成比較。 - - - 無法設定 {0}。 - - - 無法在已經是 DataSet 預設檢視表的 DataViewManager 上變更 DataSet。 - - - RowStateFilter 無法同時顯示 ModifiedOriginals 和 ModifiedCurrents。 - - - 無法在 DefaultView 或來自 DataViewManager 的 DataView 上變更 Table 屬性。 - - - 一旦設定 DataSet 屬性之後就無法變更。 - - - 在使用 DataViewManager 之前,必須先設定 DataSet。 - - - 一旦設定 Table 屬性之後就無法變更。 - - - 在使用 DataView 之前,必須先設定 DataTable。 - - - 無法繫結至沒有名稱的 DataTable。 - - - 無法設定物件至這個清單。 - - - 當 AllowNew 為 False 時,無法在 DataView 上呼叫 AddNew。 - - - DataView 未開啟。 - - - 關聯不是這個 DataView 指向資料表的父項。 - - - 當 AllowDelete 為 false 時,無法在 DataSource 上刪除。 - - - 當 AllowEdit 為 false 時,無法在 DataSource 上編輯。 - - - 索引 {0} 不是一個負數值,也不會超過資料列總數。 - - - 無法加入外部物件至這個清單。 - - - 無法清除這個清單。 - - - 無法插入外部物件至這個清單。 - - - 無法移除不在清單中的物件。 - - - {0} 既不是資料表 {1} 的 DataColumn,也不是該資料表的 DataRelation。 - - - 在 Range 物件中,Min ({0}) 必須小於或等於 max ({1})。 - - - 這是 null 範圍。 - - - MinimumCapacity 必須為非負數。 - - - 無法產生名稱 '{0}' 的識別項。 - - - 超過一個資料表使用相同的名稱 '{0}' (即使命名空間不同)。 - - - 資料行 '{0}': 類型 '{1}' 不可以是 null。 - - - 資料行 '{0}': 型別 '{1}' 沒有無參數的建構函式。 - - - 資料行 '{0}': 型別 '{1}' 沒有包含字串引數的建構函式。 - - - 無法將類型 '{0}' 的物件轉換為類型 '{1}' 的物件。 - - - Data Exception。 - - - Constraint Exception。 - - - 無法存取已刪除的資料列。 - - - 不允許重複的名稱。 - - - 不支援在 RowChanging 事件中操作。 - - - 無效的條件約束。 - - - 遺失的主索引鍵。 - - - 不允許 Null。 - - - 資料行已標示為唯讀。 - - - 資料表中找不到資料列。 - - - 找不到版本。 - - - 已修改 ReadOnly 資料。 - - - DataTableReader 對目前的 DataTable '{0}' 無效。 - - - 在 DataTableReader 中的目前 DataTable '{0}' 結構描述已經變更,DataTableReader 無效。 - - - 無法建立 DataTableReader。DataSet 中沒有 DataTable。 - - - 無法建立 DataTableReader。引數為空。 - - - 無法建立 DataTableReader。引數包含 null。 - - - Current DataRow 是 Deleted 或 Detached 狀態。 - - - 目前的 DataTable '{0}' 為空。DataTable 中沒有 DataRow。 - - - DataTable 內部索引已經毀損: '{0}'。 - - - 集合已經修改; 列舉作業可能無法執行。 - - - 已使用不同的 '{1}' 來宣告簡單類型 '{0}'。 - - - 無效的葉項。 - - - 一旦關聯的 DataSet 對應至載入的 XML 文件時,就無法變更資料表名稱。 - - - 一旦關聯的 DataSet 對應至載入的 XML 文件時,就無法變更資料表命名空間。 - - - 一旦關聯的 DataSet 對應至載入的 XML 文件時,就無法變更資料行名稱。 - - - 一旦關聯的 DataSet 對應至載入的 XML 文件時,就無法變更資料行命名空間。 - - - 一旦關聯的 DataSet 對應至載入的 XML 文件時,就無法變更 ColumnMapping 屬性。 - - - 一旦 DataSet 對應至載入的 XML 文件時,就無法從資料表加入或移除資料行。 - - - 一旦 DataSet 對應至載入的 XML 文件時,就無法從 DataSet 加入或移除資料表。 - - - 資料集對應至載入的 XML 文件後,就無法從 DataSet 加入、移除或變更巢狀關聯。 - - - DataSet 參數無效。它不可為 null。 - - - 一旦 DataSet 對應至載入的 XML 文件時,就無法變更 DataSet 名稱。 - - - 無法複製這種類型的節點: {0}。 - - - 如果 XmlDataDocument 已經包含資料,就無法載入它。請使用新的 XmlDataDocument。 - - - DataSet 最多能與一個 XmlDataDocument 產生關聯。當 DataSet 已經與其他 XmlDataDocument 產生關聯時,就無法與目前的 XmlDataDocument 產生關聯。 - - - 在嘗試使用 XML 作業編輯 XmlDataDocument 前請設定 DataSet.EnforceConstraints == false。 - - - 在 DataDocument 上不支援 GetElementById()。 - - - 無法在 DataDocument 上建立實體參考。 - - - XmlDataDocument 不支援對 DateSet 和 DataTable 使用 Clear 函式。 - - - 無法移除資料行,因為它已內建在這個 dataSet。 - - - 無法移除關聯,因為它已內建在此 dataSet。 - - - 當這個控制項的屬性變更時發生。 - - - 當這個集合的成員資格變更時發生。 - - - 無法取得值,因為它是 DBNull。 - - - 屬性 '{0}' 需要 Microsoft WindowsNT 或 WindowsNT 作業系統。 - - - 找不到要求的 .NET Framework Data Provider。可能尚未安裝。 - - - 要求的 .NET Framework Data Provider 實作沒有 System.Data.Common.DbProviderFactory 衍生類型的執行個體欄位。 - - - 找不到或無法載入註冊的 .NET Framework Data Provider。 - - - 所缺少的 .NET Framework Data Provider 組件限定名稱為必要項。 - - - 僅允許元素。 - - - 不允許子節點。 - - - 無法識別的屬性 '{0}'。 - - - 無法辨認的項目。 - - - '{0}' 區段僅可在每個組態檔中出現一次。 - - - 找不到必要的屬性 '{0}'。 - - - 必要的屬性 '{0}' 不可為空。 - - - '{0}' 參數必須是非空陣列。 - - - 屬性 '{0}' 僅可接受的值是 '{1}'。 - - - 屬性 '{0}' 可接受的值為 '{1}' 或 '{2}'。 - - - 指定的 QuotePrefix 與 QuoteSuffix 值不符。 - - - 引數 '{0}' 的長度超過其上限 '{1}'。 - - - 必須要收到引數型別 {1},卻收到型別 {0}。 - - - 索引鍵 '{0}' 的值無效。 - - - 必須要顯示索引鍵 '{1}' 才能使用索引鍵 '{0}'。 - - - 索引鍵 '{0}' 的值長度超過其上限 '{1}'。 - - - 不支援關鍵字: '{0}'。 - - - 無法載入 UDL 檔案。 - - - 無效的 UDL 檔案。 - - - 內部 .NET Framework Data Provider 錯誤 {0}。 - - - 一旦 Insert、Update 或 Delete 等命令產生後,就無法變更 QuotePrefix 和 QuoteSuffix 屬性。 - - - 必須初始化 DataAdapter.SelectCommand 屬性。 - - - 必須初始化 DataAdapter.SelectCommand.Connection 屬性; - - - {0} "{1}"。 - - - {0} "{1}",引號的用法不正確。 - - - {0} "{1}","{2}" 目前的限制不足。 - - - SqlBulkCopy.WriteToServer 已經失敗,因為 SqlBulkCopy.DestinationTableName 是無效的多部名稱 - - - 由於某個無效的多部名稱,故無法處理來自 SQL Server 的結果 - - - SqlParameter.UdtTypeName 是無效的多部名稱 - - - SqlParameter.TypeName 是無效的多部名稱 - - - SqlCommand.DeriveParameters 已失敗,因為 SqlCommand.CommandText 屬性值為無效的多部名稱。 - - - OdbcCommandBuilder.DeriveParameters 已失敗,因為 OdbcCommand.CommandText 屬性值為無效的多部名稱。 - - - OleDbCommandBuilder.DeriveParameters 已失敗,因為 OleDbCommandBuilder.CommandText 屬性值為無效的多部名稱。 - - - 不支援使用 MultiSubnetFailover 連接選項連接到鏡像的 SQL Server 執行個體。 - - - 由於 DataColumn '{1}' 是一個計算資料行,所以無法從 SourceColumn '{0}' 進行資料行對應。 - - - {1} 的 SourceColumn '{0}' 與 {3} 的 DataColumn '{2}' 之間發生不可轉換類型不符的問題。 - - - 在 DataTable '{1}' 中遺漏 SourceColumn '{2}' 的 DataColumn '{0}'。 - - - SourceColumn 必須是非空白字串。 - - - 遺漏 '{0}' 的 SourceColumn 對應。 - - - {2} 方法不支援 {0} 的列舉值 {1}。 - - - .NET Framework Odbc Data Provider 不支援 {0} 列舉值 {1}。 - - - .Net Framework OleDb Data Provider 不支援 {0} 列舉值 {1}。 - - - .NET Framework SqlClient Data Provider 不支援 {0} 列舉值 {1}。 - - - 無法擷取 ComputerNameDnsFullyQualified,{0}。 - - - 遺漏 '{1}' SourceTable 的 '{0}' DataTable。 - - - SourceTable 必須為非空白字串 - - - 遺漏 SourceTable 對應: '{0}' - - - {0}: CommandText 屬性尚未初始化 - - - {0}: Connection 屬性尚未初始化。 - - - {0} 必須有開啟與可用的 Connection。{1} - - - Fill: SelectCommand.Connection 屬性尚未初始化。 - - - FillPage: SelectCommand.Connection 屬性尚未初始化。 - - - FillSchema: SelectCommand.Connection 屬性尚未初始化。 - - - 更新需要具有連接物件的 InsertCommand。InsertCommand 的 Connection 屬性尚未初始化。 - - - 更新需要具有連接物件的 UpdateCommand。UpdateCommand 的 Connection 屬性尚未初始化。 - - - 更新需要具有連接物件的 DeleteCommand。DeleteCommand 的 Connection 屬性尚未初始化。 - - - 更新需要連接物件。Connection 屬性尚未初始化。 - - - 更新需要具有連接物件的命令複製。命令複製的 Connection 屬性尚未初始化。 - - - 更新需要連線。 - - - Update 需要具有開啟的連接物件的 {0}Command。{1} - - - Update 需要具有開啟的連接物件的 {0}Command。{1} - - - Update 需要具有開啟的連接物件的 {0}Command。{1} - - - Update 需要具有開啟的連接物件的更新命令。{1} - - - 預存程序 '{0}' 不存在。 - - - 指派給這個命令的交易必須是最內層的暫止本機交易。 - - - 交易可能與目前連接無關,或是已完成。 - - - 與目前連接關聯的交易已完成,但尚未處置。必須先處置交易,才能使用連接來執行 SQL 陳述式。 - - - 當指定給命令的連接為擱置的本機交易時,{0} 需要連接以交易。命令的 Transaction 屬性尚未初始化。 - - - 已經開啟一個與這個命令相關的 SqlResultSet,必須先將它關閉。 - - - 已經開啟與這個 {0} 建立關聯的 DataReader,必須先將它關閉。 - - - {0} DeriveParameters 只支援 CommandType.StoredProcedure,不支援 CommandType.{1}。 - - - 執行同樣的動作時,僅可呼叫方法 '{0}' 一次。 - - - 不正確的非同步結果。 - - - 呼叫 '{0}' 之前,SelectCommand 屬性尚未初始化。 - - - 此處不應出現 StatementType {0}。 - - - FillSchema: SourceTable 名稱必須是非空白的字串。 - - - {0} 的 MaxRecords 值無效。值必須 >= 0。 - - - {0} 的 StartRecord 值無效。值必須是 >= 0。 - - - Fill: SourceTable 名稱必須是非空白的字串。 - - - 階層式章節資料行必須對應至 AutoIncrement DataColumn。 - - - DataReader.GetFieldType({0}) 已傳回 null。 - - - 當使用 startRecords 或 maxRecords 的非零值時,僅指定 dataTables 陣列中的一個項目。 - - - Update 找不到 TableMapping['{0}'] 或 DataTable '{0}'。 - - - Update: 必須是非空白的 SourceTable 名稱。 - - - 當 TableMapping.DataSetTable='{0}' 時,遺漏 TableMapping。 - - - Update 需要有效的命令複製。 - - - Update 自動 SQL 產生時需要有效的 SelectCommand。 - - - 當傳遞擁有新資料列的 DataRow 集合時,Update 需要有效的 InsertCommand。 - - - 當傳遞擁有已修改資料列的 DataRow 集合時,更新需要有效的 UpdateCommand。 - - - 當傳遞擁有已刪除資料列的 DataRow 集合時,Update 需要有效的 DeleteCommand。 - - - DataRow[{0}] 是從除了 DataRow[0] 以外的其他 DataTable 取得。 - - - RowUpdatedEvent: 已經發生錯誤。沒有其他可用的資訊。 - - - RowUpdatingEvent: 已經發生錯誤。沒有其他可用的資訊。 - - - 當批次作業時,命令的 UpdateRowSource.FirstReturnedRecord 或 UpdateRowSource.Both 的 UpdatedRowSource 屬性值無效。 - - - 並行違規: UpdateCommand 已經影響必須是 {1} 記錄的 {0}。 - - - 並行違規: DeleteCommand 已經影響必須是 {1} 記錄的 {0}。 - - - 並行違規: 批次作業的命令已經影響必須是 {1} 記錄的 {0}。 + + 執行同樣的動作時,僅可呼叫方法 '{0}' 一次。 無效的 CommandTimeout 值 {0}。值必須是 >= 0。 - - {1}[{0}]: Size 屬性的大小 0 無效。 - - - {0}.Prepare 方法需要所有的參數都明確地設定型別。 - - - {0}.Prepare 方法需要所有的可變長度參數明確設定為非零的 Size。 - - - {0}.Prepare 方法必須使用型別為 '{1}' 且已明確設定 Precision 和 Scale 的參數。 - - - 不相符的 asyncResult 結束方法呼叫。必須要呼叫 {0} 卻呼叫 {1}。 - - - 作業無效。已經關閉連接。 - - - 連接已停用。 - - - 無法在交易中登記,因為本機交易正在連線上進行。請完成本機交易然後再試一次。 - - - 目前的連接中已有編列的交易。請完成目前的交易並重試。 - - - 資料庫不能是 Null、空字串或只有空白字元的字串。 - - - 引數太長。 - - - 無效的 'Connect Timeout' 值,這個值必須是 >= 0 的整數。 - - - 無效的來源緩衝區 ({0} 的大小) 位移: {1} - - - 無效的目的緩衝區 ({0} 的大小) 位移: {1} - - - 數值太大,超過 96 位元小數。 - - - 當資料流關閉時嘗試 {0} 無效。 - - - 指定的 SeekOrigin 值無效。 - - - 多個基底資料表不支援動態 SQL 產生。 - - - 沒有傳回任何基底資料表資訊的 SelectCommand 不支援動態 SQL 產生。 - - - 不會傳回任何重要資料行資訊的 SelectCommand 不支援 DeleteCommand 動態 SQL 的產生。 - - - 不會傳回任何重要資料行資訊的 SelectCommand 不支援 UpdateCommand 動態 SQL 的產生。 - - - 當 SelectCommand 不包含資料列版本資料行時,DeleteCommand 的動態 SQL 產生便不受支援。 - - - 當 SelectCommand 不包含資料列版本資料行時,UpdateCommand 的動態 SQL 產生便不受支援。 - - - 內含 QuotePrefix 或 QuoteSuffix 字元 '{1}' 的資料表名稱 '{0}' 不支援動態 SQL 的產生。 - - - 嘗試讀取資料行序數 '{0}' 無效。使用 CommandBehavior.SequentialAccess 時,您只能讀取大於或等於資料行序數 '{1}' 的值。 - - - 資料型別 '{0}' 無法組成為文字,因為包含無效的日期時間數字。 - - - 值無法組成必要型別的文字。 - - - 資料型別 '{0}' 無法組成文字,因為包含無效的小數位數上限。 - - - 提供的文字值不是資料型別 '{0}' 的有效文字。 - - - '{0}':文字值的長度必須均等。 - - - '{0}':文字值必須是十六進位的字串。 - - - 當尚未設定引號前置詞時,{0} 需要開啟的連接。 - - - 無法判斷正確的 Boolean 文字值。無法建立 Boolean 文字。 - - - 不支援與資料型別 '{0}' 相關聯的原始資料型別。 - - - {0} 的參數資料型別無效。 - - - 物件型別 {0} 與已知的管理提供者原始型別之間並無對應。 - - - 無法處理類型 {1} 所傳回的未知 TypeCode {0}。 - - - DbType {0} 與已知的 {1} 之間並無對應。 - - - 使用中的 SQL Server 版本不支援資料型別 '{0}'。 - - - 參數值 '{0}' 超出範圍。 - - - 指定的參數名稱 '{0}' 無效。 - - - 不支援多次傳回值參數。 - - - 參數 Size 的值 '{0}' 無效,該值必須大於或等於 0。 - - - 引數 '{0}' 的無效值。值必須大或等於 0。 - - - 此中繼資料的無效值。 - - - 中繼資料必須是 SqlDbType.Row - - - 將參數值從 {0} 轉換成 {1} 失敗。 - - - {0} 不支援平行交易處理。 - - - 這個 {0} 已經完成,它已不再是個可使用的項目。 - - - 當記錄為唯讀時無法呼叫 '{0}'。 - - - 當 DbDataRecord 為唯讀時無法呼叫 '{0}'。 - - - 不支援將物件型別 '{0}' 隱含轉換成資料型別 '{1}'。 - - - 緩衝區位移 '{1}' 加上可用位元組 '{0}',大於傳入之緩衝區的長度。 - - - 資料長度 '{0}' 小於 0。 - - - 指定的長度 '{0}' 超出範圍。 - - - 在 dataIndex '{0}' 上無效的 {2} 嘗試。使用 CommandBehavior.SequentialAccess 時,您只能讀取大於或等於 dataIndex '{1}' 的值。 - - - 位移必須參考值內的位置。 - - - "The ODBC 管理提供者要求指定 TABLE_NAME 限制,且 GetSchema 索引集合為非空值。 - - - 無效的方法 '{0}' 引數值。 - - - ODBC 提供者未從 SQLGETTYPEINFO 傳回結果。 - - - 未預期的 null DataTable 引數 - - - 未預期的 null DataSet 引數。 - - - 連接字串超過允許的最大長度 {0}。 - - - {0} - 無法對應型別。 - - - 未知的 SQL 型別 - {0}。 - - - 未知的 URT 型別 - {0}。 - - - 無效的負引數! - - - 無法在開啟的連接上設定屬性。 - - - SQL_TRANSACTION '{0}' 對應至 System.Data.IsolationLevel 列舉值無效。 - - - {0} - 無法啟用連接共用... - - - {0} - 無法配置環境控制代碼。 - - - {0} – 無法取得描述元控制。 - - - 不在交易之中 - - - 無效的 OdbcType 列舉值={0}。 - - - 當必須要產生 DBNull.Value 資料時請使用 IsDBNull。 - - - {0} [{1}] {2} - - - {0} - 找不到可用的錯誤資訊 - - - 連接已關閉。 - - - 內部連接沒有擁有者。 - - - .NET Framework Odbc 資料提供者需要 Microsoft Data Access Components(MDAC) 2.6 版或更新版本。目前發現已安裝版本 {0}。 - - - .NET Framework OleDb Data Provider 需要 Microsoft Data Access Components(MDAC) 2.6 版或更新版本。目前發現已安裝版本 {0}。 - - - '{1}' 提供者不支援 '{0}' 介面。目前的提供者無法使用 GetOleDbSchemaTable。 - - - '{0}' 失敗,沒有錯誤訊息,編碼: {1}。 - - - 沒有錯誤訊息,編碼: {0}。 - - - .NET Framework Data Providers 需要 Microsoft Data Access Components(MDAC)。請安裝 Microsoft Data Access Components(MDAC) 2.6 版或更新版本。 - - - .NET Framework Data Provider for OLEDB (System.Data.OleDb) 不支援 Microsoft OLE DB Provider for ODBC Drivers (MSDASQL)。請使用 .NET Framework Data Provider for ODBC (System.Data.Odbc)。 - - - .NET Framework Data Provider for OLEDB 不允許 OLE DB Provider 在非互動式環境中提示使用者。 - - - '{0}' 提供者並未登錄於本機電腦上。 - - - '{0}' 提供者不支援使用 ICommandText 介面。請改用 CommandType.TableDirect。 - - - '{0}' 提供者不支援 ITransactionLocal 介面。目前的提供者不提供本機的交易。 - - - 初始化字串的格式和 OLE DB 規格不一致。從連接字串中 char[{0}] 附近開始。 - - - .NET Framework Data OLE DB Provider(System.Data.OleDb) 不支援 'Asynchronous Processing' 功能。 - - - ConnectionString 中未指定 OLE DB Provider。正確範例為 'Provider=SQLOLEDB;'。 - - - 在 ConnectionString 中指定的OLE DB Provider (名稱) 太長。 - - - DbInfoKeywords OleDbSchemaGuid 應該是沒有限制的。 - - - DbInfoLiterals OleDbSchemaGuid 應該是沒有限制的。 - - - 結構描述 guid OleDbSchemaGuid 應該是沒有限制的。 - - - '{1}' 提供者不支援使用 {0} OleDbSchemaGuid 結構描述。 - - - '{0}' 組態設定的數值錯誤。 - - - 無法載入指定於組態設定 '{0}' 中的 XML 檔案。 - - - 命令參數[{0}] '{1}' 無效。 - - - 因為正負號不符合或資料溢位之外的原因,無法轉換命令參數[{0}]'{1}' 資料值。 - - - 命令參數[{0}] '{1}' 的轉換失敗,因為資料值為 signed,而提供者使用的型別為 unsigned。 - - - 命令參數[{0}] '{1}' 的轉換失敗,因為資料值溢位提供者使用的型別。 - - - 當傳送命令參數[{0}] '{1}' 值和停止處理時,提供者發生錯誤。 - - - 參數[{0}] '{1}' 沒有預設值。 - - - 使用參數[{0}]: {1} 時發生錯誤。 - - - Microsoft.Data.OleDb.OleDbDataAdapter 內部錯誤: 參數存取子無效: {0} {1}。 - - - Parameter[{0}]: OleDbType 屬性未初始化: OleDbType.{1}。 - - - '{0}' 提供者不支援使用 ICommandWithParameters 介面。目前的提供者並不支援這些命令參數。 - - - '{0}' 提供者不支援擷取程序的參數資訊。 - - - 無法判斷伺服器的小數位數分隔符號。無法建立非整數的數字。 - - - 該物件不是一個 ADODB.RecordSet 或 ADODB.Record。 - - - 無法擷取 ADODB.RecordSet 物件中的 '{0}' 介面。 + + {1}[{0}]: Size 屬性的大小 0 無效。 - - 無法擷取 ADODB.Record 物件中的 IRow 介面。 + + {0}.Prepare 方法需要所有的參數都明確地設定型別。 - - 型別不支援 OLE DB 介面 ISourcesRowset + + {0}.Prepare 方法需要所有的可變長度參數明確設定為非零的 Size。 - - 無法建立 ReservedWords 結構描述集合,因為提供者不支援 IDBInfo。 + + {0}.Prepare 方法必須使用型別為 '{1}' 且已明確設定 Precision 和 Scale 的參數。 - - 未設定屬性值,因為提供者不支援 '{0}' 屬性,或消費者嘗試取得或設定的屬性值不在初始化屬性群組中,且資料來源物件未初始化。 + + 不相符的 asyncResult 結束方法呼叫。必須要呼叫 {0} 卻呼叫 {1}。 - - 初始化 '{0}' 屬性失敗,理由為下列其中之一: - 值資料型別並非屬性的資料型別或並非空值。例如,資料型別為 Int32 的 DBPROP_MEMORYUSAGE 屬性,但資料型別卻為 Int64。 - 值為無效值。例如,屬性為 DBPROP_MEMORYUSAGE 且值為負數。 - 值為屬性的有效值,且提供者支援可設定的屬性,但卻不支援指定的值。例如,在寫入提供者後才新增至 OLE DB 屬性的值。 + + 作業無效。已經關閉連接。 - - Options 值無效。 + + 無法在交易中登記,因為本機交易正在連線上進行。請完成本機交易然後再試一次。 - - ColumnID 元素無效。 + + 目前的連接中已有編列的交易。請完成目前的交易並重試。 - - 指定 '{0}' 屬性套用於所有資料行,但無法套用至一或多個資料行。 + + 資料庫不能是 Null、空字串或只有空白字元的字串。 - - '{0}' 屬性是唯讀的,或消費者在已初始化資料來源物件後,才嘗試在初始化屬性群組中設定屬性值。消費者可以將唯讀屬性的值設定為目前的值。若無法在特定的資料行中設定可設定的資料行屬性,也可以傳回此狀態。 + + 無效的來源緩衝區 ({0} 的大小) 位移: {1} - - 選用的 '{0}' 屬性值未設定為指定值,且不可能設定屬性為指定值。 + + 無效的目的緩衝區 ({0} 的大小) 位移: {1} - - 未設定 '{0}' 屬性值,因為這樣做會與已存的屬性衝突。 + + 當資料流關閉時嘗試 {0} 無效。 - - (已保留)。 + + 指定的 SeekOrigin 值無效。 - - 提供者傳回未知的 DBPROPSTATUS_ value '{0}'。 + + 嘗試讀取資料行序數 '{0}' 無效。使用 CommandBehavior.SequentialAccess 時,您只能讀取大於或等於資料行序數 '{1}' 的值。 - - 當方法傳回資料時,已經延遲且執行存取子驗證。對這個資料行或參數的繫結無效。 + + {0} 的參數資料型別無效。 - - OleDbDataAdapter 內部錯誤: 無效的資料列集存取子: Ordinal={0} Status={1}。 + + 物件型別 {0} 與已知的管理提供者原始型別之間並無對應。 - - 因為發生非正負號不符或資料溢位的原因,資料值無法轉換。例如,資料於資料儲存中已經損毀但資料列仍可擷取。 + + 無法處理類型 {1} 所傳回的未知 TypeCode {0}。 - - 提供者無法配置傳回 {0} 資料的記憶體。 + + DbType {0} 與已知的 {1} 之間並無對應。 - - 轉換失敗,因為 {0} 資料值溢位消費者緩衝區中 {0} 數值部分的指定型別。 + + 使用中的 SQL Server 版本不支援資料型別 '{0}'。 - - OleDbDataAdapter 內部錯誤: [get] 未知的 OLE DB 資料型別: 0x{0} ({1})。 + + 參數值 '{0}' 超出範圍。 - - 轉換失敗,因為 {0} 資料值為 signed,而消費者緩衝區中 {0} 數值部分的指定型別為 unsigned。 + + 指定的參數名稱 '{0}' 無效。 - - OleDbDataAdapter 內部錯誤: [set] 未知的 OLE DB 資料型別: 0x{0} ({1})。 + + 參數 Size 的值 '{0}' 無效,該值必須大於或等於 0。 - - 提供者無法決定 {0} 值。例如,剛建立資料列,{0} 資料行的預設值無法使用,而消費者尚未設定新 {0} 值。 + + 引數 '{0}' 的無效值。值必須大或等於 0。 - - OLE DB Provider 傳回 {0} 的未預期狀態值。 + + 此中繼資料的無效值。 - - 如果執行緒的 ApartmentState 不是 ApartmentState.MTA,OleDbDataReader.Read 必須在建立其本身的執行緒上使用。 + + 將參數值從 {0} 轉換成 {1} 失敗。 - - 未指定的錯誤: {0} + + {0} 不支援平行交易處理。 - - IErrorInfo.GetDescription 因為 {0} 而失敗。 + + 這個 {0} 已經完成,它已不再是個可使用的項目。 - - IErrorInfo.GetSource 因為 {0} 而失敗。 + + 指定的長度 '{0}' 超出範圍。 - - .NET Framework Data OLE DB Provider(System.Data.OleDb) 不支援 DBTYPE_VECTOR 資料。 + + 在 dataIndex '{0}' 上無效的 {2} 嘗試。使用 CommandBehavior.SequentialAccess 時,您只能讀取大於或等於 dataIndex '{1}' 的值。 無效的最小或最大共用區大小值,共用區最小值不可以大於共用區最大值。 - - '{0}' 關鍵字已過時。請改用 '{1}'。 - 無法自伺服器取得其分散式交易協調器的位址。伺服器上的 DTC 是否已經啟用? - - 連接參數 <{0}> 的長度值超過所允許 65535 個字元的最大值。 - 無效的 'Packet Size' 值。這個值必須是 >= 512 且 <= 32768 的整數。 交易中儲存點的無效交易或無效名稱。 - - SQL Server 7.0 或 SQL Server 2000 不支援 {0} 的列舉值 {1}。 - User Instance 和 Failover 為不相容的選項。請在此連接字串中只選擇其中一個。 @@ -2499,33 +540,21 @@ 如果連接字串中已指定 'Authentication=Active Directory Integrated',則無法設定 Credential 屬性。 - - 使用憑證驗證指定的驗證參數不正確。 - 您嘗試連接的 SQL Server 執行個體需要加密,但此電腦不支援。 您嘗試連接的 SQL Server 執行個體不支援加密。 - - 您嘗試連線的目標 SQL Server 執行個體不支援 CTAIP。 - 不支援的 SQL Server 版本。.NET Framework SqlClient Data Provider 僅可搭配 SQL Server 7.0 版及更新版本使用。 進行非同步操作時,{0} 無法變更。 - - 此命令需要非同步連接。請在連接字串中設定 "Asynchronous Processing=true"。 - 連接嘗試逾時。 - - 等候逾時已過時。結果為已連接中斷。 - 執行個體失敗。 @@ -2556,27 +585,18 @@ 錯誤碼 0x{0} - - ChangePassword 需要 SQL Server 9.0 或更新的版本。 - 不支援非本端使用者執行個體的 SSE 執行個體重新導向。 不支援驗證 '{0}'。 - - 不支援 SQL 驗證方法 '{0}'。 - 無法為 '{0}' 具現化類型為 '{1}' 的驗證提供者。 無法將類型為 '{0}' 的 SqlAuthenticationInitializer 具現化。 - - 提供者 '{0}' 在初始化期間擲回例外狀況。 - 提供者 '{0}' 不支援驗證 '{1}'。 @@ -2586,24 +606,15 @@ 找不到 '{0}' 的驗證提供者。請安裝 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 套件 (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure),以使用 Active Directory (Entra ID) 認證方法。 - - 無法讀取驗證提供者的組態區段。 - 參數 '{0}' 不能是 Null 或空白。 - - 已完成非同步作業。 - 不可因為已在進行的非同步作業擱置而先執行命令。 無效的命令傳送到 ExecuteXmlReader。命令必須傳回 Xml 結果。 - - 通知需要 SQL Server 9.0 或更新的版本。 - UdtTypeName 的 3 個名稱組成格式無效。 @@ -2697,9 +708,6 @@ 無法初始化 SSPI 封裝。 - - 已超過連接 Timeout 的設定。在作業完成之前超過逾時等待的時間,或者是伺服器未回應。 - 已超過連接逾時的設定。在登入前的開始階段超過逾時等待的時間。這可能是因為連接逾時提供的時間不足所造成。 @@ -2745,12 +753,6 @@ 連接字串中要求一個使用者執行個體,但指定的伺服器並不支援此選項。 - - 伺服器憑證無法用於驗證應用程式。 - - - 指定資料的長度 {0} 超過允許的上限 {1}。 - 當目前沒有資料時,嘗試讀取無效。 @@ -2775,9 +777,6 @@ 資料長度 '{0}' 小於 0。 - - 當 SqlDataRecord 為唯讀時,無法呼叫 '{0}'。 - 對應的集合正在使用中且無法在此時存取; @@ -2847,21 +846,6 @@ 不支援使用 Microsoft.Data.SqlClient 連線至內容連線。 - - 不支援巢狀 TransactionScopes。 - - - 未預期的伺服器事件: {0}。 - - - '{0}' 的長度必須和 '{1}' 的長度相符。 - - - SqlDbType '{0}' 對 {1} 是無效的。只支援 {2}。 - - - 太多數值。 - 此 Stream 不支援寫入動作。 @@ -2886,192 +870,36 @@ Null - - 訊息 - - - 算術溢位。 - - - 發生除以零的錯誤。 - - - 資料為 Null。無法在值為 Null 的情況下呼叫這個方法或屬性。 - - - 數字算術導致截斷。 - SqlDateTime 溢位。必須在 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之間 - - 要串連的兩個字串擁有不同的定序。 - - - 要比較的兩個字串擁有不同的定序。 - - - 無效的旗標值。 - - - 從 SqlDecimal 轉換成 Decimal 溢位。 - 轉換溢位。 - - 無效的 SqlDateTime。 - - - 已經指定時區。SqlDateTime 不支援時區。 - 無效的陣列大小。 無效的數字整數位數/小數位數。 - - 輸入格式錯誤。 - - - SqlType 錯誤。 - - - 在此版本中 SqlBytes 及 SqlChars 不支援大於 2GB 的長度。 - 讀取時發生錯誤。 - - Stream 已關閉或處置。 - - - 沒有緩衝區。讀取或寫入作業失敗。 - - - 無法設定非零的長度,因為目前的值是 Null。 - - - 緩衝無效。讀取或寫入作業失敗。 - - - 無法寫入非零的位移,因為目前的值是 Null。 - - - 無法從大於目前長度的位移寫入。將會在緩衝區留下未初始化的資料。 - 傳回的資料大小大於 2Gb。請使用 SequentialAccess command behavior 以取得所有資料。 - - GetDayOfWeek 的引數必須為介於 1 與 7 間的整數。 - - - SQL Type 尚未載入資料。 - - - SQL Type 已經載入資料。 - - - 嘗試存取關閉的 XmlReader 無效。 - - - 當資料流已關閉時,嘗試呼叫 {0} 失敗。 - - - 當無法寫入資料流時,嘗試呼叫 {0} 失敗。 - - - 當無法讀取資料流時,嘗試呼叫 {0} 失敗。 - - - 當找不到資料流時,嘗試呼叫 {0} 失敗。 - - - 子類別並未覆寫所需的方法。 - 無法建立 '{0}' 的正規器。 內部錯誤 - - 命令參數必須有一個非空值且非空的命令文字。 - - - MetaData 參數陣列必須有等同於 ParameterDirection 陣列引數的長度。 - - - 資料配接器對應錯誤。 - - - 資料配接器錯誤。 - - - 資料庫並行違規。 - 操作已中止。 由於例外狀況操作已中止 (請參閱 InnerException 以取得詳細資訊)。 - - Fill 是否將呼叫 DataRow.AcceptChanges。 - - - Update 是否會呼叫 DataRow.AcceptChanges。 - - - 當 Update 事件 (RowUpdating 和 RowUpdated) 的 Status 為 UpdateStatus.ErrorsOccurred 時,是否要繼續執行下一個 DataRow。 - - - 配接器如何從 DataReader 填滿 DataTable。 - - - 當在 TableMappings 中的資料表或資料行遺漏時所採取的動作。 - - - 當在 DataSet 中的資料表或資料行遺漏時所採取的動作。 - - - 如何將來源資料表對應至 DataSet 資料表。 - - - 在 Fill 期間若發生可復原的錯誤時觸發的事件。 - - - Fill 應傳回給提供者特定值或一般 CLSCompliant 值。 - - - DataColumn.ColumnName - - - 來源資料行名稱 - 區分大小寫。 - - - 集合中的項目數目 - - - 指定的 DataColumnMapping 物件。 - - - 當符合這個資料表的對應時,以個別資料行的方式對應。 - - - DataTable.TableName - - - DataTableMapping 來源資料表名稱。這個名稱有區分大小寫。 - - - 集合中的項目數目 - - - 指定的 DataTableMapping 物件 - 在 DataSet 中對已刪除的資料列進行 Update 時使用。 @@ -3090,63 +918,9 @@ 在對每個 DataRow 進行 Update 之後觸發的事件。 - - 執行前一同批次作業的資料列數與資料來源不符。 - - - 若 Select/Insert/Update/DeleteCommands 尚未連接時,會使用連接。 - - - 在 DataTable 中對已刪除的資料列進行 Update 時使用。 - - - 在 DataTable 中對新增的資料列進行 Update 時使用。 - - - 用於 Fill 時。 - - - 在 DataTable 中對已修改的資料列進行 Update 時使用。 - - - Fill 應傳回給提供者特定值或一般 CLSCompliant 值。 - - - 如何將來源資料表對應至 DataTable。 - - - 當使用者未設定時,如何產生 Insert/Update/DeleteCommands。 - - - 執行前一同批次作業的資料列數與資料來源不符。 - - - 用於連接至 Data Source 的連接字串。 - - - 當連接至 Data Source 所用的 ODBC Driver 名稱。 - - - 當連接至 Data Source 所用的 DSN。 - - - 當 True 時,表示應使用管理連接集區。 - - - 當連接至 Data Source 所用的 UDL 檔案。 - - - 指定要啟用的 OLE DB Services 或使用 OleDb Provider 來停用它。 - - - 當連接至 Data Source 時所用的 OLE DB Provider 名稱。 - 應用程式的名稱。 - - 若為 true,則允許在 .NET Framework Data Provider 中使用 Asynchronous 功能。 - 可附加資料庫中的主要檔案名稱,包含名稱的完整路徑。 @@ -3156,9 +930,6 @@ 當 True 時,表示當連接狀態從集區中移除時已重設。 - - 為 True 時,表示連結的來源應為 Sql Server 內容。只有在 Sql Server 程序中執行時才能使用。 - SQL Server Language 記錄名稱。 @@ -3252,54 +1023,9 @@ 指定使用 SQL Server 的驗證方法。 - - 用以向 SQL Server 驗證的指定用戶端憑證。 - - - 用來連接至資料來源的資訊。 - - - 目前的連接逾時值,無法於 ConnectionString 中設定。 - - - 目前的資料來源目錄值,即為連接字串中的 'Database=X'。 - - - 目前的資料來源,即為連接字串中的 'Server=X'。 - - - 目前所使用的 ODBC 驅動程式。 - - - ODBC 驅動程式所存取的產品版本。 - - - 用來連接至資料來源的資訊。 - - - 目前連接的逾時值,即為 ConnectionString 中的 'Connect Timeout=X'。 - - - 目前的資料來源目錄值,即為連接字串中的 'Initial Catalog=X'。 - - - 目前的資料來源,即為連接字串中的 'Data Source=X'。 - - - 目前的 OLE DB 提供者 ProgID,即為連接字串中的 'Provider=X'。 - - - OLE DB Provider 所存取的產品版本。 - 驗證要用的存取金鑰。 - - 連接、同步或非同步的狀態。即為連接字串中的 'Asynchronous Processing=x'。 - - - 用於複寫連接的資訊。 - 用來連接至 DataSource 的資訊,例如 'Data Source=x;Initial Catalog=x;Integrated Security=SSPI'。 @@ -3324,9 +1050,6 @@ 收集此連接的統計資料。 - - 自訂資料行加密金鑰存放區提供者。 - 代表實體連接的 Guid。 @@ -3363,99 +1086,9 @@ 用於 Microsoft SQL Server 的自動編列通知。 - - 當使用者未指定 Update 與 Delete 命令時,會如何自動產生 where 子句。 - - - 表示在文字命令中,目錄名稱在適當資料表名稱中的位置。 - - - 在文字命令中,將目錄名稱與其餘識別項分隔的字元。 - - - 在文字命令中,將結構描述名稱與其餘識別項分隔的字元。 - - - 在 sql 物件周圍包裝的前置詞字串。 - - - 在 sql 物件周圍包裝的後置詞字串。 - - - 自動產生 Commands 的目標 DataAdapter。 - - - 使用 DataTable 或 SelectCommand 中的結構描述。 - - - 當使用者未指定 Update 命令時,如何自動產生 set 子句。 - - - 用以自動產生 OdbcCommands 的 DataAdapter - - - 文字命令中,用來當作引號識別項 (其中包含特殊字元) 開頭引號的字元。 - - - 文字命令中,用來當作引號識別項 (其中包含特殊字元) 結束引號的字元。 - - - 自動產生 OleDbCommands 的 DataAdapter - - - 用於數字的小數點分隔符號。 - - - 在 sql 物件周圍包裝的前置詞字串 - - - 在 sql 物件周圍包裝的後置詞字串 - 自動產生 SqlCommands 項目的 DataAdapter - - 用於數字的小數點分隔符號。 - - - 文字命令中,用來當作引號識別項 (其中包含特殊字元) 開頭引號的字元。 - - - 文字命令中,用來當作引號識別項 (其中包含特殊字元) 結束引號的字元。 - - - 當與 Prepare、FillSchema 和 CommandBuilder 情節搭配使用時,只需要設定為十進位數字參數。 - - - 當與 Prepare、FillSchema 和 CommandBuilder 情節搭配使用時,只需要設定為十進位數字參數。 - - - 參數原生型別。 - - - 參數原生型別。 - - - 參數的名稱,例如 '@p1' - - - 參數原生型別。 - - - 型別的伺服器名稱。 - - - 可變長度資料型別中的位移。 - - - XmlSchemaCollectionDatabase - - - XmlSchemaCollectionOwningSchema - - - XmlSchemaCollectionName - 不支援為參數 '{1}' 所指定的 ParameterDirection '{0}'。Table-valued parameters 只支援 support ParameterDirection.Input。 @@ -3468,9 +1101,6 @@ 參數 '{0}' 指定的 TypeName。TypeName 必須只設定為 Structured 參數。 - - SqlDbType.Structured 型別只支援多值的型別。 - 結構描述資料表中欄位 '{0}' 的 DateType 資料行為 null。DataType 不可為 null。 @@ -3483,18 +1113,9 @@ 記錄 '{0}' 中的欄位數目與原始記錄中的數目不符。 - - 範圍: 0-8000 - SqlClient 資料提供者發生無法預期的錯誤。{0} - - 無法取得 {0}、{1} 的 Type Info - - - 提供者載入下列組件失敗: {0} - 必須為 UDT 參數設定 UdtTypeName 屬性。 @@ -3504,72 +1125,9 @@ 指定的型別並未在目標伺服器上註冊。{0}。 - - 除非 UDT 參數為主索引鍵的一部分,否則不允許出現在 where 子句中。 - - - '{0}' 是無效的使用者定義型別,理由是: {1}。 - - - 同時支援記憶體中格式與使用者定義格式 - - - 不支援負數格式。 - - - 未實作 IBinarySerialize - - - 非連續的 - - - 無公用建構函式 - - - 沒有實作 INullable - - - 沒有公用建構函式 - 無 UDT 屬性 - - 尚未支援無對應的序列化。 - - - 'public static x Parse(System.Data.SqlTypes.SqlString)' 方法遺失 - - - 'public override string ToString()' 方法遺失 - - - 'public static x Null { get; }' 方法遺失 - - - Native 不支援型別 '{0}' (直接或經由其他欄位) 的欄位 - - - Type 並非公用的 - - - Native UDT 並非連續的配置,因為型別為 '{0}' - - - Native UDT 指定最大 byte 大小 - - - 欄位 '{0}' 已標示為非連續 - - - '{0}' 欄位的類型標示為明確配置,原生格式不允許此種配置 - - - 多值的組件參考必須有非零的 Assembly Id。 - - - 結構化、多值型別只可以用於參數。不可以巢狀方式置於其他型別中。 - 路徑名稱無效。 @@ -3585,9 +1143,6 @@ 嘗試升級交易時失敗。 - - 用於接收查詢通知的依存物件。 - 表示此相依性無效的屬性。 @@ -3600,9 +1155,6 @@ 新增命令至現存的依存物件。 - - Command 已與其他依存的物件關聯。無法覆寫。 - SqlCommand 物件已與其他 SqlDependency 物件關聯。 @@ -3630,12 +1182,6 @@ 指定的等候 Timeout 無效。等候 Timeout 不可 < 0。 - - 通知錯誤。Type={0}, Info={1}, Source={2}。 - - - GetMetaData 對此 SqlDbType 無效。 - 此建構函式的 dbType {0} 無效。 @@ -4104,9 +1650,6 @@ 內部錯誤。Plaintext 值不能是 Null。 - - 無法加密。加密結果為 {0} 個位元組的 ciphertext,這超出了 {1} 個位元組的允許上限。指定的 plaintext 值可能太大 (plaintext 大小為: {2} 個位元組)。 - 內部錯誤。Ciphertext 值不能是 Null。 @@ -4224,9 +1767,6 @@ SQL Server 未傳回證明資訊。enclave 類型為 '{0}',enclave 證明 URL 為 '{1}'。 - - 無法建立安全通道。錯誤訊息: {0} - 內部錯誤。建構類型為 '{1}' 的物件時,指定了 null 引數 '{0}',但 '{0}' 不得為 null。 @@ -4242,15 +1782,6 @@ 請指定 enclave 證明服務的端點,供驗證 SQL Server 執行個體中,為使用 Always Encrypted 加密之資料庫資料行計算而設定的 enclave 是否安全有效之用。 - - 無法讀取 enclave 提供者的組態區段。請確定您應用程式組態檔中的區段格式正確。錯誤訊息: {0} - - - 無法具現化名稱為 '{0}' 類型為 '{1}' 的 enclave 提供者。錯誤訊息: {2} - - - 內部錯誤。SqlColumnEncryptionEnclaveProviderName 不得為 null 或空白。 - 讀取 '{0}' 結果集時發生錯誤。未在連接字串中指定證明 URL,但查詢需要記憶體保護區計算。記憶體保護區類型是 '{1}'。 @@ -4260,9 +1791,6 @@ 內部錯誤。執行需要 enclave 計算的查詢時,從 SQL Server 收到了 null 或空白的 enclave 類型。 - - 執行查詢需要 enclave 計算,但應用程式組態沒有 enclave 提供者區段。 - 找不到記憶體保護區類型 '{0}' 和證明通訊協定 '{1}' 的記憶體保護區提供者。請在連接字串中指定正確的證明通訊協定。 @@ -4320,9 +1848,6 @@ 連線中所有命令的預設資料行加密設定。 - - 傳送敏感性資料至伺服器之前,強制加密參數。 - 包含 SQL Server 名稱及其信任的資料行主要金鑰路徑的字典物件。 @@ -4332,9 +1857,6 @@ 不支援使用 ApplicationIntent ReadOnly 連接選項連接到 SQL Server 鏡像執行個體。 - - 收到未預期的路由資訊。 - 收到無效的路由資訊。 @@ -4485,9 +2007,6 @@ 在此內容 {0} 中指定了不支援的驗證 - - Active Directory 互動式驗證逾時。使用者回應驗證要求所花的時間過長。 - 如果已設定 Credential 屬性,就無法使用 'Authentication=Active Directory Interactive'。 @@ -4497,9 +2016,6 @@ 使用中連線的伺服器處理序識別碼 (SPID)。 - - Active Directory 裝置代碼流程驗證逾時。使用者回應驗證要求所花的時間過長。 - 如果連接字串中已指定 'Authentication=Active Directory Device Code Flow',則無法設定 Credential 屬性。 @@ -4533,9 +2049,6 @@ 所要求的動作只支援一維陣列。 - - 由於在指定的集合中找不到指定的項目,所以無法移除。 - 需要非負數。 @@ -4563,87 +2076,36 @@ 如果已在連接字串中指定 'Authentication=Active Directory Default',就不能設定 AccessTokenCallback 屬性。 - - {0} 已傳回 {1}。 - - - 通訊協定錯誤: 收到的訊息包含有效的簽章,但是未如生效的保護等級所要求加密。 - - - {0} 失敗,發生錯誤: {1}。 - 此方法尚未由此類別實作。 - - 不支援要求的安全性套件。 - 不正確的 physicalConnection 類型。 嘗試設定 AppCoNtext 切換 '{0}'={1} 時發生例外。 - - 此平台上不支援類型 {0}。 - 不支援全域固定模式。 - - 無法使用 Kerberos 進行驗證。請確保已在用戶端上使用 'kinit' 初始化 Kerberos,並且已登錄 SQL Server 的服務主體名稱,以允許 Kerberos 驗證。 - 此平台上不支援關鍵字 'Network Library',請改為為 'Data Source' 加上所需通訊協定的首碼 (TCP 連線為 'tcp:' 或具名管道連線為 'np:')。 通訊端未擲出預期的 '{0}',錯誤碼為 '{1}'。 - - 伺服器正嘗試使用此平台上不支援的功能。 - 此平台上不支援關鍵字 '{0}'。 目前載入的 System.Transactions.dll 不支援全域交易。 - - 從伺服器讀取資料時收到不支援的權杖 '{0}'。 - - - '{0}' 不是支援的控制代碼類型。 - 此平台上不支援 SqlFileStream。 此平台上不支援 LocalDB。 - - 緩衝區空間不足。需要: {0} 實際: {1}。 - - - GSSAPI 作業失敗,出現狀態: {0} (次要狀態: {1})。 - - - GSSAPI 作業失敗,發生錯誤 - {0} ({1})。 - - - 不支援 Windows 以外作業系統上的通道繫結。 - - - 如果傳遞預設認證,則目標名稱應該不是空白。 - - - 使用目前安裝的 GSSAPI 實作不支援要求的保護層級。 - - - 不支援伺服器實作。 - - - 此平台上的預設認證無法使用 NTLM 驗證。 - 由 DataAdapter.Update 使用時,參數值會從 DBNull.Value 變更為 (Int32)1 或 (Int32)0 (如果非 Null)。 @@ -4668,9 +2130,6 @@ 已超過緩衝區大小上限: {0} - - 遺漏 SourceColumn '{2}' 的 DataColumn '{0}'。 - SqlBatchCommand 列表尚未初始化。 From 34add538fef682ff6be115b9973cf4e064e4b8f2 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:23:44 -0300 Subject: [PATCH 067/298] Add 7.0.1 release notes (#4191) --- .github/prompts/release-notes.prompt.md | 21 +++-- CHANGELOG.md | 42 +++++++++ release-notes/7.0/7.0.1.md | 115 ++++++++++++++++++++++++ release-notes/7.0/README.md | 2 +- 4 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 release-notes/7.0/7.0.1.md diff --git a/.github/prompts/release-notes.prompt.md b/.github/prompts/release-notes.prompt.md index 55cc7785e2..b847e3c17a 100644 --- a/.github/prompts/release-notes.prompt.md +++ b/.github/prompts/release-notes.prompt.md @@ -1,19 +1,19 @@ --- name: release-notes description: Generate release notes for a specific milestone, covering all packages in the repository that have changes. -argument-hint: +argument-hint: agent: agent -tools: ['edit/createFile', 'edit/editFiles', 'read/readFile'] +tools: ['edit/createFile', 'edit/editFiles', 'read/readFile', 'execute/runInTerminal'] --- -Generate release notes for the milestone "${input:milestone}". +Generate release notes for the milestone "${input:milestone}" on the branch "${input:branch}". This repository ships multiple packages. Only generate release notes for packages that have relevant PRs in the milestone. All packages use the same template: [release-notes/template/release-notes-template.md](release-notes/template/release-notes-template.md). ## Package Registry | Package | Release Notes Directory | How to Identify PRs | -|---------|------------------------|---------------------| +| ------- | ----------------------- | ------------------- | | `Microsoft.Data.SqlClient` | `release-notes//` | Default — PRs not assigned to another package | | `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` | `release-notes/add-ons/AzureKeyVaultProvider//` | Labels containing `AKV`, or PR titles/bodies/files referencing `AzureKeyVaultProvider`, `add-ons/`, or `AlwaysEncrypted.AzureKeyVaultProvider` | | `Microsoft.SqlServer.Server` | `release-notes/MSqlServerServer//` | PR titles/bodies/files referencing `Microsoft.SqlServer.Server` or `src/Microsoft.SqlServer.Server/` | @@ -26,7 +26,7 @@ This repository ships multiple packages. Only generate release notes for package Each package has its own versioning and dependency sources. Use these to determine package versions and dependency lists: | Package | Version Source | Dependency Source | -|---------|---------------|-------------------| +| ------- | -------------- | ----------------- | | `Microsoft.Data.SqlClient` | [tools/props/Versions.props](tools/props/Versions.props) (`MdsVersionDefault`) | [Directory.Packages.props](Directory.Packages.props) and the [project file](src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj) | | `AzureKeyVaultProvider` | [tools/props/Versions.props](tools/props/Versions.props) (`AkvVersionDefault`) | [AKV project file](src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj) and [Directory.Packages.props](Directory.Packages.props) | | `Microsoft.SqlServer.Server` | [tools/props/Versions.props](tools/props/Versions.props) (`SqlServerPackageVersion`) | [SqlServer project file](src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj) | @@ -46,7 +46,8 @@ This prompt uses the following skill: ### 1. Fetch Milestone Items - Follow the instructions in the [fetch-milestone-prs](.github/skills/fetch-milestone-prs/SKILL.md) skill to fetch all merged PRs for the milestone "${input:milestone}". -- The output will be saved to `.milestone-prs/${input:milestone}/` with individual JSON files per PR and an `_index.json` summary. +- The output will be saved to `.milestone-prs/${input:milestone}/${input:branch}` with individual JSON files per PR and an `_index.json` summary. +- Identify any milestone items that don't have corresponding commits on the release branch "${input:branch}", and vice versa. ### 2. Analyze and Categorize @@ -119,6 +120,14 @@ For each package that has relevant PRs in the milestone: - If a section for the package doesn't yet exist, add one following the existing pattern (see the `AzureKeyVaultProvider` and `Microsoft.SqlServer.Server` sections for reference). - If the section already exists, add the new version link to its Release Information list. +### 8. Markdown for GitHub Release + +- Use the contents of the new release notes markdown file to produce markdown suitable for pasting into a GitHub UI Release textbox. + - GitHub renders newlines within paragraphs and lists as hard breaks, so remove those. + - Omit the main heading and first sub-heading. + - Update any relative links to use absolute URLs pointing to the file in the repository. + - Provide this new markdown in a code block that can easily be copied and pasted directly into the GitHub UI. + ## Notes - Packages may ship as preview or stable independently. Use the actual version from the project/spec files. diff --git a/CHANGELOG.md b/CHANGELOG.md index fedf1ad568..c73574f086 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,48 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) > **Note:** Releases are sorted in reverse chronological order (newest first). +## [Stable Release 7.0.1] - 2026-04-23 + +This update brings the following changes since the [7.0.0](release-notes/7.0/7.0.0.md) release. +See the [full release notes](release-notes/7.0/7.0.1.md) for detailed descriptions. + +### Fixed + +- Fixed `SqlBulkCopy` failing on SQL Server 2016 with `Invalid column name 'graph_type'` error by using dynamic SQL to extract column names. + ([#3714](https://github.com/dotnet/SqlClient/issues/3714), + [#4092](https://github.com/dotnet/SqlClient/pull/4092), + [#4147](https://github.com/dotnet/SqlClient/pull/4147)) + +- Fixed `SqlBulkCopy` failing on Azure Synapse Analytics dedicated SQL pools by using `STRING_AGG` for the column-list query when targeting Synapse. + ([#4149](https://github.com/dotnet/SqlClient/issues/4149), + [#4176](https://github.com/dotnet/SqlClient/pull/4176), + [#4182](https://github.com/dotnet/SqlClient/pull/4182)) + +- Fixed `SqlDataReader.GetFieldType()` and `GetProviderSpecificFieldType()` returning incorrect type for vector float32 columns. + ([#4104](https://github.com/dotnet/SqlClient/issues/4104), + [#4105](https://github.com/dotnet/SqlClient/pull/4105), + [#4152](https://github.com/dotnet/SqlClient/pull/4152)) + +- Added missing `System.Data.Common` (v4.3.0) NuGet package dependency for .NET Framework consumers to resolve `CS0012` compilation errors. + ([#4063](https://github.com/dotnet/SqlClient/pull/4063), + [#4074](https://github.com/dotnet/SqlClient/pull/4074)) + +### Changed + +- Enabled the User Agent TDS feature extension unconditionally; removed the `Switch.Microsoft.Data.SqlClient.EnableUserAgent` AppContext switch. + ([#4124](https://github.com/dotnet/SqlClient/pull/4124), + [#4154](https://github.com/dotnet/SqlClient/pull/4154)) + +- Added type forwards from the core assembly to public types moved to `Microsoft.Data.SqlClient.Extensions.Abstractions`. + ([#4067](https://github.com/dotnet/SqlClient/pull/4067), + [#4117](https://github.com/dotnet/SqlClient/pull/4117)) + +- Fixed API documentation include paths and duplicate doc snippets. + ([#4084](https://github.com/dotnet/SqlClient/pull/4084), + [#4086](https://github.com/dotnet/SqlClient/pull/4086), + [#4107](https://github.com/dotnet/SqlClient/pull/4107), + [#4161](https://github.com/dotnet/SqlClient/pull/4161)) + ## [Stable Release 7.0.0] - 2026-03-17 This section summarizes all changes across the 7.0 preview cycle for users upgrading from the latest 6.1 stable release. diff --git a/release-notes/7.0/7.0.1.md b/release-notes/7.0/7.0.1.md new file mode 100644 index 0000000000..9d64ae910c --- /dev/null +++ b/release-notes/7.0/7.0.1.md @@ -0,0 +1,115 @@ +# Release Notes + +## Stable Release 7.0.1 - 2026-04-23 + +This update brings the following changes since the [7.0.0](7.0.0.md) release: + +### Fixed + +- Fixed `SqlBulkCopy` failing on SQL Server 2016 with `Invalid column name 'graph_type'` error. The column metadata query now uses dynamic SQL so that references to the `graph_type` column (introduced in SQL Server 2017) are not compiled on older versions that lack the column. + ([#3714](https://github.com/dotnet/SqlClient/issues/3714), + [#4092](https://github.com/dotnet/SqlClient/pull/4092), + [#4147](https://github.com/dotnet/SqlClient/pull/4147)) + +- Fixed `SqlBulkCopy` failing on Azure Synapse Analytics dedicated SQL pools. The column-list query previously used a variable-assignment pattern that Synapse does not support; it now uses `STRING_AGG` when targeting Synapse (engine edition 6) and falls back to the variable-assignment approach for SQL Server 2016 compatibility. + ([#4149](https://github.com/dotnet/SqlClient/issues/4149), + [#4176](https://github.com/dotnet/SqlClient/pull/4176), + [#4182](https://github.com/dotnet/SqlClient/pull/4182)) + +- Fixed `SqlDataReader.GetFieldType()` and `GetProviderSpecificFieldType()` returning `typeof(byte[])` instead of `typeof(SqlVector)` for vector float32 columns. The methods now follow the same type-determination logic as `GetValue()`. + ([#4104](https://github.com/dotnet/SqlClient/issues/4104), + [#4105](https://github.com/dotnet/SqlClient/pull/4105), + [#4152](https://github.com/dotnet/SqlClient/pull/4152)) + +- Added missing `System.Data.Common` (v4.3.0) NuGet package dependency for .NET Framework consumers. The inbox `System.Data.Common` assembly on .NET Framework predates APIs such as `IDbColumnSchemaGenerator`; without the explicit NuGet dependency, consumers encountered `CS0012` compilation errors when using these types through `Microsoft.Data.SqlClient`. + ([#4063](https://github.com/dotnet/SqlClient/pull/4063), + [#4074](https://github.com/dotnet/SqlClient/pull/4074)) + +### Changed + +- Enabled the User Agent TDS feature extension unconditionally. The `Switch.Microsoft.Data.SqlClient.EnableUserAgent` AppContext switch has been removed; the driver now always sends User Agent information during login. + ([#4124](https://github.com/dotnet/SqlClient/pull/4124), + [#4154](https://github.com/dotnet/SqlClient/pull/4154)) + +- Added type forwards from the core `Microsoft.Data.SqlClient` assembly to public types that were moved to the `Microsoft.Data.SqlClient.Extensions.Abstractions` package: `SqlAuthenticationMethod`, `SqlAuthenticationParameters`, `SqlAuthenticationProvider`, `SqlAuthenticationProviderException`, and `SqlAuthenticationToken`. This ensures binary compatibility for assemblies compiled against earlier versions of `Microsoft.Data.SqlClient` where these types lived in the core assembly. + ([#4067](https://github.com/dotnet/SqlClient/pull/4067), + [#4117](https://github.com/dotnet/SqlClient/pull/4117)) + +- Fixed API documentation include paths and duplicate doc snippets. + ([#4084](https://github.com/dotnet/SqlClient/pull/4084), + [#4086](https://github.com/dotnet/SqlClient/pull/4086), + [#4107](https://github.com/dotnet/SqlClient/pull/4107), + [#4161](https://github.com/dotnet/SqlClient/pull/4161)) + +## Contributors + +We thank the following public contributors. Their efforts toward this project are very much appreciated. + +- [edwardneal](https://github.com/edwardneal) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64, Windows ARM64) +- .NET 8.0+ (Windows x86, Windows x64, Windows ARM, Windows ARM64, Linux, macOS) + +### Dependencies + +#### .NET 9.0 + +- Microsoft.Bcl.Cryptography 9.0.13 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.13 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.13 +- System.Security.Cryptography.Pkcs 9.0.13 + +#### .NET 8.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET Standard 2.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 + +#### .NET Framework 4.6.2+ + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- System.Buffers 4.6.1 +- System.Data.Common 4.3.0 +- System.Diagnostics.DiagnosticSource 10.0.3 +- System.Memory 4.6.3 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 +- System.ValueTuple 4.6.2 diff --git a/release-notes/7.0/README.md b/release-notes/7.0/README.md index 70cfdc3835..5b37ec17d8 100644 --- a/release-notes/7.0/README.md +++ b/release-notes/7.0/README.md @@ -4,9 +4,9 @@ The following Microsoft.Data.SqlClient 7.0 releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2026-04-23 | 7.0.1 | [Release Notes](7.0.1.md) | | 2026-03-17 | 7.0.0 | [Release Notes](7.0.0.md) | | 2026-03-05 | 7.0.0-preview4.26064.3 | [Release Notes](7.0.0-preview4.md) | | 2025-12-08 | 7.0.0-preview3.25342.7 | [Release Notes](7.0.0-preview3.md) | | 2025-10-16 | 7.0.0-preview2.25289.6 | [Release Notes](7.0.0-preview2.md) | | 2025-09-12 | 7.0.0-preview1.25257.1 | [Release Notes](7.0.0-preview1.md) | - From 62d96adb8652b6234c1a5e8acf3361c2e5dec464 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Fri, 24 Apr 2026 18:49:12 -0500 Subject: [PATCH 068/298] `build2.proj` All the Things (#4204) * Adding steps/job for OneBranch pipeline builds to utilize build2.proj. * Default ThisAssembly namespace to $\(AssemblyName\) * Migrate Microsoft.SqlServer.Server to build2.proj norms * Wire official pipeline to use build2.proj for SqlSever * :robot: Wire SqlServer to CI/PR pipeline. * Migrate Microsoft.Data.SqlClient.Logging to build2.proj norms * Wire official pipeline to use build2.proj for Logging * Migrate Microsoft.Data.SqlClient.Extensions.Abstractions to build2.proj norms * Wire official pipeline to use build2.proj for Abstractions * Migrate Microsoft.Data.SqlClient.Extensions.Azure to build2.proj norms * Wire official pipeline to use build2.proj for Azure * Move AKV Provider to root folder (since it is a package we release) * Migrate Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider to build2.proj norms * Wire official pipeline to use build2.proj for AkvProvider * Migrate CI/PR pipeline AKV Provider build steps to directly call the csproj, just like all the other projects. * Mds => SqlClient Migrate the SqlClient csproj files to the same pattern as the other projects. * Wire official pipeline to use build2.proj for SqlClient * Wire CI/PR pipelines to use build2.proj for SqlClient * Delete obsolete pipeline files and build.proj * Update documentation in build2.proj * Remove global versions.props and move build number coalescing to Directory.Build.props * Remove tool restore from ref project * Add utility targets * BUILDGUIDE update and new TESTGUIDE * Round one of code review changes * build2.proj => build.proj * Nobody cares how pretty I make the commits, so I might as well stop caring too. * Oh look something else is broken * Update instructions --- .github/copilot-instructions.md | 11 +- .../ado-pipelines.instructions.md | 27 +- .../onebranch-pipeline-design.instructions.md | 16 +- .github/prompts/ado-work-item-agent.prompt.md | 6 +- .github/prompts/fix-bug.prompt.md | 6 +- .github/prompts/implement-feature.prompt.md | 6 +- .github/prompts/release-notes.prompt.md | 2 +- .../prompts/update-build-pipelines.prompt.md | 28 +- BUILDGUIDE.md | 425 +++--- Directory.Packages.props | 4 +- TESTGUIDE.md | 311 +++++ build.proj | 1160 ++++++++++++++--- build2.proj | 586 --------- .../templates/jobs/ci-build-nugets-job.yml | 19 +- .../build-and-run-tests-netcore-step.yml | 8 +- .../steps/build-and-run-tests-netfx-step.yml | 8 +- .../templates/steps/ci-project-build-step.yml | 27 +- .../templates/steps/run-all-tests-step.yml | 90 +- eng/pipelines/dotnet-sqlclient-ci-core.yml | 15 + .../jobs/pack-sqlserver-package-ci-job.yml | 113 ++ .../jobs/test-azure-package-ci-job.yml | 2 +- .../libraries/ci-build-variables.yml | 16 +- .../onebranch/jobs/build-buildproj-job.yml | 212 +++ .../jobs/build-signed-csproj-package-job.yml | 216 --- .../build-signed-sqlclient-package-job.yml | 180 --- .../onebranch/jobs/publish-symbols-job.yml | 25 +- .../jobs/validate-signed-package-job.yml | 51 +- .../onebranch/stages/build-stages.yml | 143 +- .../stages/publish-symbols-stage.yml | 25 +- .../onebranch/steps/build-buildproj-step.yml | 64 + .../onebranch/steps/build-csproj-step.yml | 43 - .../onebranch/steps/build-sqlclient-step.yml | 41 - .../onebranch/steps/pack-buildproj-step.yml | 84 ++ .../onebranch/steps/pack-csproj-step.yml | 35 - .../onebranch/steps/pack-sqlclient-step.yml | 42 - ...ml => roslyn-analyzers-buildproj-step.yml} | 34 +- .../steps/roslyn-analyzers-csproj-step.yml | 39 - .../onebranch/variables/common-variables.yml | 14 +- .../sqlclient-pr-package-ref-pipeline.yml | 1 - .../sqlclient-pr-project-ref-pipeline.yml | 1 - .../build-sqlserver-package-ci-stage.yml | 83 ++ .../stages/verify-nuget-packages-ci-stage.yml | 11 +- src/Directory.Build.props | 68 +- .../src}/.editorconfig | 0 .../src}/AzureSqlKeyCryptographer.cs | 0 .../src}/Constants.cs | 0 .../src}/LocalCache.cs | 0 ...waysEncrypted.AzureKeyVaultProvider.csproj | 68 +- .../src}/PackageReadme.md | 0 ...qlColumnEncryptionAzureKeyVaultProvider.cs | 0 .../src}/Strings.Designer.cs | 0 .../src}/Strings.resx | 0 .../src}/Utils.cs | 0 .../src/Versions.props | 86 ++ .../Abstractions/src/Abstractions.csproj | 118 +- .../src/AbstractionsVersions.props | 67 - .../Abstractions/src/Versions.props | 87 ++ .../test/Abstractions.Test.csproj | 38 +- .../Azure/src/Azure.csproj | 132 +- .../Azure/src/AzureVersions.props | 66 - .../Azure/src/Versions.props | 86 ++ .../Azure/test/Azure.Test.csproj | 24 +- .../Logging/src/Logging.csproj | 101 +- .../Logging/src/LoggingVersions.props | 67 - .../Logging/src/Versions.props | 87 ++ src/Microsoft.Data.SqlClient.slnx | 5 +- .../{MdsVersions.props => Versions.props} | 38 +- .../AkvProviderVersions.props | 66 - .../Microsoft.Data.SqlClient.csproj | 22 +- .../ref/Microsoft.Data.SqlClient.csproj | 14 +- .../src/Microsoft.Data.SqlClient.csproj | 118 +- .../src/Microsoft/Data/SqlClient/UserAgent.cs | 36 +- ...soft.Data.SqlClient.FunctionalTests.csproj | 5 - ...icrosoft.Data.SqlClient.ManualTests.csproj | 8 +- .../StressTests/Directory.Packages.props | 6 - .../Microsoft.Data.SqlClient.UnitTests.csproj | 4 - .../tests/UnitTests/UserAgentTests.cs | 30 +- src/Microsoft.SqlServer.Server/.editorconfig | 8 - ...ize.cs => IBinarySerialize.netstandard.cs} | 4 + ....cs => InvalidUdtException.netstandard.cs} | 4 + .../Microsoft.SqlServer.Server.csproj | 72 +- ...te.cs => SqlFacetAttribute.netstandard.cs} | 4 + ...cs => SqlFunctionAttribute.netstandard.cs} | 4 + ...e.cs => SqlMethodAttribute.netstandard.cs} | 4 + ...rDefinedAggregateAttribute.netstandard.cs} | 4 + ...qlUserDefinedTypeAttribute.netstandard.cs} | 4 + ...Helper.cs => StringsHelper.netstandard.cs} | 6 +- ...{TypeForwards.cs => TypeForwards.netfx.cs} | 5 +- src/Microsoft.SqlServer.Server/Versions.props | 87 ++ tools/props/Versions.props | 39 - tools/targets/GenerateThisAssemblyCs.targets | 4 +- 91 files changed, 3210 insertions(+), 2716 deletions(-) create mode 100644 TESTGUIDE.md delete mode 100644 build2.proj create mode 100644 eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml create mode 100644 eng/pipelines/onebranch/jobs/build-buildproj-job.yml delete mode 100644 eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml delete mode 100644 eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml create mode 100644 eng/pipelines/onebranch/steps/build-buildproj-step.yml delete mode 100644 eng/pipelines/onebranch/steps/build-csproj-step.yml delete mode 100644 eng/pipelines/onebranch/steps/build-sqlclient-step.yml create mode 100644 eng/pipelines/onebranch/steps/pack-buildproj-step.yml delete mode 100644 eng/pipelines/onebranch/steps/pack-csproj-step.yml delete mode 100644 eng/pipelines/onebranch/steps/pack-sqlclient-step.yml rename eng/pipelines/onebranch/steps/{roslyn-analyzers-sqlclient-step.yml => roslyn-analyzers-buildproj-step.yml} (51%) delete mode 100644 eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml create mode 100644 eng/pipelines/stages/build-sqlserver-package-ci-stage.yml rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/.editorconfig (100%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/AzureSqlKeyCryptographer.cs (100%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/Constants.cs (100%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/LocalCache.cs (100%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj (66%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/PackageReadme.md (100%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/SqlColumnEncryptionAzureKeyVaultProvider.cs (100%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/Strings.Designer.cs (100%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/Strings.resx (100%) rename src/{Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider => Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src}/Utils.cs (100%) create mode 100644 src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props delete mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props delete mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props delete mode 100644 src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props create mode 100644 src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props rename src/Microsoft.Data.SqlClient/{MdsVersions.props => Versions.props} (65%) delete mode 100644 src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props delete mode 100644 src/Microsoft.SqlServer.Server/.editorconfig rename src/Microsoft.SqlServer.Server/{IBinarySerialize.cs => IBinarySerialize.netstandard.cs} (97%) rename src/Microsoft.SqlServer.Server/{InvalidUdtException.cs => InvalidUdtException.netstandard.cs} (98%) rename src/Microsoft.SqlServer.Server/{SqlFacetAttribute.cs => SqlFacetAttribute.netstandard.cs} (98%) rename src/Microsoft.SqlServer.Server/{SqlFunctionAttribute.cs => SqlFunctionAttribute.netstandard.cs} (99%) rename src/Microsoft.SqlServer.Server/{SqlMethodAttribute.cs => SqlMethodAttribute.netstandard.cs} (98%) rename src/Microsoft.SqlServer.Server/{SqlUserDefinedAggregateAttribute.cs => SqlUserDefinedAggregateAttribute.netstandard.cs} (99%) rename src/Microsoft.SqlServer.Server/{SqlUserDefinedTypeAttribute.cs => SqlUserDefinedTypeAttribute.netstandard.cs} (99%) rename src/Microsoft.SqlServer.Server/{StringsHelper.cs => StringsHelper.netstandard.cs} (99%) rename src/Microsoft.SqlServer.Server/{TypeForwards.cs => TypeForwards.netfx.cs} (97%) create mode 100644 src/Microsoft.SqlServer.Server/Versions.props delete mode 100644 tools/props/Versions.props diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4c80da66ff..0c78f461cb 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -11,17 +11,18 @@ ## 📚 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 builds from a **single unified project** at `src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj` that multi-targets `net462`, `net8.0`, and `net9.0`. The legacy `netfx/` and `netcore/` directories are being phased out — only their `ref/` folders (which define the public API surface) remain active. +The project builds from a **single unified project** at `src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj`. It targets `net8.0` and `net9.0` on all supported hosts, and adds `net462` only when building on Windows. The legacy `netfx/` and `netcore/` directories are being phased out — only their `ref/` folders (which define the public API surface) remain active. The project includes: - **Public APIs**: Defined in `netcore/ref/` and `netfx/ref/` directories. - **Implementations**: All source code in `src/Microsoft.Data.SqlClient/src/`. - **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. + - **Unit Tests**: Located in `src/Microsoft.Data.SqlClient/tests/UnitTests/`. + - **Functional Tests**: Located in `src/Microsoft.Data.SqlClient/tests/FunctionalTests/`. + - **Manual Tests**: Located in `src/Microsoft.Data.SqlClient/tests/ManualTests/`. + - **Performance/Stress Tests**: Located in `src/Microsoft.Data.SqlClient/tests/PerformanceTests/` and `src/Microsoft.Data.SqlClient/tests/StressTests/`. - **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. +- **Building**: The repo uses `build.proj` for orchestrated build/test/pack workflows, `src/Microsoft.Data.SqlClient.slnx` for solution-centric development tooling, and Azure DevOps YAML under `eng/pipelines/` plus `eng/pipelines/onebranch/` for CI and official release flows. See `BUILDGUIDE.md` for local build details. - **CI/CD**: ADO Pipelines for CI/CD and Pull request validation are defined in the `eng/` directory, ensuring code quality and automated testing. ## 📦 Products diff --git a/.github/instructions/ado-pipelines.instructions.md b/.github/instructions/ado-pipelines.instructions.md index db053130e0..5dfd35a12c 100644 --- a/.github/instructions/ado-pipelines.instructions.md +++ b/.github/instructions/ado-pipelines.instructions.md @@ -22,14 +22,14 @@ Top-level CI/PR pipeline files: - `dotnet-sqlclient-ci-project-reference-pipeline.yml` — CI with Project references (Release) - `sqlclient-pr-package-ref-pipeline.yml` — PR validation with Package references - `sqlclient-pr-project-ref-pipeline.yml` — PR validation with Project references -- `stress-tests-pipeline.yml` — Stress tests triggered after successful CI-Package runs +- `stress/stress-tests-pipeline.yml` — Stress test pipeline and templates Reusable templates are organized under: - `common/templates/jobs/` — Job templates (`ci-build-nugets-job`, `ci-code-coverage-job`, `ci-run-tests-job`) - `common/templates/stages/` — Stage templates (`ci-run-tests-stage`) - `common/templates/steps/` — Step templates (build, test, config, publish) - `jobs/` — Package-specific CI jobs (pack/test Abstractions, Azure, Logging, stress) -- `stages/` — Package-specific CI stages (build Logging → Abstractions → SqlClient → Azure → verify → stress) +- `stages/` — Package-specific CI stages (generate secrets, build SqlServer/Logging/Abstractions/SqlClient/Azure, verify packages) - `libraries/` — Shared variables (`ci-build-variables.yml`) - `steps/` — SDK install steps @@ -43,10 +43,11 @@ Key parameters: - `testJobTimeout` (required) — test job timeout in minutes - `targetFrameworks` — Windows test TFMs; default `[net462, net8.0, net9.0, net10.0]` - `targetFrameworksUnix` — Unix test TFMs; default `[net8.0, net9.0, net10.0]` +- `netcoreVersionTestUtils` — default runtime for shared test utilities; default `net10.0` - `testSets` — test partitions; default `[1, 2, 3]` - `useManagedSNI` — SNI variants to test; default `[false, true]` - `runAlwaysEncryptedTests` — include AE test set; default `true` -- `enableStressTests` — enable stress test stage; default `false` +- `runLegacySqlTests` — include SQL Server 2016/2017 manual-test legs; default `true` - `debug` — enable debug output; default `false` - `dotnetVerbosity` — MSBuild verbosity; default `normal` @@ -54,14 +55,16 @@ Key parameters: Stages execute in dependency order (Package reference mode requires artifacts from prior stages): 1. `generate_secrets` — Generate test secrets -2. `build_logging_package_stage` — Build Logging package -3. `build_abstractions_package_stage` — Build Abstractions (depends on Logging) -4. `build_sqlclient_package_stage` — Build SqlClient + AKV Provider (depends on Abstractions + Logging) -5. `build_azure_package_stage` — Build Azure extensions (depends on Abstractions + Logging + SqlClient) -6. `verify_nuget_packages_stage` — Verify NuGet package metadata -7. `stress_tests_stage` — Optional stress tests +2. `build_sqlserver_package_stage` — Build `Microsoft.SqlServer.Server` +3. `build_logging_package_stage` — Build Logging package +4. `build_abstractions_package_stage` — Build Abstractions (package mode depends on Logging) +5. `build_sqlclient_package_stage` — Build SqlClient and produce the AKV provider package +6. `build_azure_package_stage` — Build Azure extensions +7. `verify_nuget_packages_stage` — Verify NuGet package metadata 8. `ci_run_tests_stage` — Run MDS and AKV test suites +Stress testing is no longer a stage threaded through `dotnet-sqlclient-ci-core.yml`; it lives under `eng/pipelines/stress/` as a separate pipeline flow. + When adding a new build stage, respect the dependency graph and pass artifact names/versions to downstream stages. ## PR vs CI Pipeline Differences @@ -70,12 +73,12 @@ PR pipelines: - Trigger on PRs to `dev/*`, `feat/*`, `main`; exclude `eng/pipelines/onebranch/*` paths - Use reduced TFM matrix: `[net462, net8.0, net9.0]` (excludes net10.0) - Timeout: 90 minutes -- Package-ref PR disables Always Encrypted tests in Debug config +- Package-ref PR disables Always Encrypted tests in Debug config and also disables legacy SQL Server test legs to keep validation fast CI pipelines: - Trigger on push to `main` (GitHub) and `internal/main` (ADO) with `batch: true` - Scheduled weekday builds (see individual pipeline files for cron times) -- Full TFM matrix including net10.0 +- Full TFM matrix including net10.0 test legs and legacy SQL Server manual-test coverage ## Test Configuration @@ -97,7 +100,7 @@ Flaky test quarantine: SNI testing — `useManagedSNI` controls testing with native SNI (`false`) or managed SNI (`true`) -Test timeout — `--blame-hang-timeout 10m` (configured in `build.proj`); tests exceeding 10 minutes are killed +Test timeout — `--blame-hang-timeout 10m` (configured in `build.proj` and threaded through CI test steps); tests exceeding 10 minutes are killed ## Variables diff --git a/.github/instructions/onebranch-pipeline-design.instructions.md b/.github/instructions/onebranch-pipeline-design.instructions.md index f1520fa0e0..2421674eef 100644 --- a/.github/instructions/onebranch-pipeline-design.instructions.md +++ b/.github/instructions/onebranch-pipeline-design.instructions.md @@ -34,7 +34,7 @@ Defined in `stages/build-stages.yml`. Four build stages plus validation, ordered - **`build_abstractions`** (Stage 2) — Abstractions; `dependsOn: build_independent`; downloads Logging artifact - **`build_dependent`** (Stage 3) — SqlClient and Extensions.Azure in parallel; `dependsOn: build_abstractions`; downloads Abstractions + Logging artifacts - **`build_addons`** (Stage 4) — AKV Provider; `dependsOn: build_dependent`; downloads SqlClient + Abstractions + Logging artifacts -- **`mds_package_validation`** — Validates signed SqlClient package; `dependsOn: build_dependent`; runs in parallel with Stage 4 +- **`sqlclient_package_validation`** — Validates signed SqlClient package; `dependsOn: build_dependent`; runs in parallel with Stage 4 Each build job copies PDB files into `$(JOB_OUTPUT)/symbols/` so they are included in the auto-published pipeline artifact alongside the NuGet packages in `$(JOB_OUTPUT)/packages/`. @@ -47,17 +47,16 @@ Stage conditional rules: ## Job Templates -- **`build-signed-csproj-package-job.yml`** — Generic job for csproj-based packages (Logging, SqlServer.Server, Abstractions, Azure, AKV Provider). Flow: Build DLLs → ESRP DLL signing → NuGet pack (`NoBuild=true`) → ESRP NuGet signing → Copy PDBs to artifact -- **`build-signed-sqlclient-package-job.yml`** — SqlClient-specific job (nuspec-based). Flow: Build all configurations → ESRP DLL signing (main + resource DLLs) → NuGet pack via nuspec → ESRP NuGet signing → Copy PDBs to artifact +- **`build-buildproj-job.yml`** — Shared build.proj-driven package job used for all shipped packages. Flow: build via `build.proj` → optional ESRP DLL signing → pack via `build.proj` → optional ESRP NuGet signing → copy outputs for APIScan/artifacts - **`validate-signed-package-job.yml`** — Validates signed MDS package (signature, strong names, folder structure, target frameworks) - **`publish-nuget-package-job.yml`** — Reusable release job using OneBranch `templateContext.type: releaseJob` with `inputs` for artifact download; pushes via `NuGetCommand@2` - **`publish-symbols-job.yml`** — Reusable symbols job: downloads a build artifact, locates PDBs under `symbols/`, and invokes `publish-symbols-step.yml` -When adding a new csproj-based package: -- Use `build-signed-csproj-package-job.yml` with appropriate `packageName`, `packageFullName`, `versionProperties`, and `downloadArtifacts` -- Add build and pack targets to `build.proj` +When adding a new package to the OneBranch flow: +- Extend `build-buildproj-job.yml` inputs with the new package metadata and dependency artifacts +- Add or update the corresponding build/pack targets in `build.proj` - Add version variables to `variables/common-variables.yml` -- Add artifact name variable to `variables/onebranch-variables.yml` +- Add artifact name variables to `variables/onebranch-variables.yml` ## Symbols Publishing Stage @@ -124,8 +123,7 @@ Variable groups: - Uses ESRP v6 tasks (`EsrpMalwareScanning@6`, `EsrpCodeSigning@6`) with MSI/federated identity authentication - Signing only runs when `isOfficial: true` — non-official pipelines skip ESRP steps -- csproj-based packages: sign DLLs first → pack with `NoBuild=true` → sign NuGet package (ensures NuGet contains signed DLLs) -- SqlClient: sign DLLs (including resource DLLs) → nuspec pack → sign NuGet package +- The shared OneBranch job signs DLLs before packing and signs the resulting NuGet package afterward so the published package contains signed binaries - DLL signing uses keyCode `CP-230012` (Authenticode); NuGet signing uses keyCode `CP-401405` - All ESRP credentials come from variable groups — never hardcode secrets in YAML diff --git a/.github/prompts/ado-work-item-agent.prompt.md b/.github/prompts/ado-work-item-agent.prompt.md index 6eb6319ef6..e94d667bac 100644 --- a/.github/prompts/ado-work-item-agent.prompt.md +++ b/.github/prompts/ado-work-item-agent.prompt.md @@ -23,7 +23,7 @@ Perform the following steps to address the work item. Think step-by-step. - Locate the relevant code in `src/` or `tests/`. ### 2. Planning and Branching -- Propose a descriptive branch name following the pattern `dev/username/branch-name` (e.g., `dev/jdoe/fix-connection-pool`). +- Propose a descriptive branch name following the repository rule `dev/automation/` (e.g., `dev/automation/fix-connection-pool`). - Identify any dependencies or potential breaking changes. ### 3. Implementation @@ -33,13 +33,13 @@ Perform the following steps to address the work item. Think step-by-step. ### 4. Testing and Verification - **Mandatory**: All changes must be tested. -- Create new unit tests in `tests/UnitTests` or functional tests in `tests/FunctionalTests` as appropriate. +- Create new unit tests in `src/Microsoft.Data.SqlClient/tests/UnitTests` or functional tests in `src/Microsoft.Data.SqlClient/tests/FunctionalTests` as appropriate. - Verify that the tests pass. ### 5. Documentation and Finalization - If public APIs are modified, update the documentation in `doc/`. - Provide a clear summary of changes for the Pull Request. -- Suggest an entry for [CHANGELOG.md](CHANGELOG.md) if the change is significant. +- Suggest a release-note entry under `release-notes/` or in the PR description if the change is significant; do not edit `CHANGELOG.md` directly. ## Input **Work Item ID**: ${input:workItemId} diff --git a/.github/prompts/fix-bug.prompt.md b/.github/prompts/fix-bug.prompt.md index 47b143f31f..ade780708d 100644 --- a/.github/prompts/fix-bug.prompt.md +++ b/.github/prompts/fix-bug.prompt.md @@ -25,9 +25,9 @@ Follow this workflow step-by-step: ## 3. Write a Failing Test - Create a test that reproduces the bug BEFORE implementing the fix. - Choose the correct test project: - - `tests/UnitTests/` — for isolated logic tests (no SQL Server needed) - - `tests/FunctionalTests/` — for API behavior tests (no SQL Server needed) - - `tests/ManualTests/` — for integration tests (requires SQL Server) + - `src/Microsoft.Data.SqlClient/tests/UnitTests/` — for isolated logic tests (no SQL Server needed) + - `src/Microsoft.Data.SqlClient/tests/FunctionalTests/` — for API behavior tests (no SQL Server needed) + - `src/Microsoft.Data.SqlClient/tests/ManualTests/` — for integration tests (requires SQL Server) - Follow existing naming conventions: `{ClassName}Tests.cs` with methods named `{MethodName}_{Scenario}_{ExpectedResult}`. - If the bug is platform-specific, add appropriate `[ConditionalFact]` or `[ConditionalTheory]` attributes with `[PlatformSpecific]`. - **Cover both sync and async code paths** if the affected API has both variants (e.g., `Open`/`OpenAsync`, `ExecuteReader`/`ExecuteReaderAsync`). Sync and async paths often have different internal implementations and a bug may manifest in only one. diff --git a/.github/prompts/implement-feature.prompt.md b/.github/prompts/implement-feature.prompt.md index 4beae539fe..4623cb235c 100644 --- a/.github/prompts/implement-feature.prompt.md +++ b/.github/prompts/implement-feature.prompt.md @@ -62,9 +62,9 @@ Before writing code, produce a brief implementation plan covering: 4. Test against multiple SQL Server versions. ## 5. Write Tests -- **Unit tests** in `tests/UnitTests/` for isolated logic. -- **Functional tests** in `tests/FunctionalTests/` for API behavior without SQL Server. -- **Manual tests** in `tests/ManualTests/` for full integration with SQL Server. +- **Unit tests** in `src/Microsoft.Data.SqlClient/tests/UnitTests/` for isolated logic. +- **Functional tests** in `src/Microsoft.Data.SqlClient/tests/FunctionalTests/` for API behavior without SQL Server. +- **Manual tests** in `src/Microsoft.Data.SqlClient/tests/ManualTests/` for full integration with SQL Server. - Cover: - Happy path and edge cases - **Both sync and async code paths** where the feature exposes both variants diff --git a/.github/prompts/release-notes.prompt.md b/.github/prompts/release-notes.prompt.md index b847e3c17a..0370e2b720 100644 --- a/.github/prompts/release-notes.prompt.md +++ b/.github/prompts/release-notes.prompt.md @@ -28,7 +28,7 @@ Each package has its own versioning and dependency sources. Use these to determi | Package | Version Source | Dependency Source | | ------- | -------------- | ----------------- | | `Microsoft.Data.SqlClient` | [tools/props/Versions.props](tools/props/Versions.props) (`MdsVersionDefault`) | [Directory.Packages.props](Directory.Packages.props) and the [project file](src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj) | -| `AzureKeyVaultProvider` | [tools/props/Versions.props](tools/props/Versions.props) (`AkvVersionDefault`) | [AKV project file](src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj) and [Directory.Packages.props](Directory.Packages.props) | +| `AzureKeyVaultProvider` | [tools/props/Versions.props](tools/props/Versions.props) (`AkvVersionDefault`) | [AKV project file](src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj) and [Directory.Packages.props](Directory.Packages.props) | | `Microsoft.SqlServer.Server` | [tools/props/Versions.props](tools/props/Versions.props) (`SqlServerPackageVersion`) | [SqlServer project file](src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj) | | `Extensions.Abstractions` | [AbstractionsVersions.props](src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props) | [Abstractions.csproj](src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj) | | `Extensions.Azure` | [AzureVersions.props](src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props) | [Azure.csproj](src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj) | diff --git a/.github/prompts/update-build-pipelines.prompt.md b/.github/prompts/update-build-pipelines.prompt.md index a4715fa000..63c87ea9c3 100644 --- a/.github/prompts/update-build-pipelines.prompt.md +++ b/.github/prompts/update-build-pipelines.prompt.md @@ -15,35 +15,40 @@ Follow this workflow step-by-step: ## 1. Understand the Pipeline Architecture - Read the relevant pipeline file(s) in `eng/pipelines/`. - Key pipelines: - - `dotnet-sqlclient-ci-core.yml` — Core CI pipeline (reusable by reference pipelines) + - `dotnet-sqlclient-ci-core.yml` — Core CI pipeline template used by CI and PR definitions - `dotnet-sqlclient-ci-project-reference-pipeline.yml` — CI with project references - `dotnet-sqlclient-ci-package-reference-pipeline.yml` — CI with package references - `sqlclient-pr-project-ref-pipeline.yml` — PR validation (project references) - `sqlclient-pr-package-ref-pipeline.yml` — PR validation (package references) - - `dotnet-sqlclient-signing-pipeline.yml` — Package signing - - `akv-official-pipeline.yml` — AKV provider official build (1ES/OneBranch) - - `stress-tests-pipeline.yml` — Stress tests + - `onebranch/sqlclient-official.yml` — official OneBranch build/release pipeline + - `onebranch/sqlclient-non-official.yml` — non-official OneBranch build/release pipeline + - `stress/stress-tests-pipeline.yml` — stress test pipeline - Shared templates live in `eng/pipelines/common/templates/` (jobs/, stages/, steps/). -- Variables are defined in `eng/pipelines/variables/` and `eng/pipelines/libraries/`. +- CI variables are defined in `eng/pipelines/libraries/`; OneBranch variables are defined in `eng/pipelines/onebranch/variables/`. ## 2. Identify What Needs to Change - Determine which pipeline files are affected. - Check if the change impacts shared templates that are reused across multiple pipelines. - Identify if new parameters, variables, or stages need to be added. - Review existing parameters to understand the current configuration surface: - - `targetFrameworks` / `targetFrameworksUnix` — test target frameworks + - `targetFrameworks` / `targetFrameworksUnix` — Windows and Unix test TFMs + - `netcoreVersionTestUtils` — runtime used by shared test utilities - `referenceType` — Project or Package reference - `buildConfiguration` — Debug/Release - `useManagedSNI` — Managed vs Native SNI testing + - `runLegacySqlTests` — whether to include SQL Server 2016/2017 legs ## 3. Implement the Change - Modify YAML files following the existing patterns and indentation style. - When adding new stages, follow the existing stage ordering: - 1. `build_abstractions_package_stage` - 2. `build_sqlclient_package_stage` - 3. `build_azure_package_stage` - 4. `stress_tests_stage` (optional) - 5. `run_tests_stage` + 1. `generate_secrets` + 2. `build_sqlserver_package_stage` + 3. `build_logging_package_stage` + 4. `build_abstractions_package_stage` + 5. `build_sqlclient_package_stage` + 6. `build_azure_package_stage` + 7. `verify_nuget_packages_stage` + 8. `ci_run_tests_stage` - When adding new test parameters, ensure they are wired through to test execution steps. - When modifying shared templates, verify all consuming pipelines still work. @@ -52,6 +57,7 @@ Follow this workflow step-by-step: - Test filters by platform: `nonnetfxtests`, `nonnetcoreapptests`, `nonwindowstests`, `nonlinuxtests`. - SNI testing matrix: both Native (`useManagedSNI=false`) and Managed (`useManagedSNI=true`). - Always Encrypted tests controlled by `runAlwaysEncryptedTests` parameter. +- Stress coverage is maintained under `eng/pipelines/stress/`, not as a stage inside `dotnet-sqlclient-ci-core.yml`. ## 5. Validate - Verify YAML syntax is valid. diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 5da2e2ffd6..7c8e4acb93 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -1,271 +1,318 @@ -# Guidelines for Building Microsoft.Data.SqlClient +# Build Guide for Microsoft.Data.SqlClient and Related Packages -This document provides all the necessary details to build the driver and run tests present in the repository. +This document provides details on how to build the Microsoft.Data.SqlClient package and the other related packages +contained within this repository. ## Prerequisites ### .NET SDK +Projects in this repository require the .NET SDK to be installed in order to build. For the exact version required for +building the current version, see [global.json](global.json). Downloads for .NET SDK can be found at: +https://dotnet.microsoft.com/en-us/download/dotnet -The projects in this repo require the .NET 10.0 SDK to build. Please ensure you -have the latest version of that SDK installed. +The .NET SDK contains support for building for previous versions of .NET, including support for building .NET Framework +on operating systems that do not support .NET Framework. As such, it is not necessary to install any version of the +.NET SDK aside from the version specified in [global.json](global.json). -Tests and tools may require different .NET Runtimes that may be installed -independently. For example, tests targeting .NET 8.0 will need that runtime -installed. +### Miscellaneous -### Visual Studio +**Powershell** is required to run several miscellaneous tasks as part of building and packaging. On Windows systems, +no action is required. On Linux and MacOS systems, the `pwsh` command is required to be in the `$PATH` environment +variable. For specific instructions see: [Install PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell) -This project should be built with Visual Studio 2019+ for the best compatibility. The required set of components are provided in the below file: +The **NuGet** binary is required to package the Microsoft.Data.SqlClient project. For convenience, this can be be done +via the PowerShell script [tools/scripts/downloadLatestNuget.ps1](tools/scripts/downloadLatestNuget.ps1), however, any +`nuget.exe` binary can be used. -- **Visual Studio 2019** with imported components: [VS19Components](/tools/vsconfig/VS19Components.vsconfig) +## Developer Workflow -- **Powershell**: To build SqlClient on Linux, powershell is needed as well. Follow the distro specific instructions at [Install Powershell on Linux](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux?view=powershell-7.4) +Once you've cloned the repository and made your changes to the codebase, it is time to build, test, and optionally +package the project. The `build.proj` file provides convenient targets to accomplish these tasks. -Once the environment is setup properly, execute the desired set of commands below from the _root_ folder to perform the respective operations: +> [!NOTE] +> Although every effort has been made to make building and testing work in your IDE of choice, some quirks in behavior +> may be noticed, possibly severe. All official build and test infrastructure uses the `build.proj` entrypoint, and it +> is recommended that `build.proj` is used for local development, as well. -### Manual Test Prerequisites +> [!TIP] +> `build.proj` was written with the intention of being called from `msbuild`. As such, the following examples below +> use `msbuild`. On systems where `msbuild` is not available, simply replace `msbuild` with `dotnet msbuild` to get the +> same behavior. -Manual Tests require the below setup to run: +> [!TIP] +> This section is not exhaustive of all targets or parameters to `build.proj`. Complete documentation is avilable in +> [`build.proj`](build.proj). -- SQL Server with enabled Shared Memory, TCP and Named Pipes Protocols and access to the Client OS. -- Databases "NORTHWIND" and "UdtTestDb" present in SQL Server, created using SQL scripts [createNorthwindDb.sql](tools/testsql/createNorthwindDb.sql) and [createUdtTestDb.sql](tools/testsql/createUdtTestDb.sql). To setup an Azure Database with "NORTHWIND" tables, use SQL Script: [createNorthwindAzureDb.sql](tools/testsql/createNorthwindAzureDb.sql). -- Make a copy of the configuration file [config.default.json](src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json) and rename it to `config.json`. Update the values in `config.json`: +### Building Projects - |Property|Description|Value| - |------|--------|-------------------| - |TCPConnectionString | Connection String for a TCP enabled SQL Server instance. | `Server={servername};Database={Database_Name};Trusted_Connection=True;`
OR `Data Source={servername};Initial Catalog={Database_Name};Integrated Security=True;`| - |NPConnectionString | Connection String for a Named Pipes enabled SQL Server instance.| `Server=\\{servername}\pipe\sql\query;Database={Database_Name};Trusted_Connection=True;`
OR
`Data Source=np:{servername};Initial Catalog={Database_Name};Integrated Security=True;`| - |TCPConnectionStringHGSVBS | (Optional) Connection String for a TCP enabled SQL Server with Host Guardian Service (HGS) attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = HGS; Enclave Attestation Url = {AttestationURL};`| - |TCPConnectionStringNoneVBS | (Optional) Connection String for a TCP enabled SQL Server with a VBS Enclave and using None Attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = NONE;`| - |TCPConnectionStringAASSGX | (Optional) Connection String for a TCP enabled SQL Server with a SGX Enclave and using Microsoft Azure Attestation (AAS) attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = AAS; Enclave Attestation Url = {AttestationURL};`| - |EnclaveEnabled | Enables tests requiring an enclave-configured server.| - |TracingEnabled | Enables EventSource related tests | - |AADAuthorityURL | (Optional) Identifies the OAuth2 authority resource for `Server` specified in `AADPasswordConnectionString` | `https://login.windows.net/`, where `` is the tenant ID of the Entra ID (Azure AD) tenant | - |AADPasswordConnectionString | (Optional) Connection String for testing Entra ID Password Authentication. | `Data Source={server.database.windows.net}; Initial Catalog={Azure_DB_Name};Authentication=Active Directory Password; User ID={AAD_User}; Password={AAD_User_Password};`| - |AADSecurePrincipalId | (Optional) The Application Id of a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Application ID} | - |AADSecurePrincipalSecret | (Optional) A Secret defined for a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Secret} | - |AzureKeyVaultURL | (Optional) Azure Key Vault Identifier URL | `https://{keyvaultname}.vault.azure.net/` | - |AzureKeyVaultTenantId | (Optional) The Entra ID tenant (directory) Id of the service principal. | _{Tenant ID of Active Directory}_ | - |SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`| - |LocalDbAppName | (Optional) If Local Db Testing is supported, this property configures the name of Local DB App instance available in client environment. Empty string value disables Local Db testing. | Name of Local Db App to connect to.| - |LocalDbSharedInstanceName | (Optional) If LocalDB testing is supported and the instance is shared, this property configures the name of the shared instance of LocalDB to connect to. | Name of shared instance of LocalDB. | - |FileStreamDirectory | (Optional) If File Stream is enabled on SQL Server, pass local directory path to be used for setting up File Stream enabled database. | `D:\\escaped\\absolute\\path\\to\\directory\\` | - |UseManagedSNIOnWindows | (Optional) Enables testing with Managed SNI on Windows| `true` OR `false`| - |DNSCachingConnString | Connection string for a server that supports DNS Caching| - |EnclaveAzureDatabaseConnString | (Optional) Connection string for Azure database with enclaves | - |ManagedIdentitySupported | (Optional) When set to `false` **Managed Identity** related tests won't run. The default value is `true`. | - |IsManagedInstance | (Optional) When set to `true` **TVP** related tests will use non-Azure bsl files to compare test results. This is needed when testing against Azure Managed Instances; otherwise TVP Tests will fail on TestSet 3. The default value is `false`. | - |PowerShellPath | The full path to PowerShell.exe. This is not required if the path is present in the PATH environment variable. | `D:\\escaped\\absolute\\path\\to\\PowerShell.exe` | +From the root of your repository, run `msbuild` against `build.proj` with a build target, following this pattern: -## Test Configuration Environment Variables +```bash +msbuild build.proj -t: [optional_parameters] +``` + +The following build targets can be used to build the following projects. All targets will implicitly build any other +projects they depend on. + +| `` | Description | +|:----------------------------|:--------------------------------------------------------------------------------| +| `Build` | Builds all projects for all platforms | +| `BuildAbstractions` | Builds Microsoft.Data.SqlClient.Extensions.Abstractions | +| `BuildAkvProvider` | Builds Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider | +| `BuildAzure` | Builds Microsoft.Data.SqlClient.Extensions.Azure | +| `BuildLogging` | Builds Microsoft.Data.SqlClient.Internal.Logging | +| `BuildSqlClient` | Builds all variants of Microsoft.Data.SqlClient, for all platforms | +| `BuildSqlClientRef` | Builds the reference assemblies for Microsoft.Data.SqlClient | +| `BuildSqlClientUnsupported` | Builds the "unsupported platform" assemblies for Microsoft.Data.SqlClient | +| `BuildSqlClientUnix` | Builds the Unix-specific implementation binaries of Microsoft.Data.SqlClient | +| `BuildSqlClientWindows` | Builds the Windows-specific implementation binaries of Microsoft.Data.SqlClient | +| `BuildSqlServer` | Builds Microsoft.SqlServer.Server | + +A selection of parameters for build targets in `build.proj` can be found below: + +| `[optional_parameter]` | Allowed Values | Default | Description | +|-----------------------------------|----------------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| `-p:Configuration=` | `Debug`, `Release` | `Debug` | Build configuration | +| `-p:PackageVersion | `major.minor.patch[-prerelease]` | `[blank]` | Version to assign to the target package. Assembly and file versions are derived from this, if it is provided. See Versioning for more details | + +For most projects, build output is placed in `artifacts//Project-/`. `` +is the full name of the package, `` is the build configuration, and `` is the target framework +moniker. SqlClient deviates slightly from this convention, since it consists of multiple projects and the +implementation project is OS-specific. Implementation project output is placed in +`artifacts/Microsoft.Data.SqlClient/Project-//`. The unsupported platform assemblies are placed +in `artifacts/Microsoft.Data.SqlClient.unsupported/Project-/`, and the reference assemblies are +placed in `artifacts/Microsoft.Data.SqlClient.ref/Project-/`. + +#### Examples + +Build all projects: +```bash +msbuild build.proj -t:Build +``` -Environment variables can be used to override test configuration without modifying `config.json` -files directly. This is useful when running tests via the command line, in CI/CD pipelines, -containerized environments, or when switching between configurations frequently. +Build Microsoft.Data.SqlClient in Release configuration: +```bash +msbuild build.proj -t:BuildSqlClient -p:Configuration=Release +``` -### Main Test Suite (`Microsoft.Data.SqlClient.TestUtilities`) +Build v1.2.3 of Microsoft.Data.SqlClient.Extensions.Abstractions: +```bash +msbuild build.proj -t:BuildAbstractions -p:PackageVersion=1.2.3 +``` -These variables apply to unit, functional, and manual tests. The configuration is defined in -[`Config.cs`](src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs). +### Testing Projects -| Variable | Description | Default | -|----------|-------------|---------| -| `TEST_MDS_CONFIG` | Override the path to the `config.json` file used by the test runner. | `config.json` (in the working directory) | -| `MDS_TCPConnectionString` | Override the TCP connection string. Takes precedence over the value in `config.json`. | Value from `config.json` | +This section provides a summary and brief example of how to execute tests for projects in this repository. **For more +information about test procedures, including config file setup, see [TESTGUIDE.md](TESTGUIDE.md).** -Example: +From the root of your repository, run `msbuild` against `build.proj` with a test target, following this pattern: ```bash -export TEST_MDS_CONFIG=~/my-test-config.json -export MDS_TCPConnectionString="Server=myserver;Database=mydb;Trusted_Connection=True;" -dotnet test src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +msbuild build.proj -t: [optional_parameters] ``` -### Performance Tests +| `` | Description | +|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| `Test` | Runs all tests in the repository for all platforms supported by the host OS. _This will take a considerable amount of time and is not recommended_. | +| `TestAzure` | Runs all tests for Microsoft.Data.SqlClient.Extensions.Azure | +| `TestSqlClient` | Runs all tests for Microsoft.Data.SqlClient. | +| `TestSqlClientFunctional` | Runs the "functional" test project for Microsoft.Data.SqlClient. These are a mix of unit and integration tests against live servers. | +| `TestSqlClientManual` | Runs the "manual" test project for Microsoft.Data.SqlClient. These are generally integration tests against live servers. | +| `TestSqlClientUnit` | Runs the unit test project for Microsoft.Data.SqlClient. These are a mix of unit tests and integration tests against simulated servers. | -These variables apply to performance benchmarks. The configuration is defined in -[`Config.cs`](src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs). +> [!TIP] +> Test targets will automatically build the projects they depend on. Therefore, it is not necessary to explicitly build +> (eg) SqlClient before executing the (eg) functional tests target. -| Variable | Description | Default | -|----------|-------------|---------| -| `RUNNER_CONFIG` | Override the path to the `runnerconfig.json` file used by performance benchmarks. | `runnerconfig.json` (in the DLL output directory) | +A selection of parameters for test targets in `build.proj` relevant to common developer workflows can be found below: -See [Run Performance Tests](#run-performance-tests) for more details. +| `[optional_parameter]` | Default Value | Description | +|------------------------|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `-p:Configuration=` | `Debug` | Build configuration. Can be `Debug` or `Release`. | +| `-p:DotnetPath=` | `[blank]` | Path to `dotnet` binary to run the test project. This is useful for running tests against x86 platform on a x86_64 machine. Path must end with `\` or `/`. | +| `-p:TestBlameTimeout=` | `10m` | How long to wait on a test before timing it out. Use `0` to disable hang timeouts. | +| `-p:TestFilters=` | `category!=failing&category!=flaky&category!=interactive` | Filters to use to select the xUnit tests to execute. Use `none` to run all possible tests. | +| `-p:TestFramework=` | `[blank]` | Target framework moniker for the version of .NET to use to execute tests. | +| `-p:TestSet=` | `[blank]` | The `TestSqlClientManual` project is very large and is split into multiple sets that can be executed individually. This parameter allows selecting between test sets: `1`, `2`, `3`, and `AE`. | -### Azure Extension Tests +#### Examples -These variables apply to the Azure extension test suite. The configuration is defined in -[`Config.cs`](src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs). +Run Microsoft.Data.SqlClient unit tests: -| Variable | Description | Default | -|----------|-------------|---------| -| `TEST_MDS_CONFIG` | Override the path to the `config.json` file used by Azure extension tests. | `config.json` | -| `ADO_POOL` | Set to any value to indicate tests are running in an Azure DevOps CI pool. | _(unset)_ | -| `SYSTEM_ACCESSTOKEN` | Azure Pipelines system access token, used for workload identity federation tests. | _(unset)_ | -| `TEST_DEBUG_EMIT` | Set to any value to enable debug output of configuration values during test startup. Sensitive values are Base64-encoded. | _(unset)_ | +```bash +msbuild build.proj -t:TestSqlClientUnit +``` -## MSBuild Reference +Run Microsoft.Data.SqlClient manual test set 2: +```bash +msbuild build.proj -t:TestSqlClientManual -p:TestSet=2 +``` -### Targets +Run Microsoft.Data.SqlClient functional tests against x86 dotnet: +```bash +msbuild build.proj -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' +``` -The following build targets are defined in `build.proj`: +Run all Microsoft.Data.SqlClient.Extensions.Azure unit tests, including interactive, but excluding failing tests: +```bash +msbuild build.proj -t:TestAzure -p:TestFilters=category!=failing +``` -|Target|Description| -|-|-| -|`BuildAbstractions`|Restore and build the Abstractions package.| -|`BuildAkvProvider`|Builds the Azure Key Vault Provider package for all supported platforms.| -|`BuildAllConfigurations`|Default target. Builds the .NET Framework and .NET drivers for all target frameworks and operating systems.| -|`BuildAzure`|Restore and build the Azure package.| -|`BuildLogging`|Restore and build the Logging package.| -|`BuildNetCore`|Builds the .NET driver for all target frameworks.| -|`BuildNetCoreAllOS`|Builds the .NET driver for all target frameworks and operating systems.| -|`BuildNetFx`|Builds the .NET Framework driver for all target frameworks.| -|`BuildSqlClient`|Build the driver for all target frameworks.| -|`Clean`|Cleans all generated files.| -|`PackAbstractions`|Pack the Abstractions NuGet package into `packages/`. Requires `BuildAbstractions` first.| -|`PackAkvProvider`|Pack the Azure Key Vault Provider NuGet package (requires a prior build).| -|`PackAzure`|Pack the Azure NuGet package into `packages/`. Requires `BuildAzure` first.| -|`PackLogging`|Pack the Logging NuGet package into `packages/`. Requires `BuildLogging` first.| -|`Restore`|Restores NuGet packages.| -|`RunTests`|Runs the unit, functional, and manual tests for the .NET Framework and .NET drivers| -|`RunUnitTests`|Runs just the unit tests for the .NET Framework and .NET drivers| -|`RunFunctionalTests`|Runs just the functional tests for the .NET Framework and .NET drivers| -|`RunManualTests`|Runs just the manual tests for the .NET Framework and .NET drivers| +Run Microsoft.Data.SqlClient functional tests against net8.0 runtime: +```bash +msbuild build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 +``` -### Parameters +### Packaging Projects -The following parameters may be defined as MSBuild properties to configure the -build: +Just like bulding and testing the various projects in this repository, packaging the projects into NuGet packages is +also handle by `build.proj`. From the root of your repository, run `msbuild` against `build.proj` with a test target, +following this pattern: -|Name|Supported Values|Default|Description| -|-|-|-|-| -|`Configuration`|`Debug`, `Release`|`Debug`|Sets the release configuration.| -|`OSGroup`|`Unix`, `Windows_NT`, `AnyOS`|typically defaults to the client system's OS, unless using `BuildAllConfigurations` or an `AnyOS` specific target|The operating system to target.| -|`Platform`|`AnyCPU`, `x86`, `x64`, `ARM`, `ARM64`|`AnyCPU`|May only be set when using package reference type or running tests.| -|`TestSet`|`1`, `2`, `3`, `AE`, or any combination thereof|`''`|Build or run a subset of the manual tests. Omit (default) to run all tests.| -|`DotnetPath`|Absolute file path to an installed `dotnet` version.|The system default specified by the path variable|Set to run tests using a specific dotnet version (e.g. C:\net6-win-x86\)| -|`TF`|`net8.0`, `net462`, `net47`, `net471`, `net472`, `net48`, `net481`|`net9.0` in netcore, `net462` in netfx|Sets the target framework when building or running tests. Not applicable when building the drivers.| -|`ResultsDirectory`|An absolute file path|./TestResults relative to current directory|Specifies where to write test results.| +```bash +msbuild build.proj -t: [optional_parameters] +``` -## Example Commands to Run Tests Using MSBuild (Recommended) +| `` | Desription | +|--------------------|-------------------------------------------------------------------------------------| +| `Pack` | Packages all projects in the repository. | +| `PackAbstractions` | Packages the Microsoft.Data.SqlClient.Extensions.Abstractions package | +| `PackAkvProvider` | Packages the Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider package | +| `PackAzure` | Packages the Microsoft.Data.SqlClient.Extensions.Azure package | +| `PackLogging` | Packages the Microsoft.Data.SqlClient.Internal.Logging package | +| `PackSqlClient` | Packages the Microsoft.Data.SqlClient package | +| `PackSqlServer` | Packages the Microsoft.SqlServer.Server package | -Using the default configuration and running all tests: +> [!TIP] +> For convenience, the Pack targets will automatically build the target project and any dependencies. -```bash -msbuild -t:RunTests -``` +A selection of parameters for pack targets in `build.proj` relevant to common developer workflows can be found below: + +| `[optional_parameter]` | Default Value | Allowed Values | Description | +|------------------------------------|---------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `-p:Configuration=` | `Debug` | `Debug`, `Release` | Build configuration. Only applies if project and dependencies are being built. | +| `-p:NugetPath=` | `[blank]` | eg. `C:\my\nuget.exe` | _Only applies to `PackSqlClient`._ Path to `nuget.exe` that to use. If not provided, defaults to `nuget.exe` in the PATH. | +| `-p:PackBuild=` | `true` | `true`, `false` | Whether or not to build the project before packing. If `false`, project must be built using the same parameters. | +| `-p:PackageVersion` | `[blank]` | eg. `1.2.3-dev123` | Version to assign to the package. If `PackBuild` is `true`, the assembly and file versions will be derived from this version. See Versioning for more details. | -Using the Release configuration: +#### Examples +Package Microsoft.Data.SqlClient.Internal.Logging into a NuGet package: ```bash -msbuild -t:RunTests -p:Configuration=Release +msbuild build.proj -t:PackLogging ``` -Running only the unit tests: - +Package Microsoft.Data.SqlClient if `nuget.exe` is not in the `$PATH` environment variable: ```bash -msbuild -t:RunUnitTests +msbuild build.proj -t:PackSqlClient -p:NugetPath="C:\my\nuget.exe" ``` -Using a specific .NET runtime to run tests: - +Package version 1.2.3 of Microsoft.Data.SqlClient.Extensions.Abstractions: ```bash -msbuild -t:RunTests -p:DotnetPath=C:\net8-win-x86\ +msbuild build.proj -t:PackAbstractions -p:PackageVersionAbstractions=1.2.3 ``` -To run tests against a specific version of .NET/.NET Framework, set the `-p:TF` parameter. - +Package version Microsoft.Data.SqlClient.Extensions.Azure without building it beforehand: ```bash -msbuild -t:RunTests -p:TF=net8.0 -msbuild -t:RunTests -p:TF=net462 +msbuild build.proj -t:PackAzure -p:PackBuild=false ``` -## Example Commands to Run Tests using `dotnet` +## Versioning -Under the hood, the MSBuild commands to run tests use `dotnet` commands. But, if you wish to run -them without the overhead of wrapping/unwrapping in MSBuild, you can run them directly. +Versioning can be accomplished by using a mix of different parameters to the `build.proj` targets: +`PackageVersion`, `BuildNumber`, and `BuildSuffix`. Using these in different combinations, can generate +appropriate package, assembly, and file versions for different scenarios. For most developer workflows, it is not +necessary to specify any of these parameters - appropriate versions based on the latest release will be generated +automatically. This section primarily exists to document the various parameters, their effects, and the scenarios they +can be useful for. -To change the processor architecture that runs the test (where possible, ie, x86 on x64), use the -appropriate `dotnet` executable. +`PackageVersion` applies to whatever package is being built. For example, if you are building the +Microsoft.Data.SqlClient package, the appropriate parameter is `-p:PackageVersionSqlClient`. -By default, the tests will be executed on all supported .NET/.NET framework versions. To run on a -specific version, pass the `-f` parameter with the desired version (eg `net9.0`). +Each package has a `Versions.props` file in its root directory that defines a "default" version. This should be defined +as the latest released version of the package. For the table below, we assume this is "1.2.3". -The `--filter` parameter is used to select which tests run. The default `category!=failing& -category!=flaky&category!=interactive` prevents tests that are known to be failing or flaky from -running. To run a specific test, use `FullyQualifiedName=[fully qualified name of the test method]` -as the filter parameter. To run all possible tests, even known failing and flaky ones, simply omit -the filter parameter. Please note, however, that this will still omit tests that cannot run on the -current platform or with the current test configuration (eg, Windows tests on Linux, or SQL DB tests -when Azure Synapse is configured). +| `PackageVersion` | `BuildNumber` | `BuildSuffix` | Package Version | Assembly Version | File Version | Scenario | +|------------------|---------------|---------------|------------------|------------------|---------------|------------------------------------------------------------| +| N/A | N/A | N/A | `1.2.3-dev` | `1.0.0` | `1.2.3.0` | Standard developer scenario | +| `9.8.7` | N/A | N/A | `9.8.7` | `9.0.0` | `9.8.7.0` | Developer is building a specific version of the package | +| `9.8.7-preview1` | N/A | N/A | `9.8.7-preview1` | `9.0.0` | `9.8.7.0` | Developer is buildins a pre-release version of the package | +| N/A | `1234` | N/A | `1.2.3` | `1.0.0` | `1.2.3.1234` | Automated pipelines building GA releases | +| N/A | `1234` | `ci` | `1.2.3-ci1234` | `1.0.0` | `1.2.3.1234` | Automated pipelines building non-prod releases | -### Run Functional Tests +--- -```bash -dotnet test "src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj" \ - -p:Configuration=Release \ - --filter "category!=failing&category!=flaky&category!=interactive" +## Package Mode Builds -``` +The above documentation is the default mode of operation, and is the recommended mode for most developers. However, +`build.proj` supports "package mode" builds. In this mode, instead of projects depending on other projects, they +depend on NuGet packages. This mode is useful for verifying that packages work with each other, especially in automated +build scenarios. For completeness, and debugging of autoamted builds, this section documents behavior of "package mode". -### Run Manual Tests +To switch to "package mode", set the `ReferenceType` parameter in `build.proj` to `Package`. And, optionally, include +one or more of the following parameters: -```bash -dotnet test "src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj" \ - -p:Configuration=Release \ - --filter "category!=failing&category!=flaky&category!=interactive" -``` +* `PackageVersionAbstractions` +* `PackageVersionAkvProvider` +* `PackageVersionAzure` +* `PackageVersionLogging` +* `PackageVersionSqlClient` +* `PackageVersionSqlServer` -### Run Unit Tests -```bash -dotnet test "src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj" \ - -p:Configuration=Release \ - --filter "category!=failing&category!=flaky&category!=interactive" -``` +These parameters pull double duty. In targets where the package is being built, the parameter sets the version of the +package. In targets where the package is being referenced, the parameter sets the version of the package that is being +referenced. -## Testing with Package References +If these parameters are not specified, the latest version, as defined in the `Versions.props` file, will be used. -The MDS driver consists of several components, each of which produces its own -NuGet package. During development, components reference each other via -`` properties by default. This means that building -and testing one component will implicitly build its project referenced -dependencies. +The `nuget.config` for this repository defines a local feed that points to the `packages` directory. This allows +developers that need to test against development packages to drop their development packages into this directory, and +run subsequent `build.proj` targets against them. -Alternatively, the `ReferenceType` build property may be specified with a value -of `Package`. This will change inter-component dependencies to use -`` dependencies, and require that dependent components be -built and packaged before building the depending component. This will generate NuGet -packages in the root packages/ directory, and will be automatically searched by NuGet -(see our root `NuGet.config`). +### Examples -Then, you can specify `Package` references be used, for example: +Build Microsoft.Data.SqlClient version 7.1.1 that references Microsoft.Data.SqlClient.Extensions.Abstractions v1.0.1 +and Microsoft.Data.SqlClient.Internal.Logging v2.2.2. ```bash -dotnet build -t:BuildLogging,PackLogging -dotnet build -t:BuildSqlServer,PackSqlServer -dotnet build -t:BuildAbstractions,PackAbstractions -p:ReferenceType=Package -dotnet build -t:BuildAzure,PackAzure -p:ReferenceType=Package -dotnet build -t:BuildSqlClient -p:ReferenceType=Package -dotnet build -t:GenerateMdsPackage -dotnet build -t:BuildAKVNetCore -p:ReferenceType=Package -dotnet build -t:GenerateAkvPackage +# Build v2.2.2 of Logging and copy to packages +msbuild build.proj -t:PackLogging \ + -p:ReferenceType=Package \ + -p:PackageVersionLogging=2.2.2 +cp artifacts/Microsoft.Data.SqlClient.Internal.Logging/Debug/*.*pkg packages/ + +# Build v1.0.1 of Abstractions that depends on v2.2.2 of Logging +msbuild build.proj -t:PackAbstractions \ + -p:ReferenceType=Package \ + -p:PackageVersionAbstractions=1.0.1 \ + -p:PackageVersionLogging=2.2.2 \ +cp artifacts/Microsoft.Data.SqlClient.Extensions.Abstractions/Package-Debug/*.*pkg packages/ + +# Build SqlClient +msbuild -t:PackSqlClient \ + -p:ReferenceType=Package \ + -p:PackageVersionSqlClient=7.1.1 \ + -p:PackageVersionAbstractions=1.0.1 \ + -p:PackageVersionLogging=2.2.2 +cp artifacts/Microsoft.Data.SqlClient/Package-Debug/*.*pkg packages/ ``` -The above will build the MDS and AKV components, place their NuGet packages into -the `packages/` directory. +Run Microsoft.Data.SqlClient functional tests against the versions build above: +```bash +msbuild build.proj -t:TestSqlClientFunctional \ + -p:ReferenceType=Package \ + -p:PackageVersionSqlClient=7.1.1 \ + -p:PackageVersionAbstractions=1.0.1 \ + -p:PackageVersionLogging=2.2.2 +``` -A non-AnyCPU platform reference can only be used with package reference type. -Otherwise, the specified platform will be replaced with AnyCPU in the build -process. +---- -### Running Tests with Reference Type -Provide property to `dotnet test` commands for testing desired reference type. +Manual test prerequisites and configuration are covered in [TESTGUIDE.md](TESTGUIDE.md#manual-test-prerequisites). -```bash -dotnet test -p:ReferenceType=Project ... -``` +--- ## Using Managed SNI on Windows diff --git a/Directory.Packages.props b/Directory.Packages.props index b1a858bf72..62918f7d65 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -40,10 +40,10 @@ Version="$(AzurePackageVersion)" /> + Version="$(SqlClientPackageVersion)" /> + Version="$(AkvProviderPackageVersion)" />
diff --git a/TESTGUIDE.md b/TESTGUIDE.md new file mode 100644 index 0000000000..fdcb8749ec --- /dev/null +++ b/TESTGUIDE.md @@ -0,0 +1,311 @@ +# Test Guide for Microsoft.Data.SqlClient + +This guide describes how to run the test projects in this repository and how to configure the SQL Server-backed manual +tests. + +For build prerequisites and general `build.proj` usage, see [BUILDGUIDE.md](BUILDGUIDE.md). + +## Test Projects + +The primary test projects for Microsoft.Data.SqlClient are under +[src/Microsoft.Data.SqlClient/tests](src/Microsoft.Data.SqlClient/tests): + +| Project | Path | Purpose | +|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| +| Unit tests | [src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj](src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj) | Unit tests and tests against simulated servers. | +| Functional tests | [src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj](src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj) | Functional tests for public and internal behavior. Some tests use simulated servers or local test infrastructure. | +| Manual tests | [src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj](src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj) | Integration tests that generally require a configured SQL Server or Azure SQL target. | + +These projects target `net8.0`, `net9.0`, and `net10.0` on all platforms. On Windows, they also target `net462`. + +## Recommended Entry Point + +Use [build.proj](build.proj) from the repository root: + +```bash +msbuild build.proj -t: [optional_parameters] +``` + +If `msbuild` is not available, use `dotnet msbuild`: + +```bash +dotnet msbuild build.proj -t: [optional_parameters] +``` + +Test targets build the projects they depend on, so a separate build step is not required for normal test runs. + +## Test Targets + +| Target | Description | +|---------------------------|--------------------------------------------------------------------------------------------------------------------------| +| `Test` | Runs all test targets in the repository. This can take a long time and is not recommended for routine local development. | +| `TestAbstractions` | Runs Microsoft.Data.SqlClient.Extensions.Abstractions tests. | +| `TestAzure` | Runs Microsoft.Data.SqlClient.Extensions.Azure tests. | +| `TestSqlClient` | Runs all Microsoft.Data.SqlClient test projects. | +| `TestSqlClientUnit` | Runs Microsoft.Data.SqlClient unit tests. | +| `TestSqlClientFunctional` | Runs Microsoft.Data.SqlClient functional tests. | +| `TestSqlClientManual` | Runs Microsoft.Data.SqlClient manual tests. | + +## Common Commands + +Run the SqlClient unit tests: + +```bash +msbuild build.proj -t:TestSqlClientUnit +``` + +Run the SqlClient functional tests: + +```bash +msbuild build.proj -t:TestSqlClientFunctional +``` + +Run the SqlClient manual tests: + +```bash +msbuild build.proj -t:TestSqlClientManual +``` + +Run only manual test set 2: + +```bash +msbuild build.proj -t:TestSqlClientManual -p:TestSet=2 +``` + +Run manual test sets 1 and 3: + +```bash +msbuild build.proj -t:TestSqlClientManual -p:TestSet=13 +``` + +Run Always Encrypted manual tests: + +```bash +msbuild build.proj -t:TestSqlClientManual -p:TestSet=AE +``` + +Run a specific target framework: + +```bash +msbuild build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 +``` + +Run functional tests against an x86 `dotnet` installation: + +```bash +msbuild build.proj -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' +``` + +Run all Azure extension tests, including `interactive` tests, while still excluding tests marked `failing` or `flaky`: + +```bash +msbuild build.proj -t:TestAzure -p:TestFilters=category!=failing +``` + +## Test Parameters + +The most commonly used test parameters are: + +| Parameter | Default | Description | +|-----------------------------|-----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| +| `-p:Configuration=` | `Debug` | Build configuration. Use `Debug` or `Release`. | +| `-p:DotnetPath=` | Empty | Path to the folder containing the `dotnet` binary. The path must end with `\` or `/`. | +| `-p:ReferenceType=` | `Project` | For functional and manual SqlClient tests, use `Project` to test the source project or `Package` to test a package reference. | +| `-p:TestBlameTimeout=` | `10m` | Enables hang blame collection with the specified timeout. Use `0` to disable hang timeouts. | +| `-p:TestCodeCoverage=` | `true` | Collects code coverage when set to `true`. | +| `-p:TestFilters=` | `category!=failing&category!=flaky&category!=interactive` | xUnit filter expression. Use `none` to run without the default filter. | +| `-p:TestFramework=` | Empty | Target framework to run. If omitted, all target frameworks supported by the project and host OS are run. | +| `-p:TestResultsFolderPath=` | `test_results` | Directory where test results are written. | +| `-p:TestSet=` | Empty | Selects manual test sets. Supported values include `1`, `2`, `3`, `AE`, and combinations such as `13` or `12AE`. | + +## Test Filters + +`build.proj` passes `TestFilters` to `dotnet test --filter`. By default, tests marked with these categories are excluded: + +| Category | Why it is excluded by default | +|---------------|-------------------------------------------------------------------------------------| +| `failing` | Known failing tests. | +| `flaky` | Intermittently failing tests. | +| `interactive` | Tests that require user interaction or external setup not suitable for normal runs. | + +Examples: + +```bash +# Run a single test by fully-qualified name. +msbuild build.proj -t:TestSqlClientUnit -p:TestFilters=FullyQualifiedName=Namespace.ClassName.MethodName + +# Run only flaky tests while investigating quarantine failures. +msbuild build.proj -t:TestSqlClientManual -p:TestFilters=category=flaky + +# Disable the default filter. +msbuild build.proj -t:TestSqlClientFunctional -p:TestFilters=none +``` + +When passing filter expressions that contain shell-sensitive characters such as `&`, quote or escape the value as +required by your shell. + +## Running Test Projects Directly + +`build.proj` is the recommended entry point because it keeps logging, code coverage, package-reference mode, and +common parameters consistent. For quick local investigation, you can run a test project directly: + +```bash +dotnet test src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj \ + -p:Configuration=Debug \ + --filter "category!=failing&category!=flaky&category!=interactive" +``` + +For manual tests, pass `TestSet` to the test project when needed: + +```bash +dotnet test src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj \ + -p:Configuration=Debug \ + -p:TestSet=2 \ + --filter "category!=failing&category!=flaky&category!=interactive" +``` + +## Manual Test Prerequisites + +Manual tests require SQL Server or Azure SQL resources and a local test configuration file. + +For a basic local SQL Server run, prepare: + +- A SQL Server instance that the test machine can reach. +- Shared Memory, TCP, and Named Pipes protocols enabled when testing local Windows SQL Server scenarios. +- The `NORTHWIND` database created from [tools/testsql/createNorthwindDb.sql](tools/testsql/createNorthwindDb.sql). For + Azure SQL, use [tools/testsql/createNorthwindAzureDb.sql](tools/testsql/createNorthwindAzureDb.sql). +- The `UdtTestDb` database created from [tools/testsql/createUdtTestDb.sql](tools/testsql/createUdtTestDb.sql) if you + want UDT tests to run. +- A login or integrated-security principal with permissions to create and drop the temporary objects used by the tests. + +Feature-specific tests require additional resources. If those resources are not configured, the corresponding +conditional tests are skipped. + +## Manual Test Configuration + +Edit the source configuration file at `src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/ +config.json`. The test utilities project copies that file to the test output directory, where the manual tests load it +by default. + +The template file is: + +[src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json](src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json) + +`config.json` is git-ignored. If it does not exist, the test utilities project copies `config.default.json` to +`config.json` before compile. You can also create it manually: + +```bash +cp src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json \ + src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.json +``` + +Update `config.json` for your environment before running manual tests. The most important values for a basic run are `TCPConnectionString` and `NPConnectionString`. + +```jsonc +{ + "TCPConnectionString": "Data Source=tcp:localhost;Database=Northwind;Integrated Security=true;Encrypt=false;", + "NPConnectionString": "Data Source=np:localhost;Database=Northwind;Integrated Security=true;Encrypt=false;", + "EnclaveEnabled": false, + "TracingEnabled": false, + "SupportsIntegratedSecurity": true +} +``` + +For SQL Server in a Linux container, WSL, or another host where SQL authentication is easier than integrated security, use a TCP connection string like: + +```jsonc +{ + "TCPConnectionString": "Data Source=tcp:127.0.0.1;User Id=sa;Password=;Database=Northwind;Encrypt=false;TrustServerCertificate=true" +} +``` + +You can override the config file path with the `MDS_TEST_CONFIG` environment variable: + +```bash +MDS_TEST_CONFIG=/path/to/config.json msbuild build.proj -t:TestSqlClientManual -p:TestSet=2 +``` + +On PowerShell: + +```powershell +$env:MDS_TEST_CONFIG = "C:\path\to\config.json" +msbuild build.proj -t:TestSqlClientManual -p:TestSet=2 +``` + +## Configuration Properties + +| Property | Description | Example or notes | +|----------------------------------|---------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| +| `TCPConnectionString` | Connection string for a TCP-enabled SQL Server or Azure SQL database. | `Data Source=tcp:localhost;Database=Northwind;Integrated Security=true;Encrypt=false;` | +| `NPConnectionString` | Connection string for a Named Pipes-enabled SQL Server instance. | `Data Source=np:localhost;Database=Northwind;Integrated Security=true;Encrypt=false;` | +| `TCPConnectionStringHGSVBS` | Optional connection string for SQL Server with VBS enclave and HGS attestation. | Include `Attestation Protocol=HGS` and `Enclave Attestation Url`. | +| `TCPConnectionStringNoneVBS` | Optional connection string for SQL Server with VBS enclave and no attestation. | Include `Attestation Protocol=None`. | +| `TCPConnectionStringAASSGX` | Optional connection string for SQL Server with SGX enclave and Microsoft Azure Attestation. | Include `Attestation Protocol=AAS` and `Enclave Attestation Url`. | +| `EnclaveEnabled` | Enables tests that require an enclave-configured server. | `true` or `false`. | +| `TracingEnabled` | Enables tracing-related tests. | `true` or `false`. | +| `AADAuthorityURL` | Optional OAuth authority for `AADPasswordConnectionString`. | `https://login.windows.net/` | +| `AADPasswordConnectionString` | Optional connection string for Microsoft Entra ID password authentication tests. | Uses `Authentication=Active Directory Password`. | +| `AADServicePrincipalId` | Optional application ID for service-principal authentication tests. | Former docs may refer to this as a secure principal ID. | +| `AADServicePrincipalSecret` | Optional application secret for service-principal authentication tests. | Keep this only in local, ignored config files or secure pipeline variables. | +| `AzureKeyVaultURL` | Optional Azure Key Vault URL for Always Encrypted tests. | `https://.vault.azure.net/` | +| `AzureKeyVaultTenantId` | Optional Entra ID tenant ID for Azure Key Vault tests. | Tenant ID GUID. | +| `SupportsIntegratedSecurity` | Whether the user running tests has integrated-security access to the target SQL Server. | `true` or `false`. | +| `LocalDbAppName` | Optional LocalDB instance name. Empty disables LocalDB testing. | `MSSQLLocalDB` or another local instance. | +| `LocalDbSharedInstanceName` | Optional shared LocalDB instance name. | Used only when testing shared LocalDB. | +| `SupportsFileStream` | Whether FileStream tests are supported by the target. | `true` or `false`. | +| `FileStreamDirectory` | Directory used for FileStream database setup. | Use an escaped absolute path in JSON. | +| `UseManagedSNIOnWindows` | Enables Managed SNI on Windows test coverage. | `true` or `false`. | +| `DNSCachingConnString` | Optional connection string for DNS caching tests. | Used with DNS caching server settings. | +| `DNSCachingServerCR` | Optional DNS caching control-ring server. | Feature-specific tests only. | +| `DNSCachingServerTR` | Optional DNS caching tenant-ring server. | Feature-specific tests only. | +| `IsDNSCachingSupportedCR` | Enables DNS caching control-ring tests. | `true` or `false`. | +| `IsDNSCachingSupportedTR` | Enables DNS caching tenant-ring tests. | `true` or `false`. | +| `IsAzureSynapse` | Marks the target as Azure Synapse. | Some SQL Server-specific tests are skipped when `true`. | +| `EnclaveAzureDatabaseConnString` | Optional Azure SQL database connection string for enclave tests. | Feature-specific tests only. | +| `ManagedIdentitySupported` | Whether managed identity tests should run. | Defaults to `true`. Set `false` if unavailable. | +| `UserManagedIdentityClientId` | Optional client ID for user-assigned managed identity tests. | Feature-specific tests only. | +| `KerberosDomainUser` | Optional Kerberos test domain user. | Feature-specific tests only. | +| `KerberosDomainPassword` | Optional Kerberos test domain password. | Keep only in local, ignored config files or secure pipeline variables. | +| `IsManagedInstance` | Marks the target as Azure SQL Managed Instance. | Set `true` for Managed Instance to use non-Azure TVP baseline files in test set 3. | +| `PowerShellPath` | Full path to PowerShell if it is not on `PATH`. | `C:\\escaped\\path\\to\\powershell.exe` | +| `AliasName` | Optional SQL Server alias used by alias-related tests. | Feature-specific tests only. | + +## Manual Test Sets + +The manual test project is split into compile-time sets so large runs can be parallelized. + +| TestSet | Coverage | +|---------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| `1` | Smaller SQL connectivity and command scenarios. | +| `2` | Broad data access coverage, including adapters, bulk copy, retry logic, data reader, schema, DNS caching, and related scenarios. | +| `3` | Additional integration coverage, including LocalDB, pooling, parameters, transactions, JSON, Kerberos, UDT, vector, and other SQL feature tests. | +| `AE` | Always Encrypted tests. | + +If `TestSet` is omitted, all sets are compiled and run. You can combine sets by concatenating values, for example +`-p:TestSet=23` or `-p:TestSet=12AE`. + +## Results and Diagnostics + +Test results are written to `test_results` by default. Override the location with `TestResultsFolderPath`: + +```bash +msbuild build.proj -t:TestSqlClientUnit -p:TestResultsFolderPath=/tmp/sqlclient-test-results +``` + +Hang blame collection is enabled by default with a `10m` timeout. To increase the timeout: + +```bash +msbuild build.proj -t:TestSqlClientManual -p:TestBlameTimeout=30m +``` + +To disable hang blame collection: + +```bash +msbuild build.proj -t:TestSqlClientManual -p:TestBlameTimeout=0 +``` + +Code coverage is enabled by default. To disable it for a faster local run: + +```bash +msbuild build.proj -t:TestSqlClientUnit -p:TestCodeCoverage=false +``` diff --git a/build.proj b/build.proj index f6954667c4..e32caef276 100644 --- a/build.proj +++ b/build.proj @@ -1,270 +1,1022 @@ - + - + + - - false + + + + + -p:BuildNumber=$(BuildNumber) + + + + + -p:BuildSuffix=$(BuildSuffix) + + + Debug - TestResults - - + - - - true - ContinuousIntegrationBuild=$(BuildForRelease);EmbedUntrackedSources=$(BuildForRelease) - + + nuget - - - - - - - + + true + + --no-build + + + BuildSqlClient - - + + + + -p:AbstractionsPackageVersion=$(PackageVersionAbstractions) + - - - $(CommonProperties) + + + + -p:AkvProviderPackageVersion=$(PackageVersionAkvProvider) + - - $(SqlServerProperties);SqlServerPackageVersion=$(SqlServerPackageVersion) - + + + + -p:AzurePackageVersion=$(PackageVersionAbstractions) + - - $(SqlServerProperties);SqlServerAssemblyFileVersion=$(SqlServerAssemblyFileVersion) - - + + + + -p:LoggingPackageVersion=$(PackageVersionLogging) + - - - + + + + -p:SqlClientPackageVersion=$(PackageVersionSqlClient) + - - - + + + + -p:SqlServerPackageVersion=$(PackageVersionSqlServer) + - - - + + Project + + -p:ReferenceType=Package + - - - $(CommonProperties) + + + + -p:SigningKeyPath="$(SigningKeyPath)" + + + + 10m + + --blame-hang + --blame-hang-dump-type full + --blame-hang-timeout $(TestBlameTimeout) + + + + true + + --collect "Code coverage" + --settings "$(RepoRoot)src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/CodeCoverage.runsettings" + + + + category!=failing&category!=flaky&category!=interactive + + + --filter "$(TestFilters)" + + + -f $(TestFramework) - dotnet build -p:AbstractionsPackageVersion= + + $(RepoRoot)test_results - That results in $(AbstractionsPackageVersion) being defined as empty, - and cannot be overridden by the project. + - - $(AbstractionsProperties);AbstractionsPackageVersion=$(AbstractionsPackageVersion) - - - - - $(AbstractionsProperties);AbstractionsAssemblyFileVersion=$(AbstractionsAssemblyFileVersion) - + + -p:TestSet="$(TestSet)" - - + + + + + + + + + + + + + + + - - + + + + + + + + + + + $(RepoRoot)src/Microsoft.Data.SqlClient/ + $(RepoRoot)artifacts/Microsoft.Data.SqlClient/ + + + $(SqlClientSrcRoot)src/Microsoft.Data.SqlClient.csproj + $(SqlClientSrcRoot)ref/Microsoft.Data.SqlClient.csproj + $(SqlClientSrcRoot)notsupported/Microsoft.Data.SqlClient.csproj + + + $(SqlClientSrcRoot)tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj + $(SqlClientSrcRoot)tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj + $(SqlClientSrcRoot)tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj + + + $(RepoRoot)tools/specs/Microsoft.Data.SqlClient.nuspec + $(RepoRoot)tools/GenAPI/Microsoft.DotNet.GenAPI/ + $(GenApiPath)Microsoft.DotNet.GenAPI.csproj + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(GenApiProjectPath)" + -p:Configuration=$(Configuration) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientNotSupportedProjectPath)" + -p:Configuration=$(Configuration) + -p:GenApiPath="@(GenApiArtifactPath->'%(FullPath)')" + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionSqlClientArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + - - + + + + + "$(DotnetPath)dotnet" build $(SqlClientRefProjectPath) + -p:Configuration=$(Configuration) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionSqlClientArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + - - - $(CommonProperties) + + + + + "$(DotnetPath)dotnet" build $(SqlClientProjectPath) + -p:Configuration=$(Configuration) + -p:TargetOs=Unix + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionSqlClientArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + - - - $(LoggingProperties);LoggingPackageVersion=$(LoggingPackageVersion) - - - - - $(LoggingProperties);LoggingAssemblyFileVersion=$(LoggingAssemblyFileVersion) - - + + + + + "$(DotnetPath)dotnet" build $(SqlClientProjectPath) + -p:Configuration=$(Configuration) + -p:TargetOs=Windows_NT + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionSqlClientArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + - - + + + + + + git rev-parse HEAD + + + $([System.Text.RegularExpressions.Regex]::Replace($(GitCommand), "\s+", " ")) + + + + + + + + + + "$(DotnetPath)dotnet" msbuild "$(SqlClientProjectPath)" + -nologo + -verbosity:quiet + -getProperty:SqlClientPackageVersion + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionSqlClientArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(GetSqlClientPackageVersionCommand), "\s+", " ")) + + + + + + + + + <_EvaluatedSqlClientPackageVersion>$([System.Text.RegularExpressions.Regex]::Replace($(_EvaluatedSqlClientPackageVersion), "\s", "")) + $([System.Text.RegularExpressions.Regex]::Replace($(CommitId), "\s", "")) + + "$(NugetPath)" pack "$(SqlClientNuspecPath)" + -Symbols + -SymbolPackageFormat snupkg + -Version "$(_EvaluatedSqlClientPackageVersion)" + -OutputDirectory "$(SqlClientArtifactRoot)/$(ReferenceType)-$(Configuration)" + -properties "COMMITID=$(CommitId);Configuration=$(Configuration);ReferenceType=$(ReferenceType);AbstractionsPackageVersion=$(PackageVersionAbstractions);LoggingPackageVersion=$(PackageVersionLogging)" + + + $([System.Text.RegularExpressions.Regex]::Replace($(NuGetCommand), "\s+", " ")) + + + - - + + + + + + + + + SqlClientFunctional-$(OS) + $(LogFilePrefix)-$(TestFramework) + + + "$(DotnetPath)dotnet" test "$(SqlClientFunctionalTestProjectPath)" + -p:Configuration=$(Configuration) + $(TestBlameArgument) + $(TestCodeCoverageArgument) + $(TestFiltersArgument) + $(TestFrameworkArgument) + --results-directory "$(TestResultsFolderPath)" + --logger:"trx;LogFilePrefix=$(LogFilePrefix)" + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + - - + + + + SqlClientManual-$(OS) + $(LogFilePrefix)-$(TestFramework) + $(LogFilePrefix)-$(TestSet) + + + "$(DotnetPath)dotnet" test "$(SqlClientManualTestProjectPath)" + -p:Configuration=$(Configuration) + $(TestBlameArgument) + $(TestCodeCoverageArgument) + $(TestFiltersArgument) + $(TestFrameworkArgument) + $(TestSetArgument) + --results-directory "$(TestResultsFolderPath)" + --logger:"trx;LogFilePrefix=$(LogFilePrefix)" + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + - + + + + + SqlClientUnit-$(OS) + $(LogFilePrefix)-$(TestFramework) + + + "$(DotnetPath)dotnet" test "$(SqlClientUnitTestProjectPath)" + -p:Configuration=$(Configuration) + $(TestBlameArgument) + $(TestCodeCoverageArgument) + $(TestFiltersArgument) + $(TestFrameworkArgument) + --results-directory "$(TestResultsFolderPath)" + --logger:"trx;LogFilePrefix=$(LogFilePrefix)" + + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + - $(CommonProperties) + $(RepoRoot)src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/ + $(AkvProviderSrcRoot)src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj + - + + + + "$(DotnetPath)dotnet" build "$(AkvProviderProjectPath)" + -p:Configuration=$(Configuration) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionAkvProviderArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + - dotnet build -p:AzurePackageVersion= + + + + + "$(DotnetPath)dotnet" pack "$(AkvProviderProjectpath)" + -p:Configuration=$(Configuration) + $(PackBuildArgument) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionAkvProviderArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + - That results in $(AzurePackageVersion) being defined as empty, and cannot - be overridden by the project. - --> - - $(AzureProperties);AzurePackageVersion=$(AzurePackageVersion) - - - - - $(AzureProperties);AzureAssemblyFileVersion=$(AzureAssemblyFileVersion) - + + + + $(RepoRoot)src/Microsoft.Data.SqlClient.Extensions/Abstractions/ + $(AbstractionsSrcRoot)src/Abstractions.csproj + $(AbstractionsSrcRoot)test/Abstractions.Test.csproj - - + + + + + "$(DotnetPath)dotnet" build "$(AbstractionsProjectPath)" + -p:Configuration=$(Configuration) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionAbstractionsArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + - - + + + + + "$(DotnetPath)dotnet" pack "$(AbstractionsProjectPath)" + -p:Configuration=$(Configuration) + $(PackBuildArgument) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionAbstractionsArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + AbstractionsTests-$(OS) + $(LogFilePrefix)-$(TestFramework) + + + "$(DotnetPath)dotnet" test "$(AbstractionsTestProjectPath)" + -p:Configuration=$(Configuration) + $(TestBlameArgument) + $(TestCodeCoverageArgument) + $(TestFiltersArgument) + $(TestFrameworkArgument) + --results-directory "$(TestResultsFolderPath)" + --logger:"trx;LogFilePrefix=$(LogFilePrefix)" + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + $(RepoRoot)src/Microsoft.Data.SqlClient.Extensions/Azure/ + $(AzureSrcRoot)src/Azure.csproj + $(AzureSrcRoot)test/Azure.Test.csproj + + + + + + + "$(DotnetPath)dotnet" build "$(AzureProjectPath)" + -p:Configuration=$(Configuration) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionAzureArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + - + + + "$(DotnetPath)dotnet" pack "$(AzureProjectPath)" + -p:Configuration=$(Configuration) + $(PackBuildArgument) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionAzureArgument) + + + $(ReferenceTypeArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + - - - - - - - - - - - - - - + + + + AzureTests-$(OS) + $(LogFilePrefix)-$(TestFramework) + + + "$(DotnetPath)dotnet" test "$(AzureTestProjectPath)" + -p:Configuration=$(Configuration) + $(TestBlameArgument) + $(TestCodeCoverageArgument) + $(TestFiltersArgument) + $(TestFrameworkArgument) + --results-directory "$(TestResultsFolderPath)" + --logger:"trx;LogFilePrefix=$(LogFilePrefix)" + + + $(ReferenceTypeArgument) + + $(PackageVersionSqlClientArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + - + + - $(CommonProperties) - - - $(AkvProviderProperties);AkvPackageVersion=$(AkvPackageVersion) - - - - $(AkvProviderProperties);AkvAssemblyFileVersion=$(AkvAssemblyFileVersion) - - - - $(AkvProviderProperties);MdsPackageVersion=$(MdsPackageVersion) - - - - $(AkvProviderProperties);LoggingPackageVersion=$(LoggingPackageVersion) - - - - $(AkvProviderProperties);AbstractionsPackageVersion=$(AbstractionsPackageVersion) - + $(RepoRoot)src/Microsoft.Data.SqlClient.Internal/Logging/src/ + $(LoggingSrcRoot)Logging.csproj - - + + + + + "$(DotnetPath)dotnet" build $(LoggingProjectPath) + -p:Configuration=$(Configuration) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + - - + + + + + "$(DotnetPath)dotnet" pack $(LoggingProjectPath) + -p:Configuration=$(Configuration) + $(PackBuildArgument) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionLoggingArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + - - + + + + $(RepoRoot)src/Microsoft.SqlServer.Server/ + $(SqlServerSrcRoot)Microsoft.SqlServer.Server.csproj + + + + + + + "$(DotnetPath)dotnet" build $(SqlServerProjectPath) + -p:Configuration=$(Configuration) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionSqlServerArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + "$(DotnetPath)dotnet" pack $(SqlServerProjectPath) + -p:Configuration=$(Configuration) + $(PackBuildArgument) + $(SigningKeyPathArgument) + + + $(BuildNumberArgument) + $(BuildSuffixArgument) + $(PackageVersionSqlServerArgument) + + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + diff --git a/build2.proj b/build2.proj deleted file mode 100644 index e2f760af75..0000000000 --- a/build2.proj +++ /dev/null @@ -1,586 +0,0 @@ - - - - - - - - - - - - - -p:BuildNumber=$(BuildNumber) - - - - - - -p:BuildSuffix=$(BuildSuffix) - - - - Debug - - - - - - nuget - - - - true - BuildMds - - - - - -p:AbstractionsPackageVersion=$(PackageVersionAbstractions) - - - - - - -p:LoggingPackageVersion=$(PackageVersionLogging) - - - - - - -p:MdsPackageVersion=$(PackageVersionMds) - - - - Project - - -p:ReferenceType=Package - - - - - - -p:SigningKeyPath="$(SigningKeyPath)" - - - - 10m - - --blame-hang - --blame-hang-dump-type full - --blame-hang-timeout $(TestBlameTimeout) - - - - true - - --collect "Code coverage" - --settings "$(RepoRoot)src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/CodeCoverage.runsettings" - - - - category!=failing&category!=flaky - - - --filter "$(TestFilters)" - - - - - -f $(TestFramework) - - - $(RepoRoot)test_results - - - - -p:TestSet="$(TestSet)" - - - - - - - $(RepoRoot)src/Microsoft.Data.SqlClient/ - $(RepoRoot)artifacts/Microsoft.Data.SqlClient/ - - - $(MdsSrcRoot)src/Microsoft.Data.SqlClient.csproj - $(MdsSrcRoot)ref/Microsoft.Data.SqlClient.csproj - $(MdsSrcRoot)notsupported/Microsoft.Data.SqlClient.csproj - - - $(MdsSrcRoot)tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj - $(MdsSrcRoot)tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj - $(MdsSrcRoot)tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj - - - $(RepoRoot)tools/specs/Microsoft.Data.SqlClient.nuspec - $(RepoRoot)tools/GenAPI/Microsoft.DotNet.GenAPI/ - $(GenApiPath)Microsoft.DotNet.GenAPI.csproj - - - - - - - - - - - "$(DotnetPath)dotnet" build "$(GenApiProjectPath)" - -p:Configuration=$(Configuration) - - - $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - - - - - - - - - - - - "$(DotnetPath)dotnet" build "$(MdsNotSupportedProjectPath)" - -p:Configuration=$(Configuration) - -p:GenApiPath="@(GenApiArtifactPath->'%(FullPath)')" - $(SigningKeyPathArgument) - - - $(BuildNumberArgument) - $(BuildSuffixArgument) - $(PackageVersionMdsArgument) - - - $(ReferenceTypeArgument) - $(PackageVersionAbstractionsArgument) - $(PackageVersionLoggingArgument) - - - $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - - - - - - - - - - "$(DotnetPath)dotnet" build $(MdsRefProjectPath) - -p:Configuration=$(Configuration) - $(SigningKeyPathArgument) - - - $(BuildNumberArgument) - $(BuildSuffixArgument) - $(PackageVersionMdsArgument) - - - $(ReferenceTypeArgument) - $(PackageVersionAbstractionsArgument) - - - $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - - - - - - - - - - "$(DotnetPath)dotnet" build $(MdsProjectPath) - -p:Configuration=$(Configuration) - -p:TargetOs=Unix - $(SigningKeyPathArgument) - - - $(BuildNumberArgument) - $(BuildSuffixArgument) - $(PackageVersionMdsArgument) - - - $(ReferenceTypeArgument) - $(PackageVersionAbstractionsArgument) - $(PackageVersionLoggingArgument) - - - $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - - - - - - - - - - "$(DotnetPath)dotnet" build $(MdsProjectPath) - -p:Configuration=$(Configuration) - -p:TargetOs=Windows_NT - $(SigningKeyPathArgument) - - - $(BuildNumberArgument) - $(BuildSuffixArgument) - $(PackageVersionMdsArgument) - - - $(ReferenceTypeArgument) - $(PackageVersionAbstractionsArgument) - $(PackageVersionLoggingArgument) - - - $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - - - - - - - - - - - - - git rev-parse HEAD - - - $([System.Text.RegularExpressions.Regex]::Replace($(GitCommand), "\s+", " ")) - - - - - - - - - - "$(DotnetPath)dotnet" msbuild "$(MdsProjectPath)" - -nologo - -verbosity:quiet - -getProperty:MdsPackageVersion - - - $(BuildNumberArgument) - $(BuildSuffixArgument) - $(PackageVersionMdsArgument) - - - $([System.Text.RegularExpressions.Regex]::Replace($(GetMdsPackageVersionCommand), "\s+", " ")) - - - - - - - - - <_EvaluatedMdsPackageVersion>$([System.Text.RegularExpressions.Regex]::Replace($(_EvaluatedMdsPackageVersion), "\s", "")) - $([System.Text.RegularExpressions.Regex]::Replace($(CommitId), "\s", "")) - - "$(NugetPath)" pack "$(MdsNuspecPath)" - -Symbols - -SymbolPackageFormat snupkg - -Version "$(_EvaluatedMdsPackageVersion)" - -OutputDirectory "$(MdsArtifactRoot)/$(ReferenceType)-$(Configuration)" - -properties "COMMITID=$(CommitId);Configuration=$(Configuration);ReferenceType=$(ReferenceType);AbstractionsPackageVersion=$(PackageVersionAbstractions);LoggingPackageVersion=$(PackageVersionLogging)" - - - $([System.Text.RegularExpressions.Regex]::Replace($(NuGetCommand), "\s+", " ")) - - - - - - - - - - - - - - MdsFunctional-$(OS) - $(LogFilePrefix)-$(TestFramework) - - - "$(DotnetPath)dotnet" test "$(MdsFunctionalTestProjectPath)" - -p:Configuration=$(Configuration) - $(TestBlameArgument) - $(TestCodeCoverageArgument) - $(TestFiltersArgument) - $(TestFrameworkArgument) - --results-directory "$(TestResultsFolderPath)" - --logger:"trx;LogFilePrefix=$(LogFilePrefix)" - - - $(ReferenceTypeArgument) - $(PackageVersionAbstractionsArgument) - $(PackageVersionLoggingArgument) - $(PackageVersionMdsArgument) - - - $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - - - - - - - - MdsManual-$(OS) - $(LogFilePrefix)-$(TestFramework) - $(LogFilePrefix)-$(TestSet) - - - "$(DotnetPath)dotnet" test "$(MdsManualTestProjectPath)" - -p:Configuration=$(Configuration) - $(TestBlameArgument) - $(TestCodeCoverageArgument) - $(TestFiltersArgument) - $(TestFrameworkArgument) - $(TestSetArgument) - --results-directory "$(TestResultsFolderPath)" - --logger:"trx;LogFilePrefix=$(LogFilePrefix)" - - - $(ReferenceTypeArgument) - $(PackageVersionAbstractionsArgument) - $(PackageVersionLoggingArgument) - $(PackageVersionMdsArgument) - - - $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - - - - - - - - - MdsUnit-$(OS) - $(LogFilePrefix)-$(TestFramework) - - - "$(DotnetPath)dotnet" test "$(MdsUnitTestProjectPath)" - -p:Configuration=$(Configuration) - $(TestBlameArgument) - $(TestCodeCoverageArgument) - $(TestFiltersArgument) - $(TestFrameworkArgument) - --results-directory "$(TestResultsFolderPath)" - --logger:"trx;LogFilePrefix=$(LogFilePrefix)" - - - - $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - - - - diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index 5e17b506c1..afb20ac80e 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -176,20 +176,15 @@ jobs: mdsPackageVersion: ${{ parameters.mdsPackageVersion }} akvPackageVersion: ${{ parameters.akvPackageVersion }} - - task: MSBuild@1 + - task: DotNetCoreCLI@2 displayName: 'Create AKV Provider NuGet Package' inputs: - solution: build.proj - msbuildArchitecture: x64 - platform: '${{ parameters.platform }}' - configuration: '${{ parameters.buildConfiguration }}' - msbuildArguments: >- - -t:PackAkvProvider - -p:ReferenceType=${{ parameters.referenceType }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:AkvPackageVersion=${{ parameters.akvPackageVersion }} - -p:PackageOutputPath=$(packagePath) + command: pack + packagesToPack: src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj + configurationToPack: ${{ parameters.buildConfiguration }} + nobuild: true + packDirectory: $(packagePath) + buildProperties: ReferenceType=${{ parameters.referenceType }};AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }};LoggingPackageVersion=${{ parameters.loggingPackageVersion }};SqlClientPackageVersion=${{ parameters.mdsPackageVersion }};AkvProviderPackageVersion=${{ parameters.akvPackageVersion }};BuildNumber=$(Build.BuildNumber);AssemblyBuildNumber=$(assemblyBuildNumber) - task: PublishPipelineArtifact@1 displayName: 'Publish Pipeline Artifacts' 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 8c1d665e60..2fe5cb5fe8 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 @@ -49,7 +49,7 @@ steps: -p:ReferenceType=Package -p:Configuration=Release -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n --filter "category!=failing&category!=flaky&category!=interactive" @@ -70,7 +70,7 @@ steps: -p:ReferenceType=Package -p:Configuration=Release -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n --filter "category=flaky" @@ -92,7 +92,7 @@ steps: -p:ReferenceType=Package -p:Configuration=Release -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n --filter "category!=failing&category!=flaky&category!=interactive" @@ -114,7 +114,7 @@ steps: -p:ReferenceType=Package -p:Configuration=Release -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n --filter "category=flaky" 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 c13256c614..2783094ce7 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 @@ -49,7 +49,7 @@ steps: -p:ReferenceType=Package -p:Configuration=Release -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n --filter "category!=failing&category!=flaky&category!=interactive" @@ -70,7 +70,7 @@ steps: -p:ReferenceType=Package -p:Configuration=Release -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n --filter "category=flaky" @@ -92,7 +92,7 @@ steps: -p:ReferenceType=Package -p:Configuration=Release -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n --filter "category!=failing&category!=flaky&category!=interactive" @@ -114,7 +114,7 @@ steps: -p:ReferenceType=Package -p:Configuration=Release -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n --filter "category=flaky" 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 d2604e0b3d..bbd2d10744 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -79,40 +79,33 @@ steps: displayName: 'Build Driver' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) inputs: - solution: build2.proj + solution: build.proj msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.buildConfiguration }}' msbuildArguments: - -t:BuildMds + -t:BuildSqlClient -p:ReferenceType=${{ parameters.referenceType }} -p:BuildNumber=${{ parameters.assemblyBuildNumber }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} # Build AKV Provider - ${{ if or(eq(parameters.build, 'AkvProvider'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - - task: MSBuild@1 + - task: DotNetCoreCLI@2 displayName: 'Build AKV Provider' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) inputs: - solution: build.proj - msbuildArchitecture: x64 - platform: '${{ parameters.platform }}' - configuration: '${{ parameters.buildConfiguration }}' - msbuildArguments: >- - -t:BuildAkvProvider + command: build + projects: src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj + arguments: >- + --configuration ${{ parameters.buildConfiguration }} -p:ReferenceType=${{ parameters.referenceType }} - -p:TestEnabled=true - -p:GenerateNuget=false - -p:Configuration=${{ parameters.buildConfiguration }} -p:BuildNumber=${{ parameters.buildNumber }} -p:AssemblyBuildNumber=${{ parameters.assemblyBuildNumber }} - -p:AkvPackageVersion=${{ parameters.akvPackageVersion }} + -p:AkvProviderPackageVersion=${{ parameters.akvPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - verbosityRestore: Detailed - verbosityPack: Detailed + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} 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 68689adf44..e3397e45f0 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -81,19 +81,19 @@ steps: displayName: 'Run Unit Tests ${{parameters.msbuildArchitecture }}' condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: - solution: build2.proj + solution: build.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:TestMdsUnit + -t:TestSqlClientUnit -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:TestResultsFolderPath=TestResults ${{ else }}: # x86 msbuildArguments: >- - -t:TestMdsUnit + -t:TestSqlClientUnit -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} @@ -103,13 +103,13 @@ steps: displayName: 'Run Flaky Unit Tests ${{parameters.msbuildArchitecture }}' condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: - solution: build2.proj + solution: build.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:TestMdsUnit + -t:TestSqlClientUnit -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:TestFilters="category=flaky" @@ -117,7 +117,7 @@ steps: -p:TestCodeCoverage=false ${{ else }}: # x86 msbuildArguments: >- - -t:TestMdsUnit + -t:TestSqlClientUnit -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} @@ -130,27 +130,27 @@ steps: displayName: 'Run Functional Tests ${{parameters.msbuildArchitecture }}' condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: - solution: build2.proj + solution: build.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:TestMdsFunctional + -t:TestSqlClientFunctional -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:TestResultsFolderPath=TestResults ${{ else }}: # x86 msbuildArguments: >- - -t:TestMdsFunctional + -t:TestSqlClientFunctional -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:TestResultsFolderPath=TestResults @@ -158,29 +158,29 @@ steps: condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) displayName: 'Run Flaky Functional Tests ${{parameters.msbuildArchitecture }}' inputs: - solution: build2.proj + solution: build.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:TestMdsFunctional + -t:TestSqlClientFunctional -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults -p:TestCodeCoverage=false ${{ else }}: # x86 msbuildArguments: >- - -t:TestMdsFunctional + -t:TestSqlClientFunctional -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults @@ -191,29 +191,29 @@ steps: condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) displayName: 'Run Manual Tests ${{parameters.msbuildArchitecture }}' inputs: - solution: build2.proj + solution: build.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:TestMdsManual + -t:TestSqlClientManual -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:TestResultsFolderPath=TestResults ${{ else }}: # x86 msbuildArguments: >- - -t:TestMdsManual + -t:TestSqlClientManual -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:TestResultsFolderPath=TestResults retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} @@ -222,31 +222,31 @@ steps: condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) displayName: 'Run Flaky Manual Tests ${{parameters.msbuildArchitecture }}' inputs: - solution: build2.proj + solution: build.proj msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- - -t:TestMdsManual + -t:TestSqlClientManual -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults -p:TestCodeCoverage=false ${{ else }}: # x86 msbuildArguments: >- - -t:TestMdsManual + -t:TestSqlClientManual -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults @@ -260,15 +260,15 @@ steps: condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom - projects: build2.proj + projects: build.proj custom: msbuild arguments: >- - -t:TestMdsUnit + -t:TestSqlClientUnit -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} -p:TestResultsFolderPath=TestResults verbosityRestore: Detailed @@ -279,15 +279,15 @@ steps: condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom - projects: build2.proj + projects: build.proj custom: msbuild arguments: >- - -t:TestMdsUnit + -t:TestSqlClientUnit -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults @@ -301,15 +301,15 @@ steps: condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom - projects: build2.proj + projects: build.proj custom: msbuild arguments: >- - -t:TestMdsFunctional + -t:TestSqlClientFunctional -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} -p:TestResultsFolderPath=TestResults verbosityRestore: Detailed @@ -320,15 +320,15 @@ steps: displayName: 'Run Flaky Functional Tests' inputs: command: custom - projects: build2.proj + projects: build.proj custom: msbuild arguments: >- - -t:TestMdsFunctional + -t:TestSqlClientFunctional -p:TestFramework=${{ parameters.targetFramework }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults @@ -336,22 +336,21 @@ steps: verbosityRestore: Detailed verbosityPack: Detailed continueOnError: true - - task: DotNetCoreCLI@2 displayName: 'Run Manual Tests' condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom - projects: build2.proj + projects: build.proj custom: msbuild arguments: >- - -t:TestMdsManual + -t:TestSqlClientManual -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} -p:TestResultsFolderPath=TestResults verbosityRestore: Detailed @@ -363,16 +362,16 @@ steps: condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) inputs: command: custom - projects: build2.proj + projects: build.proj custom: msbuild arguments: >- - -t:TestMdsManual + -t:TestSqlClientManual -p:TestFramework=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:Configuration=${{ parameters.buildConfiguration }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults @@ -380,3 +379,4 @@ steps: verbosityRestore: Detailed verbosityPack: Detailed continueOnError: true + diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index d39c590efc..c8181aff5a 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -119,12 +119,26 @@ variables: - name: mdsArtifactsName value: MDS.Artifacts + - name: sqlServerArtifactsName + value: SqlServer.Artifacts + stages: # Generate secrets used throughout the pipeline. - template: /eng/pipelines/stages/generate-secrets-ci-stage.yml@self parameters: debug: ${{ parameters.debug }} + # Build the SqlServer package, and publish it to the pipeline artifacts + # under the given artifact name. This runs in parallel with the Secrets + # generation stage since it has no package dependencies. + - template: /eng/pipelines/stages/build-sqlserver-package-ci-stage.yml@self + parameters: + sqlServerArtifactsName: $(sqlServerArtifactsName) + sqlServerPackageVersion: $(sqlServerPackageVersion) + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + # Build the Logging package, and publish it to the pipeline artifacts # under the given artifact name. This runs in parallel with the Secrets # generation stage since it has no package dependencies. @@ -212,6 +226,7 @@ stages: azureArtifactsName: $(azureArtifactsName) loggingArtifactsName: $(loggingArtifactsName) mdsArtifactsName: $(mdsArtifactsName) + sqlServerArtifactsName: $(sqlServerArtifactsName) # Run the MDS and AKV tests. - template: /eng/pipelines/common/templates/stages/ci-run-tests-stage.yml@self diff --git a/eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml b/eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml new file mode 100644 index 0000000000..915bf1e9bd --- /dev/null +++ b/eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml @@ -0,0 +1,113 @@ +################################################################################ +# Licensed to the .NET Foundation under one or more agreements. The .NET +# Foundation licenses this file to you under the MIT license. See the LICENSE +# file in the project root for more information. +################################################################################ + +# This job packs the Microsoft.SqlServer.Server package into NuGet and symbols +# packages and publishes them as a named pipeline artifact. +# +# This template defines a job named 'pack_sqlserver_package_job' that can be +# depended on by downstream jobs. + +parameters: + + # The type of build to produce (Release or Debug) + - name: buildConfiguration + type: string + values: + - Release + - Debug + + # True to emit debug information and steps. + - name: debug + type: boolean + default: false + + # The name of the pipeline artifacts to publish. + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + + # The version to apply to the SqlServer NuGet package and its assemblies. + - name: sqlServerPackageVersion + type: string + + # The list of upstream jobs to depend on. + - name: dependsOn + type: object + default: [] + + # The verbosity level for the dotnet CLI commands. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +jobs: + + - job: pack_sqlserver_package_job + displayName: Pack SqlServer Package + + dependsOn: ${{ parameters.dependsOn }} + + pool: + name: Azure Pipelines + vmImage: ubuntu-latest + + variables: + + # The SqlServer project file to use for all dotnet CLI commands. + - name: project + value: src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj + + # The directory where the NuGet packages will be staged before being + # published as pipeline artifacts. + - name: dotnetPackagesDir + value: $(Build.StagingDirectory)/dotnetPackages + + # Explicitly unset the $PLATFORM environment variable that is set by the + # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. + - name: Platform + value: '' + + # Do the same for $CONFIGURATION since we explicitly set it using our + # 'buildConfiguration' parameter, and we don't want the environment to + # override us. + - name: Configuration + value: '' + + steps: + + # Emit environment variables if debug is enabled. + - ${{ if eq(parameters.debug, true) }}: + - pwsh: 'Get-ChildItem Env: | Sort-Object Name' + displayName: '[Debug] Print Environment Variables' + + # Install the .NET SDK. + - template: /eng/pipelines/steps/install-dotnet.yml@self + parameters: + debug: ${{ parameters.debug }} + + # Create the NuGet packages. + - task: DotNetCoreCLI@2 + displayName: Create NuGet Package + inputs: + command: pack + packagesToPack: $(project) + configurationToPack: ${{ parameters.buildConfiguration }} + packDirectory: $(dotnetPackagesDir) + verbosityToPack: ${{ parameters.dotnetVerbosity }} + buildProperties: SqlServerPackageVersion=${{ parameters.sqlServerPackageVersion }} + + - task: PublishPipelineArtifact@1 + displayName: Publish Pipeline Artifact + inputs: + targetPath: $(dotnetPackagesDir) + artifactName: ${{ parameters.sqlServerArtifactsName }} + publishLocation: pipeline diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 5e5dffe381..0ad4246cb0 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -153,7 +153,7 @@ jobs: --verbosity ${{ parameters.dotnetVerbosity }} -p:ReferenceType=${{ parameters.referenceType }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} # Explicitly unset the $PLATFORM environment variable that is set by the # 'ADO Build properties' Library in the ADO SqlClientDrivers public diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 027501df0f..6d39d085b7 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -28,7 +28,7 @@ variables: # Abstractions library NuGet package version - name: abstractionsPackageVersion - value: 1.0.0.$(Build.BuildNumber)-ci + value: 1.0.0-ci$(Build.BuildNumber) # AKV provider assembly file version - name: akvAssemblyFileVersion @@ -36,7 +36,7 @@ variables: # AKV provider NuGet package version - name: akvPackageVersion - value: 7.0.0.$(Build.BuildNumber)-ci + value: 7.0.0-ci$(Build.BuildNumber) # Azure library assembly file version - name: azureAssemblyFileVersion @@ -44,7 +44,7 @@ variables: # Azure library NuGet package version - name: azurePackageVersion - value: 1.0.0.$(Build.BuildNumber)-ci + value: 1.0.0-ci$(Build.BuildNumber) # Logging library assembly file version - name: loggingAssemblyFileVersion @@ -52,16 +52,22 @@ variables: # Logging library NuGet package version - name: loggingPackageVersion - value: 1.0.0.$(Build.BuildNumber)-ci + value: 1.0.0-ci$(Build.BuildNumber) # MDS library NuGet package version # NOTE: This differs from the other structures! MdsVersions.props will deconstruct a provided # package version and build an assembly version from it. If the build number is included # before the "-" the build number will be appended again, generating an invalid file version. - # @TODO: This is a band-aid to ensure that CI builds until we can centralize versioning. - name: mdsPackageVersion value: 7.0.0-ci$(AssemblyBuildNumber) + # SqlServer library NuGet package version + # NOTE: SqlServer's version props derive file version from the package version by appending the + # assembly build number, so this must remain a three-part SemVer base with a prerelease + # suffix, not a four-part numeric version. + - name: sqlServerPackageVersion + value: 1.0.0-ci$(AssemblyBuildNumber) + # Local NuGet feed directory where downloaded pipeline artifacts are placed. # NuGet.config references this as a local package source for restore. - name: localFeedPath diff --git a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml new file mode 100644 index 0000000000..c700a57701 --- /dev/null +++ b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml @@ -0,0 +1,212 @@ +################################################################################# +# 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. # +################################################################################# + +# Generic job template for building and signing simple build.proj packages. +# +# The job builds DLLs via build.proj, ESRP signs the DLLs, creates NuGet packages from the signed +# DLLs, ESRP signs the NuGet packages, and finally copies all the build output to the job output. + +parameters: + # Common Build Job Properties ############################################ + - name: apiScanDllPath + type: string + + - name: apiScanPdbPath + type: string + + # True to enable ESRP malware scanning and code signing steps, which should not be run on + # non-official pipelines as they access production resources. If true, Signing* parameters must + # be provided. + - name: shouldSignPackage + type: boolean + + # Signing Parameters ----------------------------------------------------- + # @TODO: Signing Parameters Object + + - name: signingAppRegistrationClientId + type: string + + - name: signingAppRegistrationTenantId + type: string + + - name: signingAuthAkvName + type: string + + - name: signingAuthSignCertName + type: string + + - name: signingEsrpClientId + type: string + + - name: signingEsrpConnectedServiceName + type: string + + # Package Properties ##################################################### + + # A list of packages that the package being built with this job depends on. Each entry in the + # object should have the fields: + # * artifactName - Name of the artifact published in a previous job that contains the desired + # version of the dependency. + # * shortName - Short name of the dependency. This name should correspond to the short name used + # when building the package via build.proj + # * version - the version of the dependency that this package depends on + - name: dependencies + type: object + default: [] + + # Assembly file version for APIScan (e.g. 1.0.0.12345). + - name: fileVersion + type: string + + # The full name of the package. This is used in the job name, and to form DLL and PDB filenames + # for APIScan. + - name: packageFullName + type: string + values: + - 'Microsoft.Data.SqlClient' + - 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' + - 'Microsoft.Data.SqlClient.Extensions.Abstractions' + - 'Microsoft.Data.SqlClient.Extensions.Azure' + - 'Microsoft.Data.SqlClient.Internal.Logging' + - 'Microsoft.SqlServer.Server' + + # Short package name used in the job name, display strings, filesystem paths, and as a suffix for + # the default Build and Pack targets if those aren't specified. + - name: packageShortName + type: string + values: + - 'Abstractions' + - 'AkvProvider' + - 'Azure' + - 'Logging' + - 'SqlClient' + - 'SqlServer' + + # The version of the package. + - name: packageVersion + type: string + +jobs: + - job: 'build_package_${{ parameters.packageShortName }}' + displayName: 'Build: ${{ parameters.packageFullName }}' + pool: + type: windows + + variables: + # Placeholder for dependency package arguments. This will be constructed later as it depends + # on scripting to decompose parameters.dependencies. + dependencyPackageArguments: '' + + ob_outputDirectory: '$(JOB_OUTPUT)' + + # APIScan configuration for the package being built by this job. + ob_sdl_apiscan_enabled: true + ob_sdl_apiscan_softwareFolder: ${{ parameters.apiScanDllPath }} + ob_sdl_apiscan_symbolsFolder: ${{ parameters.apiScanPdbPath }} + ob_sdl_apiscan_softwarename: ${{ parameters.packageFullName }} + ob_sdl_apiscan_versionNumber: ${{ parameters.fileVersion }} + + steps: + - template: /eng/pipelines/onebranch/steps/script-output-environment-variables-step.yml@self + + # Constructs the dependency package version parameters to pass to the build steps. Generates + # a string of the form (eg) "-p:PackageVersionAzure=1.2.3" + - ${{ each package in parameters.dependencies }}: + # Append the dependency to the package dependency arguments argument in the form of, eg: + # -p:PackageVersionAzure=1.2.3 + - script: echo "##vso[task.setvariable variable=dependencyPackageArguments]$(dependencyPackageArguments) -p:PackageVersion${{ package.shortName }}=${{ package.version }} + displayName: "Append Package Version Argument - ${{ package.shortName }}" + + # Download the pipeline artifact for this dependency package + - task: DownloadPipelineArtifact@2 + displayName: "Download Artifact - ${{ package.shortName }}" + inputs: + artifactName: ${{ package.artifactName }} + targetPath: $(REPO_ROOT)/packages + + # Install the .NET SDK. + - template: /eng/pipelines/steps/install-dotnet.yml@self + + # Perform Roslyn analysis before building, since this step will clobber build output. + - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml@self + parameters: + dependencyArguments: $(dependencyPackageArguments) + packageShortName: ${{ parameters.packageShortName }} + packageVersion: ${{ parameters.packageVersion }} + + # Build the package, producing DLLs only (no NuGet package yet). + - template: /eng/pipelines/onebranch/steps/build-buildproj-step.yml@self + parameters: + buildConfiguration: Release + dependencyArguments: $(dependencyPackageArguments) + packageShortName: ${{ parameters.packageShortName }} + packageVersion: ${{ parameters.packageVersion }} + + - ${{ if eq(parameters.shouldSignPackage, true) }}: + # ESRP sign the DLLs. + - template: /eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml@self + parameters: + appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + authAkvName: '${{ parameters.signingAuthAkvName }}' + authSignCertName: '${{ parameters.signingAuthSignCertName }}' + esrpClientId: '${{ parameters.signingEsrpClientId }}' + esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + pattern: '${{ parameters.packageFullName }}.dll' + + # Copy signed/unsigned DLLs and PDBs to APIScan folders. + - task: CopyFiles@2 + displayName: Copy DLLs for APIScan + inputs: + SourceFolder: $(BUILD_OUTPUT)/${{ parameters.packageFullName }} + Contents: "**/${{ parameters.packageFullName }}.dll" + TargetFolder: '${{ parameters.apiScanDllPath }}' + # We must preserve the folder structure since our C# projects may produce multiple + # identically named DLLs for different target frameworks (e.g. netstandard2.0, net5.0, + # etc.), and we need to keep those separate for APIScan to work correctly. + flattenFolders: false + + - task: CopyFiles@2 + displayName: Copy PDBs for APIScan + inputs: + SourceFolder: $(BUILD_OUTPUT)/${{ parameters.packageFullName }} + Contents: "**/${{ parameters.packageFullName }}.pdb" + TargetFolder: '${{ parameters.apiScanPdbPath }}' + # We must preserve the folder structure since our C# projects may produce multiple + # identically named DLLs for different target frameworks (e.g. netstandard2.0, net5.0, + # etc.), and we need to keep those separate for APIScan to work correctly. + flattenFolders: false + + # Pack the signed DLLs into NuGet package (NoBuild=true). + - template: /eng/pipelines/onebranch/steps/pack-buildproj-step.yml@self + parameters: + buildConfiguration: Release + dependencyArguments: $(dependencyPackageArguments) + packageFullName: ${{ parameters.packageFullName }} + packageShortName: ${{ parameters.packageShortName }} + packageVersion: ${{ parameters.packageVersion }} + + - ${{ if eq(parameters.shouldSignPackage, true) }}: + # ESRP sign the NuGet package. + - template: /eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml@self + parameters: + appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + authAkvName: '${{ parameters.signingAuthAkvName }}' + authSignCertName: '${{ parameters.signingAuthSignCertName }}' + esrpClientId: '${{ parameters.signingEsrpClientId }}' + esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + searchPath: '$(PACK_OUTPUT)' + searchPattern: '${{ parameters.packageFullName}}.*nupkg' + + # Copy the contents of the build output for this package to the artifacts folder + - task: CopyFiles@2 + displayName: Copy Build Output to Artifacts Folder + inputs: + SourceFolder: '$(BUILD_OUTPUT)/${{ parameters.packageFullName }}' + Contents: '**/*.*' + TargetFolder: '$(JOB_OUTPUT)' + flattenFolders: false diff --git a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml deleted file mode 100644 index 94e6438ef5..0000000000 --- a/eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml +++ /dev/null @@ -1,216 +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. # -################################################################################# - -# Generic job template for building and signing simple csproj-based packages, currently: -# -# - Abstractions -# - Azure -# - Logging -# - SqlServer -# -# The job builds DLLs via build.proj, ESRP signs the DLLs, then creates NuGet packages from the -# signed DLLs, and finally ESRP signs the NuGet packages. - -parameters: - - # Signing Parameters ----------------------------------------------------- - - - name: signingAppRegistrationClientId - type: string - - - name: signingAppRegistrationTenantId - type: string - - - name: signingAuthAkvName - type: string - - - name: signingAuthSignCertName - type: string - - - name: signingEsrpClientId - type: string - - - name: signingEsrpConnectedServiceName - type: string - - # OTHERS +===================================== - - # Short package name used in the job name, display strings, filesystem paths, and as a suffix for - # the default Build and Pack targets if those aren't specified. - - name: packageName - type: string - values: - - Abstractions - - AkvProvider - - Azure - - Logging - - SqlServer - - # The full name of the package. This is used in the job name, and to form DLL and PDB filenames - # for APIScan. - - name: packageFullName - type: string - - # The MSBuild build target in build.proj (e.g. BuildLogging). If not specified, defaults to - # Build. - - name: buildTarget - type: string - default: "" - - # True to enable ESRP malware scanning and code signing steps, which should not be - # run on non-official pipelines as they access production resources. - - name: isOfficial - type: boolean - - # The MSBuild pack target in build.proj (e.g. PackLogging). If not specified, defaults to - # Pack. - - name: packTarget - type: string - default: "" - - # Additional MSBuild -p: arguments for version properties. These may include versions of - # packages this package depends on, or versions for this package itself. - - name: versionProperties - type: string - default: "" - - # Assembly file version for APIScan (e.g. 1.0.0.12345). - - name: assemblyFileVersion - type: string - - # Optional list of pipeline artifacts to download before building. Each entry is an object - # with 'artifactName' (the pipeline artifact name) and 'displayName' (used in the task label). - # This replaces hard-coded packageName conditionals so callers declare their own dependencies. - - name: downloadArtifacts - type: object - default: [] - -jobs: - - job: build_package_${{ parameters.packageName }} - displayName: Build ${{ parameters.packageFullName }} - pool: - type: windows - - variables: - # Inform OneBranch that files put in this directory should be uploaded as artifacts. - ob_outputDirectory: $(JOB_OUTPUT) - - # APIScan configuration for this Extension package - ob_sdl_apiscan_enabled: true - ob_sdl_apiscan_softwareFolder: $(JOB_OUTPUT)/assemblies - ob_sdl_apiscan_symbolsFolder: $(JOB_OUTPUT)/symbols - ob_sdl_apiscan_softwarename: ${{ parameters.packageFullName }} - ob_sdl_apiscan_versionNumber: ${{ parameters.assemblyFileVersion }} - - # If parameters.buildTarget is not provided, default to "Build{parameters.packageName}" - buildTarget: ${{ coalesce(parameters.buildTarget, format('Build{0}', parameters.packageName)) }} - - # If parameters.packTarget is not provided, default to "Pack{parameters.packageName}" - packTarget: ${{ coalesce(parameters.packTarget, format('Pack{0}', parameters.packageName)) }} - - steps: - - template: /eng/pipelines/onebranch/steps/script-output-environment-variables-step.yml@self - - # Download any pipeline artifacts this package depends on. - - ${{ each artifact in parameters.downloadArtifacts }}: - - task: DownloadPipelineArtifact@2 - displayName: Download ${{ artifact.displayName }} - inputs: - artifactName: ${{ artifact.artifactName }} - targetPath: $(JOB_INPUT) - - # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self - - # Perform Roslyn analysis before building, since this step will clobber build output. - - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml@self - parameters: - buildTarget: '${{ variables.buildTarget }}' - msBuildArguments: >- - -p:Configuration=Release - -p:ReferenceType=Package - ${{ parameters.versionProperties }} - - # Build the package, producing DLLs only (no NuGet package yet). - - template: /eng/pipelines/onebranch/steps/build-csproj-step.yml@self - parameters: - buildTarget: '${{ variables.buildTarget }}' - versionProperties: ${{ parameters.versionProperties }} - - - ${{ if eq(parameters.isOfficial, true) }}: - # ESRP sign the DLLs. - - template: /eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml@self - parameters: - appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' - appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' - authAkvName: '${{ parameters.signingAuthAkvName }}' - authSignCertName: '${{ parameters.signingAuthSignCertName }}' - esrpClientId: '${{ parameters.signingEsrpClientId }}' - esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - # Minimatch pattern (multi-line, one pattern per line). - # **/ matches any nested directory (e.g. net8.0/, netstandard2.0/fr/). - # - # Matches: net8.0/.dll - # netstandard2.0/fr/.resources.dll - pattern: | - **/${{ parameters.packageFullName }}.dll - **/${{ parameters.packageFullName }}.resources.dll - - # Copy DLLs to the assemblies/ subdirectory for APIScan. - - task: CopyFiles@2 - displayName: Copy DLLs for APIScan - inputs: - SourceFolder: $(BUILD_OUTPUT)/Package/bin - # Matches: .dll (main assembly) across all TFM subdirs - # .resources.dll (localized satellite assemblies in locale subdirs, if any) - Contents: | - **/${{ parameters.packageFullName }}.dll - **/${{ parameters.packageFullName }}.resources.dll - TargetFolder: $(JOB_OUTPUT)/assemblies - # We must preserve the folder structure since our C# projects may produce multiple - # identically named DLLs for different target frameworks (e.g. netstandard2.0, net5.0, - # etc.), and we need to keep those separate for APIScan to work correctly. - flattenFolders: false - - # Copy PDBs into the output directory so they are included in the published pipeline - # artifact. The symbols publishing stage will download this artifact and publish PDBs - # for this package using the files under symbols/. - - task: CopyFiles@2 - displayName: Copy PDBs for APIScan and Symbols Publishing - inputs: - SourceFolder: $(BUILD_OUTPUT)/Package/bin - # Matches: .pdb across all TFM subdirs. - # Note: Resource DLLs are resource-only satellite assemblies and do not produce PDBs. - Contents: '**/${{ parameters.packageFullName }}.pdb' - TargetFolder: $(JOB_OUTPUT)/symbols - flattenFolders: false - - # Pack the signed DLLs into NuGet package (NoBuild=true). - - template: /eng/pipelines/onebranch/steps/pack-csproj-step.yml@self - parameters: - packTarget: ${{ variables.packTarget }} - versionProperties: ${{ parameters.versionProperties }} - - - ${{ if eq(parameters.isOfficial, true) }}: - # ESRP sign the NuGet package. - - template: /eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml@self - parameters: - appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' - appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' - authAkvName: '${{ parameters.signingAuthAkvName }}' - authSignCertName: '${{ parameters.signingAuthSignCertName }}' - esrpClientId: '${{ parameters.signingEsrpClientId }}' - esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - searchPath: $(JOB_OUTPUT)/packages - # Minimatch pattern with extglob. - # [0-9] matches a single digit, anchoring to the version segment so - # similarly-prefixed package names are not matched. - # ?(s) is an extglob that optionally matches 's'. - # - # Matches: .1.0.0.nupkg - # .1.0.0-preview.1.snupkg - # Excludes: .SomeOther.1.0.0.nupkg - searchPattern: '${{ parameters.packageFullName }}.[0-9]*.?(s)nupkg' diff --git a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml b/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml deleted file mode 100644 index 5bf33f9c09..0000000000 --- a/eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml +++ /dev/null @@ -1,180 +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. # -################################################################################# - -# Job that performs a build of Microsoft.Data.SqlClient using the build2.proj file. - -parameters: - # Whether this build is for an "official" OneBranch pipeline. This is used to enable ESRP signing - # on the artifacts of this job. - - name: isOfficial - 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 - - # Package Parameters - - name: abstractionsArtifactName - type: string - - - name: abstractionsPackageVersion - type: string - - - name: loggingArtifactName - type: string - - - name: loggingPackageVersion - type: string - - - name: sqlClientAssemblyFileVersion - type: string - - - name: sqlClientPackageVersion - type: string - -jobs: - - job: build_package_SqlClient - displayName: 'Build Microsoft.Data.SqlClient' - pool: - type: windows - - variables: - ob_outputDirectory: '$(JOB_OUTPUT)' - ob_sdl_apiscan_softwareFolder: $(JOB_OUTPUT)/assemblies - ob_sdl_apiscan_symbolsFolder: $(JOB_OUTPUT)/symbols - ob_sdl_apiscan_softwarename: 'Microsoft.Data.SqlClient' - ob_sdl_apiscan_versionNumber: ${{ parameters.sqlClientAssemblyFileVersion }} - - steps: - # Dump environment and parameters - - template: /eng/pipelines/onebranch/steps/script-output-environment-variables-step.yml@self - - - powershell: | - $jsonParams = '${{ convertToJson(parameters) }}' -replace '\\', '\\' - $jsonParams | ConvertFrom-Json | Format-List - displayName: 'Output Job Parameters' - - # Download Abstractions and Logging packages from previous stages into /packages/ so they - # are available via the local NuGet feed when restoring. - # @TODO: With the new build2.proj pack targets, this technically is not necessary. - - task: DownloadPipelineArtifact@2 - displayName: Download Microsoft.Data.SqlClient.Extensions.Abstractions Artifact - inputs: - artifactName: '${{ parameters.abstractionsArtifactName }}' - targetPath: '$(JOB_INPUT)' - - - task: DownloadPipelineArtifact@2 - displayName: Download Microsoft.Data.SqlClient.Extensions.Logging Artifact - inputs: - artifactName: '${{ parameters.loggingArtifactName }}' - targetPath: '$(JOB_INPUT)' - - # Install the .NET SDK - - template: /eng/pipelines/steps/install-dotnet.yml@self - - # Perform analysis before building, since this step will clobber build output - - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml@self - parameters: - abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' - loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' - sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' - - # Perform the actual build - - template: /eng/pipelines/onebranch/steps/build-sqlclient-step.yml@self - parameters: - abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' - loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' - sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' - - # Sign the DLLs - - ${{ if parameters.isOfficial }}: - - template: /eng/pipelines/onebranch/steps/esrp-dll-signing-step.yml@self - parameters: - appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' - appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' - authAkvName: '${{ parameters.signingAuthAkvName }}' - authSignCertName: '${{ parameters.signingAuthSignCertName }}' - esrpClientId: '${{ parameters.signingEsrpClientId }}' - esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - # Minimatch pattern (multi-line, one pattern per line). - # **/ matches any nested directory (e.g. net8.0/, net462/fr/). - # - # Matches: net8.0/Microsoft.Data.SqlClient.dll - # net462/fr/Microsoft.Data.SqlClient.resources.dll - # Excludes: net8.0/Microsoft.Data.SqlClient.SNI.dll (no ** path matches) - pattern: | - **/Microsoft.Data.SqlClient.dll - **/Microsoft.Data.SqlClient.resources.dll - - # Copy DLLs to the assemblies/ subdirectory for APIScan. - - task: CopyFiles@2 - displayName: Copy DLLs for APIScan - inputs: - SourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/Package-Release' - # Matches: Microsoft.Data.SqlClient.dll (main assembly) - # Microsoft.Data.SqlClient.resources.dll (localized satellite assemblies in locale subdirs) - # Excludes: Microsoft.Data.SqlClient.SNI*.dll (native SNI — not part of this package) - Contents: | - **/Microsoft.Data.SqlClient.dll - **/Microsoft.Data.SqlClient.resources.dll - TargetFolder: '$(JOB_OUTPUT)/assemblies' - flattenFolders: false - - # Copy PDBs into the output directory so they are included in the published pipeline - # artifact. The symbols publishing stage will download this artifact and publish PDBs - # for this package using the files under symbols/. - - task: CopyFiles@2 - displayName: Copy PDBs for APIScan and Symbols Publishing - inputs: - SourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/Package-Release' - # Matches: Microsoft.Data.SqlClient.pdb (main assembly symbols) - # Excludes: Microsoft.Data.SqlClient.SNI*.pdb (native SNI — symbols published separately) - # Note: Resource DLLs are resource-only satellite assemblies and do not produce PDBs. - Contents: '**/Microsoft.Data.SqlClient.pdb' - TargetFolder: '$(JOB_OUTPUT)/symbols' - flattenFolders: false - - # Package the build output into a NuGet package - - template: /eng/pipelines/onebranch/steps/pack-sqlclient-step.yml - parameters: - abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' - loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' - sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' - - # Sign the NuGet packages - - ${{ if parameters.isOfficial }}: - - template: /eng/pipelines/onebranch/steps/esrp-nuget-signing-step.yml@self - parameters: - appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' - appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' - authAkvName: '${{ parameters.signingAuthAkvName }}' - authSignCertName: '${{ parameters.signingAuthSignCertName }}' - esrpClientId: '${{ parameters.signingEsrpClientId }}' - esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - searchPath: '$(JOB_OUTPUT)/packages' - # Minimatch pattern with extglob. - # [0-9] matches a single digit, anchoring to the version segment so - # names like Microsoft.Data.SqlClient.SNI are not matched. - # ?(s) is an extglob that optionally matches 's'. - # - # Matches: Microsoft.Data.SqlClient.6.1.0.nupkg - # Microsoft.Data.SqlClient.6.1.0.snupkg - # Excludes: Microsoft.Data.SqlClient.SNI.6.0.0.nupkg - searchPattern: 'Microsoft.Data.SqlClient.[0-9]*.?(s)nupkg' diff --git a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml index de2d7e4634..8ee37ad7d7 100644 --- a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml +++ b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml @@ -17,20 +17,16 @@ parameters: - name: artifactName type: string - # Short package name used in the job name (e.g. Logging, SqlServer, SqlClient). - - name: packageName - type: string - # The full NuGet package name (e.g. Microsoft.Data.SqlClient.Internal.Logging). - name: packageFullName type: string - # The package version, used for symbol versioning. - - name: packageVersion + # Short package name used in the job name (e.g. Logging, SqlServer, SqlClient). + - name: packageShortName type: string - # Pattern to use to search for PDB files to upload/publish, relative to the symbols folder. - - name: searchPattern + # The package version, used for symbol versioning. + - name: packageVersion type: string # Symbols Publishing Parameters ------------------------------------------ @@ -51,7 +47,7 @@ parameters: type: string jobs: - - job: publish_symbols_${{ parameters.packageName }} + - job: 'publish_symbols_${{ parameters.packageShortName }}' displayName: 'Publish Symbols: ${{ parameters.packageFullName }}' pool: type: linux @@ -69,10 +65,7 @@ jobs: ob_sdl_binskim_enabled: false # Path where the downloaded artifact will be placed. - artifactPath: $(Pipeline.Workspace)/${{ parameters.artifactName }} - - # Path to the PDB files within the downloaded artifact. - symbolsPath: $(Pipeline.Workspace)/${{ parameters.artifactName }}/symbols + artifactPath: '$(Pipeline.Workspace)/${{ parameters.packageFullName }}' # PublishSymbols@2 runs on the OneBranch host agent (outside the build container) due to 1ES # Pipeline Template credential isolation. On Linux, the host resolves to the Microsoft org by @@ -96,7 +89,7 @@ jobs: displayName: 'Download ${{ parameters.packageFullName }} Artifact' inputs: artifactName: '${{ parameters.artifactName }}' - targetPath: '$(artifactPath)' + targetPath: '${{ variables.artifactPath }}' - template: /eng/pipelines/onebranch/steps/publish-symbols-step.yml@self parameters: @@ -108,7 +101,7 @@ jobs: publishToInternal: true publishToPublic: true publishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' - searchPattern: '${{ parameters.searchPattern }}' - symbolsFolder: '$(symbolsPath)' + searchPattern: '**/${{ parameters.packageFullName }}.pdb' + symbolsFolder: '${{ variables.artifactPath }}' uploadAccount: '${{ parameters.symbolsUploadAccount }}' version: '${{ parameters.packageVersion }}' diff --git a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml index a99b46d45f..afa5aa5918 100644 --- a/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/onebranch/jobs/validate-signed-package-job.yml @@ -49,8 +49,8 @@ jobs: variables: # More settings at https://aka.ms/obpipelines/yaml/jobs # Path within the downloaded artifact where NuGet packages are located. - - name: packagesPath - value: '$(Pipeline.Workspace)\${{ parameters.artifactName }}\packages' + - name: artifactPath + value: '$(Pipeline.Workspace)\${{ parameters.artifactName }}' # Path to the SqlClient NuGet package after installation. This path will only exist once the package # been installed. @@ -86,31 +86,62 @@ jobs: - ${{ if eq(parameters.isOfficial, true) }}: - powershell: | # Propagate parameters to PS variables ####################### - $packagesPath = "${{ variables.packagesPath }}" - echo "packagesPath= $packagesPath" + $artifactPath = "${{ variables.artifactPath }}" + echo "artifactPath= $artifactPath" + + # Discover packages ########################################## + $packageFiles = Get-ChildItem -Path $artifactPath -Recurse -File -Include *.nupkg,*.snupkg + if ($packageFiles.Count -eq 0) + { + Write-Error "No NuGet package files were found under '$artifactPath'." + } # Verify package signatures ################################## echo "> 1. Verify signature of source package(s)" - nuget verify -All $packagesPath\*.nupkg + $packageFiles | Where-Object { $_.Extension -eq ".nupkg" } | ForEach-Object { + nuget verify -All $_.FullName + } echo "> 2. Verify signature of symbols package(s)" - nuget verify -All $packagesPath\*.snupkg + $packageFiles | Where-Object { $_.Extension -eq ".snupkg" } | ForEach-Object { + nuget verify -All $_.FullName + } displayName: "Verify nuget signature" # Install NuGet package to the temporary directory - powershell: | # Propagate pipeline to PS variables ############################# - $packagesPath = "${{ variables.packagesPath }}" - echo "packagesPath= $packagesPath" + $artifactPath = "${{ variables.artifactPath }}" + echo "artifactPath= $artifactPath" + + $expectedPackageVersion = "${{ parameters.expectedPackageVersion }}" + echo "expectedPackageVersion= $expectedPackageVersion" $nugetPackageInstallRoot = "${{ variables.nugetPackageInstallRoot }}" echo "nugetPackageInstallRoot= $nugetPackageInstallRoot" + # Find the SqlClient NuGet package ############################### + Get-ChildItem "$artifactPath" -Recurse + + $packagePaths = @(Get-ChildItem -Path $artifactPath -Recurse -File -Filter "Microsoft.Data.SqlClient.$expectedPackageVersion.nupkg") + if ($packagePaths.Count -eq 0) + { + Write-Error "Unable to find Microsoft.Data.SqlClient.$expectedPackageVersion.nupkg under '$artifactPath'." + } + if ($packagePaths.Count -gt 1) + { + Write-Error "Multiple Microsoft.Data.SqlClient.$expectedPackageVersion.nupkg files were found under '$artifactPath'." + } + + $packageSource = Split-Path -Path $packagePaths[0].FullName -Parent + echo "Found package path: $($packagePaths[0].FullName)" + echo "Using package source: $packageSource" + # Install NuGet Package ########################################## echo "> 1. Installing Microsoft.Data.SqlClient NuGet package..." Install-Package ` -Name "Microsoft.Data.SqlClient" ` - -Source "$packagesPath" ` + -Source "$packageSource" ` -Destination $nugetPackageInstallRoot ` -Force ` -SkipDependencies @@ -118,8 +149,6 @@ jobs: echo "> 2. Listing contents of installed Microsoft.Data.SqlClient NuGet package:" Write-Host $nugetPackageInstallRoot Get-ChildItem $nugetPackageInstallRoot - Write-Host "$packagesPath" - Get-ChildItem "$packagesPath" displayName: "Install NuGet Package" # Find all DLL files in the installed NuGet package, verify each is signed with a strong name diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index 252f63fb0a..096da7e949 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -119,8 +119,12 @@ stages: jobs: - ${{ if or(eq(parameters.buildAkvProvider, true), eq(parameters.buildSqlClient, true)) }}: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + # Build Microsoft.Data.SqlClient.Internal.Logging + - template: /eng/pipelines/onebranch/jobs/build-buildproj-job.yml@self parameters: + apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient.Internal.Logging/dlls' + apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient.Internal.Logging/pdbs' + shouldSignPackage: ${{ parameters.isOfficial }} signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' @@ -128,18 +132,20 @@ stages: signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - isOfficial: ${{ parameters.isOfficial }} - packageName: Logging - packageFullName: Microsoft.Data.SqlClient.Internal.Logging - versionProperties: >- - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:LoggingAssemblyFileVersion=${{ parameters.loggingFileVersion }} - assemblyFileVersion: '${{ parameters.loggingFileVersion }}' + dependencies: [] + fileVersion: '${{ parameters.loggingFileVersion }}' + packageFullName: 'Microsoft.Data.SqlClient.Internal.Logging' + packageShortName: 'Logging' + packageVersion: '${{ parameters.loggingPackageVersion }}' - ${{ if eq(parameters.buildSqlServerServer, true) }}: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + # Build Microsoft.SqlServer.Server + - template: /eng/pipelines/onebranch/jobs/build-buildproj-job.yml@self parameters: + apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.SqlServer.Server/dlls' + apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.SqlServer.Server/pdbs' + shouldSignPackage: ${{ parameters.isOfficial }} signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' @@ -147,13 +153,11 @@ stages: signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - isOfficial: ${{ parameters.isOfficial }} - packageName: SqlServer - packageFullName: Microsoft.SqlServer.Server - versionProperties: >- - -p:SqlServerAssemblyFileVersion=${{ parameters.sqlServerFileVersion }} - -p:SqlServerPackageVersion=${{ parameters.sqlServerPackageVersion }} - assemblyFileVersion: '${{ parameters.sqlServerFileVersion }}' + dependencies: [] + fileVersion: '${{ parameters.sqlServerFileVersion }}' + packageFullName: 'Microsoft.SqlServer.Server' + packageShortName: 'SqlServer' + packageVersion: '${{ parameters.sqlServerPackageVersion }}' # ==================================================================== # Stage 2: Abstractions package (depends on Logging from Stage 1) @@ -166,8 +170,12 @@ stages: dependsOn: build_independent jobs: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + # Build Microsoft.Data.SqlClient.Extensions.Abstractions + - template: /eng/pipelines/onebranch/jobs/build-buildproj-job.yml@self parameters: + apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient.Extensions.Abstractions/dlls' + apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient.Extensions.Abstractions/pdbs' + shouldSignPackage: ${{ parameters.isOfficial }} signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' @@ -175,17 +183,14 @@ stages: signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - isOfficial: ${{ parameters.isOfficial }} - packageName: Abstractions - packageFullName: Microsoft.Data.SqlClient.Extensions.Abstractions - versionProperties: >- - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:AbstractionsAssemblyFileVersion=${{ parameters.abstractionsFileVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - assemblyFileVersion: '${{ parameters.abstractionsFileVersion }}' - downloadArtifacts: + dependencies: - artifactName: '${{ parameters.loggingArtifactsName }}' - displayName: 'Logging Package' + shortName: 'Logging' + version: '${{ parameters.loggingPackageVersion }}' + fileVersion: '${{ parameters.abstractionsFileVersion }}' + packageFullName: 'Microsoft.Data.SqlClient.Extensions.Abstractions' + packageShortName: 'Abstractions' + packageVersion: '${{ parameters.abstractionsPackageVersion }}' # ==================================================================== # Stage 3: Core packages (depend on Abstractions) @@ -198,9 +203,11 @@ stages: dependsOn: build_abstractions jobs: - - template: /eng/pipelines/onebranch/jobs/build-signed-sqlclient-package-job.yml@self + - template: /eng/pipelines/onebranch/jobs/build-buildproj-job.yml@self parameters: - isOfficial: ${{ parameters.isOfficial }} + apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient/dlls' + apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient/pdbs' + shouldSignPackage: ${{ parameters.isOfficial }} signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' @@ -208,15 +215,24 @@ stages: signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - abstractionsArtifactName: '${{ parameters.abstractionsArtifactsName }}' - abstractionsPackageVersion: '${{ parameters.abstractionsPackageVersion }}' - loggingArtifactName: '${{ parameters.loggingArtifactsName }}' - loggingPackageVersion: '${{ parameters.loggingPackageVersion }}' - sqlClientAssemblyFileVersion: '${{ parameters.sqlClientFileVersion }}' - sqlClientPackageVersion: '${{ parameters.sqlClientPackageVersion }}' - - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + dependencies: + - artifactName: '${{ parameters.abstractionsArtifactsName }}' + shortName: 'Abstractions' + version: '${{ parameters.abstractionsPackageVersion }}' + - artifactName: '${{ parameters.loggingArtifactsName }}' + shortName: 'Logging' + version: '${{ parameters.loggingPackageVersion }}' + fileVersion: '${{ parameters.sqlClientFileVersion }}' + packageFullName: 'Microsoft.Data.SqlClient' + packageShortName: 'SqlClient' + packageVersion: '${{ parameters.sqlClientPackageVersion }}' + + # Build Microsoft.Data.SqlClient.Extensions.Azure + - template: /eng/pipelines/onebranch/jobs/build-buildproj-job.yml@self parameters: + apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient.Extensions.Azure/dlls' + apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient.Extensions.Azure/pdbs' + shouldSignPackage: ${{ parameters.isOfficial }} signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' @@ -224,20 +240,17 @@ stages: signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - isOfficial: ${{ parameters.isOfficial }} - packageName: Azure - packageFullName: Microsoft.Data.SqlClient.Extensions.Azure - versionProperties: >- - -p:AzurePackageVersion=${{ parameters.azurePackageVersion }} - -p:AzureAssemblyFileVersion=${{ parameters.azureFileVersion }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - assemblyFileVersion: '${{ parameters.azureFileVersion }}' - downloadArtifacts: + dependencies: - artifactName: '${{ parameters.abstractionsArtifactsName }}' - displayName: Abstractions Package + shortName: 'Abstractions' + version: '${{ parameters.abstractionsPackageVersion }}' - artifactName: '${{ parameters.loggingArtifactsName }}' - displayName: Logging Package + shortName: 'Logging' + version: '${{ parameters.loggingPackageVersion }}' + fileVersion: '${{ parameters.azureFileVersion }}' + packageFullName: 'Microsoft.Data.SqlClient.Extensions.Azure' + packageShortName: 'Azure' + packageVersion: '${{ parameters.azurePackageVersion }}' # ==================================================================== # Stage 4: Add-on packages (depend on core packages) @@ -249,8 +262,11 @@ stages: dependsOn: build_dependent jobs: - - template: /eng/pipelines/onebranch/jobs/build-signed-csproj-package-job.yml@self + - template: /eng/pipelines/onebranch/jobs/build-buildproj-job.yml@self parameters: + apiScanDllPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/dlls' + apiScanPdbPath: '$(REPO_ROOT)/apiScan/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/pdbs' + shouldSignPackage: ${{ parameters.isOfficial }} signingAppRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' signingAppRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' signingAuthAkvName: '${{ parameters.signingAuthAkvName }}' @@ -258,23 +274,20 @@ stages: signingEsrpClientId: '${{ parameters.signingEsrpClientId }}' signingEsrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - isOfficial: ${{ parameters.isOfficial }} - packageName: AkvProvider - packageFullName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider - versionProperties: >- - -p:AkvPackageVersion=${{ parameters.akvProviderPackageVersion }} - -p:AkvAssemblyFileVersion=${{ parameters.akvProviderFileVersion }} - -p:MdsPackageVersion=${{ parameters.sqlClientPackageVersion }} - -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} - -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} - assemblyFileVersion: '${{ parameters.akvProviderFileVersion }}' - downloadArtifacts: - - artifactName: '${{ parameters.sqlClientArtifactsName }}' - displayName: SqlClient Package + dependencies: - artifactName: '${{ parameters.abstractionsArtifactsName }}' - displayName: Abstractions Package + shortName: 'Abstractions' + version: '${{ parameters.abstractionsPackageVersion }}' - artifactName: '${{ parameters.loggingArtifactsName }}' - displayName: Logging Package + shortName: 'Logging' + version: '${{ parameters.loggingPackageVersion }}' + - artifactName: '${{ parameters.sqlClientArtifactsName }}' + shortName: 'SqlClient' + version: '${{ parameters.sqlClientPackageVersion }}' + fileVersion: '${{ parameters.akvProviderFileVersion }}' + packageFullName: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' + packageShortName: 'AkvProvider' + packageVersion: '${{ parameters.akvProviderPackageVersion }}' # ==================================================================== # Validation diff --git a/eng/pipelines/onebranch/stages/publish-symbols-stage.yml b/eng/pipelines/onebranch/stages/publish-symbols-stage.yml index e716d2255d..56e8f827b8 100644 --- a/eng/pipelines/onebranch/stages/publish-symbols-stage.yml +++ b/eng/pipelines/onebranch/stages/publish-symbols-stage.yml @@ -129,11 +129,9 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self parameters: artifactName: '${{ parameters.loggingArtifactsName }}' - packageName: Logging packageFullName: Microsoft.Data.SqlClient.Internal.Logging + packageShortName: Logging packageVersion: '${{ parameters.loggingPackageVersion }}' - # Matches: Microsoft.Data.SqlClient.Internal.Logging.pdb across all TFM subdirs. - searchPattern: '**/Microsoft.Data.SqlClient.Internal.Logging.pdb' symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' @@ -145,11 +143,9 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self parameters: artifactName: '${{ parameters.sqlServerArtifactsName }}' - packageName: SqlServer packageFullName: Microsoft.SqlServer.Server + packageShortName: SqlServer packageVersion: '${{ parameters.sqlServerPackageVersion }}' - # Matches: Microsoft.SqlServer.Server.pdb across all TFM subdirs. - searchPattern: '**/Microsoft.SqlServer.Server.pdb' symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' @@ -161,11 +157,9 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self parameters: artifactName: '${{ parameters.abstractionsArtifactsName }}' - packageName: Abstractions packageFullName: Microsoft.Data.SqlClient.Extensions.Abstractions + packageShortName: Abstractions packageVersion: '${{ parameters.abstractionsPackageVersion }}' - # Matches: Microsoft.Data.SqlClient.Extensions.Abstractions.pdb across all TFM subdirs. - searchPattern: '**/Microsoft.Data.SqlClient.Extensions.Abstractions.pdb' symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' @@ -177,12 +171,9 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self parameters: artifactName: '${{ parameters.sqlClientArtifactsName }}' - packageName: SqlClient packageFullName: Microsoft.Data.SqlClient + packageShortName: SqlClient packageVersion: '${{ parameters.sqlClientPackageVersion }}' - # Matches: Microsoft.Data.SqlClient.pdb across all OS/TFM subdirs. - # Excludes: Microsoft.Data.SqlClient.SNI*.pdb (native SNI — symbols published separately). - searchPattern: '**/Microsoft.Data.SqlClient.pdb' symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' @@ -194,11 +185,9 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self parameters: artifactName: '${{ parameters.azureArtifactsName }}' - packageName: Azure packageFullName: Microsoft.Data.SqlClient.Extensions.Azure + packageShortName: Azure packageVersion: '${{ parameters.azurePackageVersion }}' - # Matches: Microsoft.Data.SqlClient.Extensions.Azure.pdb across all TFM subdirs. - searchPattern: '**/Microsoft.Data.SqlClient.Extensions.Azure.pdb' symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' @@ -210,11 +199,9 @@ stages: - template: /eng/pipelines/onebranch/jobs/publish-symbols-job.yml@self parameters: artifactName: '${{ parameters.akvProviderArtifactsName }}' - packageName: AkvProvider packageFullName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + packageShortName: AkvProvider packageVersion: '${{ parameters.akvProviderPackageVersion }}' - # Matches: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb across all TFM subdirs. - searchPattern: '**/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb' symbolsAzureSubscription: '${{ parameters.symbolsAzureSubscription }}' symbolsPublishProjectName: '${{ parameters.symbolsPublishProjectName }}' symbolsPublishServer: '${{ parameters.symbolsPublishServer }}' diff --git a/eng/pipelines/onebranch/steps/build-buildproj-step.yml b/eng/pipelines/onebranch/steps/build-buildproj-step.yml new file mode 100644 index 0000000000..69c402792e --- /dev/null +++ b/eng/pipelines/onebranch/steps/build-buildproj-step.yml @@ -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. # +################################################################################# + +# This collection of steps to build a project via the build.proj. This will execute the "Build*" +# target in build.proj, where * is the packageShortName provided in the parameters. + +parameters: + # Build configuration - Release or Debug. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Optional arguments to pass to msbuild to indicate what version of dependencies should be used. + # They should be in the form of "-p:PackageVersionFooBar=1.2.3 ..." + - name: dependencyArguments + type: string + default: '' + + # Project to build. This short name will be appended to "Build" to generate the appropriate + # target to build from build.proj. + - name: packageShortName + type: string + values: + - Azure + - AkvProvider + - Abstractions + - Logging + - SqlClient + - SqlServer + + # Version to assign to the project to build. This will be passed into msbuild as + # "-p:PackageVersion(packageShortName)". + # @TODO: Make this optional to enable progressively migrating to single-source versioning. + - name: packageVersion + type: string + +steps: + # Download the strong name signing key from secure file storage + - task: DownloadSecureFile@1 + displayName: 'Download Signing Key' + inputs: + secureFile: 'netfxKeypair.snk' + name: keyFile + + - task: MSBuild@1 + displayName: 'build.proj - Build${{ parameters.packageShortName }}' + inputs: + solution: '$(REPO_ROOT)/build.proj' + configuration: '${{ parameters.buildConfiguration }}' + msbuildArguments: >- + -t:Build${{ parameters.packageShortName }} + -p:ReferenceType=Package + -p:SigningKeyPath="$(keyFile.secureFilePath)" + -p:BuildNumber="$(Build.BuildNumber)" + -p:PackageVersion${{ parameters.packageShortName }}="${{ parameters.packageVersion }}" + ${{ parameters.dependencyArguments }} + + - script: tree /a /f $(BUILD_OUTPUT) + displayName: Output Build Output Tree diff --git a/eng/pipelines/onebranch/steps/build-csproj-step.yml b/eng/pipelines/onebranch/steps/build-csproj-step.yml deleted file mode 100644 index a4a38b9fc3..0000000000 --- a/eng/pipelines/onebranch/steps/build-csproj-step.yml +++ /dev/null @@ -1,43 +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. # -################################################################################# - -# Generic build step for csproj-based packages. Each project uses a build.proj target that runs -# Build only and produces assemblies within $(BUILD_OUTPUT). Downstream ESRP DLL signing must -# locate the assemblies within $(BUILD_OUTPUT) for all target frameworks that the csproj targets. -# NuGet packaging is done separately via pack-csproj-step.yml after DLL signing. - -parameters: - # The MSBuild build target in build.proj (e.g. BuildLogging, BuildAbstractions, - # BuildAzure). - - name: buildTarget - type: string - - # Additional MSBuild arguments for version properties, e.g. - # -p:LoggingPackageVersion=1.0.0 -p:LoggingAssemblyFileVersion=1.0.123 - - name: versionProperties - type: string - default: '' - -steps: - - task: DownloadSecureFile@1 - displayName: Download Signing Key - inputs: - secureFile: netfxKeypair.snk - name: keyFile - - - task: MSBuild@1 - displayName: 'Build.proj - ${{ parameters.buildTarget }}' - inputs: - solution: '$(REPO_ROOT)/build.proj' - configuration: 'Release' - msbuildArguments: >- - -t:${{ parameters.buildTarget }} - -p:ReferenceType=Package - -p:SigningKeyPath=$(keyFile.secureFilePath) - ${{ parameters.versionProperties }} - - - script: tree /a /f $(BUILD_OUTPUT) - displayName: Output Build Output Tree diff --git a/eng/pipelines/onebranch/steps/build-sqlclient-step.yml b/eng/pipelines/onebranch/steps/build-sqlclient-step.yml deleted file mode 100644 index 390d17fc34..0000000000 --- a/eng/pipelines/onebranch/steps/build-sqlclient-step.yml +++ /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. # -################################################################################# - -# This collection of steps builds Microsoft.Data.SqlClient via build2.proj - -parameters: - - name: abstractionsPackageVersion - type: string - - - name: loggingPackageVersion - type: string - - - name: sqlClientPackageVersion - type: string - -steps: - - task: DownloadSecureFile@1 - displayName: 'Download Signing Key' - inputs: - secureFile: 'netfxKeypair.snk' - name: keyFile - - - task: MSBuild@1 - displayName: 'Build2.proj - Build SqlClient' - inputs: - solution: '$(REPO_ROOT)/build2.proj' - configuration: 'Release' - msbuildArguments: >- - -t:BuildMds - -p:BuildNumber="$(Build.BuildNumber)" - -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" - -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" - -p:PackageVersionMds="${{ parameters.sqlClientPackageVersion }}" - -p:ReferenceType=Package - -p:SigningKeyPath="$(keyFile.secureFilePath)" - - - script: tree /a /f $(BUILD_OUTPUT) - displayName: Output Build Output Tree diff --git a/eng/pipelines/onebranch/steps/pack-buildproj-step.yml b/eng/pipelines/onebranch/steps/pack-buildproj-step.yml new file mode 100644 index 0000000000..a0e6e02426 --- /dev/null +++ b/eng/pipelines/onebranch/steps/pack-buildproj-step.yml @@ -0,0 +1,84 @@ +################################################################################# +# 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. # +################################################################################# + +# Generic pack step for build.proj packages. This step runs the Pack* target to generate a NuGet +# package of the target package. The generated NuGet packages will then be copied to $(PACK_OUTPUT). +# +# Note: This step assumes that the package has been built previously using a Build* target. +# `-p:PackBuild=false` will be passed to msbuild to ensure the previously built assemblies are +# not trampled. + +parameters: + # Build configuration - Release or Debug. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Optional arguments to pass to msbuild to indicate what version of dependencies should be used. + # They should be in the form of "-p:PackageVersionFooBar=1.2.3 ..." + - name: dependencyArguments + type: string + default: '' + + # The full name of the package. This will be used to generate paths when copying NuGet packages + # out of the build output. + - name: packageFullName + type: string + values: + - Microsoft.Data.SqlClient + - Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + - Microsoft.Data.SqlClient.Extensions.Abstractions + - Microsoft.Data.SqlClient.Extensions.Azure + - Microsoft.Data.SqlClient.Internal.Logging + - Microsoft.SqlServer.Server + + # Project to build. This short name will be appended to "Pack" to generate the appropriate + # target to package via build.proj. + - name: packageShortName + type: string + values: + - Abstractions + - AkvProvider + - Azure + - Logging + - SqlClient + - SqlServer + + # Version to assign to the project to build. This will be passed into msbuild as + # "-p:PackageVersion(packageShortName)". + # @TODO: Make this optional to enable progressively migrating to single-source versioning. + - name: packageVersion + type: string + +steps: + - task: MSBuild@1 + displayName: 'build.proj - Pack${{ parameters.packageShortName }}' + inputs: + solution: '$(REPO_ROOT)/build.proj' + configuration: '${{ parameters.buildConfiguration }}' + msbuildArguments: >- + -t:Pack${{ parameters.packageShortName }} + -p:PackBuild=false + -p:ReferenceType=Package + -p:BuildNumber="$(Build.BuildNumber)" + -p:PackageVersion${{ parameters.packageShortName }}="${{ parameters.packageVersion }}" + ${{ parameters.dependencyArguments }} + + - script: tree /a /f $(BUILD_OUTPUT) + displayName: Output Build Output Tree + + - task: CopyFiles@2 + displayName: 'Copy NuGet Packages to PACK_OUTPUT' + inputs: + contents: '**/${{ parameters.packageFullName }}*.*nupkg' + flattenFolders: true + sourceFolder: '$(BUILD_OUTPUT)/${{ parameters.packageFullName }}/' + targetFolder: '$(PACK_OUTPUT)' + + - script: tree /a /f $(PACK_OUTPUT) + displayName: Output Package Output Tree diff --git a/eng/pipelines/onebranch/steps/pack-csproj-step.yml b/eng/pipelines/onebranch/steps/pack-csproj-step.yml deleted file mode 100644 index 535106a9d1..0000000000 --- a/eng/pipelines/onebranch/steps/pack-csproj-step.yml +++ /dev/null @@ -1,35 +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. # -################################################################################# - -# Generic pack step for csproj-based Extension packages (Logging, Abstractions, -# Azure). This step runs the Pack target after DLLs have been signed. The -# NoBuild=true property ensures the DLLs are not rebuilt. - -parameters: - # The MSBuild pack target in build.proj (e.g. PackLogging, PackAbstractions, - # PackAzure). - - name: packTarget - type: string - - # Additional MSBuild arguments for version properties. - - name: versionProperties - type: string - default: '' - -steps: - - task: MSBuild@1 - displayName: 'Build.proj - ${{ parameters.packTarget }}' - inputs: - solution: '$(REPO_ROOT)/build.proj' - configuration: 'Release' - msbuildArguments: >- - -t:${{ parameters.packTarget }} - -p:ReferenceType=Package - -p:PackagesDir=$(JOB_OUTPUT)/packages/ - ${{ parameters.versionProperties }} - - - script: tree /a /f $(JOB_OUTPUT)/packages - displayName: List Pack Output Tree After Pack diff --git a/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml b/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml deleted file mode 100644 index 73229fe834..0000000000 --- a/eng/pipelines/onebranch/steps/pack-sqlclient-step.yml +++ /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. # -################################################################################# - -parameters: - # Package version parameters ---- - - name: abstractionsPackageVersion - type: string - - - name: loggingPackageVersion - type: string - - - name: sqlClientPackageVersion - type: string - -steps: - - task: MSBuild@1 - displayName: 'Build2.proj - Pack SqlClient' - inputs: - solution: '$(REPO_ROOT)/build2.proj' - configuration: 'Release' - msbuildArguments: >- - -t:PackMds - -p:BuildNumber="$(Build.BuildNumber)" - -p:PackBuild=false - -p:PackageVersionAbstractions="${{ parameters.abstractionsPackageVersion }}" - -p:PackageVersionLogging="${{ parameters.loggingPackageVersion }}" - -p:PackageVersionMds="${{ parameters.sqlClientPackageVersion }}" - -p:ReferenceType=Package - - - script: tree /a /f $(BUILD_OUTPUT) - displayName: Output Build Output Tree - - - task: CopyFiles@2 - displayName: 'Copy NuGet Packages to JOB_OUTPUT' - inputs: - contents: '**/Microsoft.Data.SqlClient*.*nupkg' - flattenFolders: true - sourceFolder: '$(BUILD_OUTPUT)/Microsoft.Data.SqlClient/' - targetFolder: '$(JOB_OUTPUT)/packages' diff --git a/eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml similarity index 51% rename from eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml rename to eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml index ac515aa021..ba8cb79c83 100644 --- a/eng/pipelines/onebranch/steps/roslyn-analyzers-sqlclient-step.yml +++ b/eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml @@ -13,28 +13,44 @@ # steps that perform versioning or signing. parameters: - - name: abstractionsPackageVersion + # Optional arguments to pass to msbuild to indicate what version of dependencies should be used. + - name: dependencyArguments type: string + default: '' - - name: loggingPackageVersion + # Project to build. This short name will be appended to "Build" to generate the appropriate + # target to build from build.proj. + - name: packageShortName type: string + values: + - Abstractions + - AkvProvider + - Azure + - Logging + - SqlClient + - SqlServer - - name: sqlClientPackageVersion + # Version to assign to the project to build. This will be passed into msbuild as + # "-p:PackageVersion(packageShortName)". + # @TODO: Make this optional to enable progressively migrating to single-source versioning. + - name: packageVersion type: string steps: + # GOTCHA: If there are any blank lines in msbuildCommandLine, it will consider it "multiple + # arguments" and fail. So, don't split msBuildCommandLine into multiple blocks. - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 - displayName: 'Roslyn Analyzers: build2.proj Build SqlClient' + displayName: 'Roslyn Analyzers - build.proj Build${{ parameters.packageShortName }}' inputs: msBuildArchitecture: x64 msBuildCommandLine: >- msbuild - $(REPO_ROOT)/build2.proj - -t:BuildMds + $(REPO_ROOT)/build.proj + -t:Build${{ parameters.packageShortName }} -p:Configuration=Release - -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} - -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} - -p:PackageVersionMds=${{ parameters.sqlClientPackageVersion }} -p:ReferenceType=Package + -p:BuildNumber="$(Build.BuildNumber)" + -p:PackageVersion${{ parameters.packageShortName }}="${{ parameters.packageVersion }}" + ${{ parameters.dependencyArguments }} msBuildVersion: 17.0 setupCommandLinePicker: vs2022 diff --git a/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml deleted file mode 100644 index 53b17d4bb9..0000000000 --- a/eng/pipelines/onebranch/steps/roslyn-analyzers-csproj-step.yml +++ /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. # -################################################################################# - -# This template defines a step to run Roslyn Analyzers against builds driven by -# build.proj. It is used for both the full Microsoft.Data.SqlClient (SqlClient) build -# and for individual extension packages; callers control the analyzed targets -# via the msBuildArguments parameter. -# It uses the RoslynAnalyzers@3 task from the Secure Development Team's SDL -# extension: -# https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/roslyn-analyzers-build-task -# -# GOTCHA: This step will clobber any existing build output. It should be run -# _before_ any build steps that perform versioning or signing. - -parameters: - # Target to build from build.proj. This is separated from the rest of the msBuildArguments to - # provide a rich display name. - - name: buildTarget - type: string - - # Additional build arguments to append to the end of the MSBuild command. - - name: msBuildArguments - type: string - -steps: - - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 - displayName: 'Roslyn Analyzers: build.proj ${{ parameters.buildTarget }}' - inputs: - msBuildVersion: 17.0 - msBuildArchitecture: x64 - setupCommandLinePicker: vs2022 - msBuildCommandLine: >- - msbuild - $(REPO_ROOT)\build.proj - -t:${{ parameters.buildTarget }} - ${{ parameters.msBuildArguments }} diff --git a/eng/pipelines/onebranch/variables/common-variables.yml b/eng/pipelines/onebranch/variables/common-variables.yml index 3df1a6264f..5b6209d59d 100644 --- a/eng/pipelines/onebranch/variables/common-variables.yml +++ b/eng/pipelines/onebranch/variables/common-variables.yml @@ -18,8 +18,7 @@ variables: value: $(Build.SourcesDirectory) # This is where our C# projects place their build outputs (see Directory.Build.props - # ). We exclude the reference type here since Official pipelines always use - # Package mode. + # ). - name: BUILD_OUTPUT value: $(REPO_ROOT)/artifacts @@ -30,14 +29,7 @@ variables: - name: JOB_INPUT value: $(REPO_ROOT)/packages - # Root directory for all job output artifacts (NuGet packages, symbols, etc.). - # OneBranch auto-publishes everything under this directory. It is intentionally - # separated from packages/ (where downloaded pipeline artifacts go) so that ESRP - # signing and OneBranch artifact publishing only operate on the current job's output. - # - # Sub-directory layout: - # assemblies/ - DLL assemblies for APIScan (preserving TFM folder structure) - # packages/ - NuGet packages (.nupkg, .snupkg) - # symbols/ - PDB symbol files (preserving TFM folder structure) + # Root directory for all job output artifacts (NuGet packages, symbols, etc.). OneBranch auto + # publishes everything under this directory as the artifact for the job. - name: JOB_OUTPUT value: $(REPO_ROOT)/output diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index 32921a733e..ba2ff262bf 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -46,7 +46,6 @@ pr: - tools/* - azurepipelines-coverage.yml - build.proj - - build2.proj - Directory.Packages.props - dotnet-tools.json - global.json diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index 738f8c1b6a..a928175f17 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -46,7 +46,6 @@ pr: - tools/* - azurepipelines-coverage.yml - build.proj - - build2.proj - Directory.Packages.props - dotnet-tools.json - global.json diff --git a/eng/pipelines/stages/build-sqlserver-package-ci-stage.yml b/eng/pipelines/stages/build-sqlserver-package-ci-stage.yml new file mode 100644 index 0000000000..1711725373 --- /dev/null +++ b/eng/pipelines/stages/build-sqlserver-package-ci-stage.yml @@ -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. +################################################################################ + +# This stage builds the Microsoft.SqlServer.Server package and publishes the +# resulting NuGet packages as pipeline artifacts. +# +# The NuGet packages have the following properties: +# +# Name: Microsoft.SqlServer.Server +# Version: (from parameter) +# +# The following NuGet packages are published: +# +# Microsoft.SqlServer.Server..nupkg +# Microsoft.SqlServer.Server..snupkg (symbols) +# +# The packages are published to pipeline artifacts with the name specified by +# the sqlServerArtifactsName parameter. +# +# This template defines a stage named 'build_sqlserver_package_stage' that +# can be depended on by downstream stages. + +parameters: + + # Additional stages we depend on, if any. + - name: additionalDependsOn + type: object + default: [] + + # The type of build to produce (Release or Debug) + - name: buildConfiguration + type: string + default: Release + values: + - Release + - Debug + + # True to emit debug information and steps. + - name: debug + type: boolean + default: false + + # The name of the pipeline artifacts to publish. + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + + # The version to apply to the SqlServer NuGet package and DLLs. + - name: sqlServerPackageVersion + type: string + + # The verbosity level for the dotnet CLI commands. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +stages: + + - stage: build_sqlserver_package_stage + displayName: Build SqlServer Package + + dependsOn: + - secrets_stage + - ${{ each dep in parameters.additionalDependsOn }}: + - ${{ dep }} + + jobs: + - template: /eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} diff --git a/eng/pipelines/stages/verify-nuget-packages-ci-stage.yml b/eng/pipelines/stages/verify-nuget-packages-ci-stage.yml index 845f944edb..3dccd61989 100644 --- a/eng/pipelines/stages/verify-nuget-packages-ci-stage.yml +++ b/eng/pipelines/stages/verify-nuget-packages-ci-stage.yml @@ -34,6 +34,10 @@ parameters: type: string default: MDS.Artifacts + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + stages: - stage: verify_nuget_packages_stage @@ -43,6 +47,7 @@ stages: - build_abstractions_package_stage - build_azure_package_stage - build_logging_package_stage + - build_sqlserver_package_stage - build_sqlclient_package_stage jobs: @@ -85,7 +90,11 @@ stages: artifactName: ${{ parameters.mdsArtifactsName }} targetPath: $(packagesDir) - # TODO: Add SqlServer here when we start building it as part of PR/CI pipelines. + - task: DownloadPipelineArtifact@2 + displayName: Download SqlServer Package Artifacts + inputs: + artifactName: ${{ parameters.sqlServerArtifactsName }} + targetPath: $(packagesDir) # Run the verification tool against all downloaded packages. - template: /eng/pipelines/common/templates/steps/verify-nuget-package-step.yml@self diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 6dcd4bbbd9..6ec3fba9a8 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -12,6 +12,51 @@ Project + + + + true + + + + + + + + + + + + + 0 + $(BuildNumber.Split('.')[0]) + + $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) @@ -62,26 +107,6 @@ low - - - - true - - - - - - - - @@ -103,9 +128,6 @@ false - - - diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/.editorconfig b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/.editorconfig similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/.editorconfig rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/.editorconfig diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/AzureSqlKeyCryptographer.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/AzureSqlKeyCryptographer.cs diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Constants.cs b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Constants.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Constants.cs rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Constants.cs diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/LocalCache.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/LocalCache.cs diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj similarity index 66% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj index bfef481e09..909e36df7f 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj +++ b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj @@ -1,53 +1,37 @@ - - - - - - + + + Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider netstandard2.0 - + - Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + true Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider - - - $(AkvDefaultMajorVersion).0.0.0 - - $(AkvAssemblyFileVersion) - $(AkvAssemblyFileVersion) - $(AkvPackageVersion) - + + - - $(Artifacts)/obj/$(Configuration)/AzureKeyVaultProvider - $(Artifacts)/bin/$(Configuration)/AzureKeyVaultProvider - true + $(AkvProviderAssemblyVersion) + $(AkvProviderFileVersion) + $(AkvProviderPackageVersion) - + - See: https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#pack-target - --> + + + $(RepoRoot)artifacts/ + $(ArtifactPath)$(AssemblyName)/$(Configuration) + + + + $(AssemblyName) - $(AkvPackageVersion) - $(PackagesDir) true snupkg @@ -83,7 +67,6 @@
- - - - - - + + + @@ -106,5 +89,4 @@ - diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/PackageReadme.md b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/PackageReadme.md similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/PackageReadme.md rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/PackageReadme.md diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/SqlColumnEncryptionAzureKeyVaultProvider.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/SqlColumnEncryptionAzureKeyVaultProvider.cs diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Strings.Designer.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Strings.Designer.cs diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Strings.resx similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Strings.resx diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Utils.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs rename to src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Utils.cs diff --git a/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props new file mode 100644 index 0000000000..25e3ff90ff --- /dev/null +++ b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props @@ -0,0 +1,86 @@ + + + + + + + 7.0.0 + + + + + + + + $(AkvProviderPackageVersion.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(AkvProviderVersionDefault)-$(BuildSuffix)$(BuildNumber) + $(AkvProviderVersionDefault) + + $(AkvProviderVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + dev + + $(AkvProviderVersionDefault)-$(BuildSuffix) + $(AkvProviderVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(AkvProviderFileVersion.Split('.')[0]).0.0.0 + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj index edd9144040..45acd55488 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -1,51 +1,19 @@ - - - - - + - netstandard2.0 + Microsoft.Data.SqlClient.Extensions.Abstractions + netstandard2.0 - - - - - - + + true enable enable + Microsoft.Data.SqlClient - - - Microsoft.Data.SqlClient.Extensions.Abstractions - - - - - - $(AbstractionsDefaultMajorVersion).0.0.0 - - $(AbstractionsAssemblyFileVersion) - $(AbstractionsAssemblyFileVersion) - $(AbstractionsPackageVersion) - + @@ -53,36 +21,30 @@ - + + + $(AbstractionsAssemblyVersion) + $(AbstractionsFileVersion) + $(AbstractionsPackageVersion) + - - $(Artifacts)/obj/$(Configuration)/$(AssemblyName) - - - $(Artifacts)/bin/$(Configuration)/$(AssemblyName) - - - true + + + + + + + + $(RepoRoot)artifacts/ + $(ArtifactPath)$(AssemblyName)/$(Configuration)/ - + + $(AssemblyName) - $(AbstractionsPackageVersion) - $(PackagesDir) true snupkg @@ -105,29 +67,15 @@ - - - - - - + + + + + - - - - $(AssemblyName) - - - + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props deleted file mode 100644 index 75be8d98c3..0000000000 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - 1 - - - <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' != ''">$(AbstractionsPackageVersion) - <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' == ''">$(AbstractionsDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev - - - - <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' != ''">$(AbstractionsAssemblyFileVersion) - - <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' == '' and '$(AbstractionsPackageVersion)' != ''">$(AbstractionsPackageVersion.Split('-')[0]) - - <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' == '' and '$(AbstractionsPackageVersion)' == ''">$(AbstractionsDefaultMajorVersion).0.0.$(AssemblyBuildNumber) - - - $(_OurPackageVersion) - $(_OurAssemblyFileVersion) - - - - diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props new file mode 100644 index 0000000000..32330ad3a6 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props @@ -0,0 +1,87 @@ + + + + + + + 1.0.0 + + + + + + + + $(AbstractionsPackageVersion.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(AbstractionsVersionDefault)-$(BuildSuffix)$(BuildNumber) + $(AbstractionsVersionDefault) + + $(AbstractionsVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + dev + + $(AbstractionsVersionDefault)-$(BuildSuffix) + $(AbstractionsVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(AbstractionsFileVersion.Split('.')[0]).0.0.0 + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj index aeb752d648..427a7aaf9f 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj @@ -1,29 +1,25 @@  - + + Microsoft.Data.SqlClient.Extensions.Abstractions.Test net462;net8.0;net9.0;net10.0 - enable - enable + false true - Microsoft.Data.SqlClient.Extensions.Abstractions.Test - - - - - - - - - - + + + enable + enable + + + PreserveNewest @@ -31,4 +27,18 @@ + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj index b244dd467e..423c55387f 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj @@ -1,51 +1,19 @@ - - - - - + - netstandard2.0;net462 + Microsoft.Data.SqlClient.Extensions.Azure + net462;netstandard2.0 - - - - - - + + true enable enable + Microsoft.Data.SqlClient - - - Microsoft.Data.SqlClient.Extensions.Azure - - - - - - $(AzureDefaultMajorVersion).0.0.0 - - $(AzureAssemblyFileVersion) - $(AzureAssemblyFileVersion) - $(AzurePackageVersion) - + @@ -53,36 +21,30 @@ - + + + $(AzureAssemblyVersion) + $(AzureFileVersion) + $(AzurePackageVersion) + - - $(Artifacts)/obj/$(Configuration)/$(AssemblyName) - - - $(Artifacts)/bin/$(Configuration)/$(AssemblyName) - - - true + + + + + + + + $(RepoRoot)artifacts/ + $(ArtifactPath)$(AssemblyName)/$(Configuration)/ - + + $(AssemblyName) - $(AzurePackageVersion) - $(PackagesDir) true snupkg @@ -105,28 +67,24 @@ - - - - - - + + + + + - - - - - - + + + + + @@ -135,18 +93,6 @@ - - - - $(AssemblyName) - + - diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props deleted file mode 100644 index 2102cd798a..0000000000 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - 1 - - - <_OurPackageVersion Condition="'$(AzurePackageVersion)' != ''">$(AzurePackageVersion) - <_OurPackageVersion Condition="'$(AzurePackageVersion)' == ''">$(AzureDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev - - - - <_OurAssemblyFileVersion Condition="'$(AzureAssemblyFileVersion)' != ''">$(AzureAssemblyFileVersion) - - <_OurAssemblyFileVersion Condition="'$(AzureAssemblyFileVersion)' == '' and '$(AzurePackageVersion)' != ''">$(AzurePackageVersion.Split('-')[0]) - - <_OurAssemblyFileVersion Condition="'$(AzureAssemblyFileVersion)' == '' and '$(AzurePackageVersion)' == ''">$(AzureDefaultMajorVersion).0.0.$(AssemblyBuildNumber) - - - $(_OurPackageVersion) - $(_OurAssemblyFileVersion) - - - - diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props new file mode 100644 index 0000000000..1876c965eb --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props @@ -0,0 +1,86 @@ + + + + + + + 1.0.0 + + + + + + + + $(AzurePackageVersion.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(AzureVersionDefault)-$(BuildSuffix)$(BuildNumber) + $(AzureVersionDefault) + + $(AzureVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + dev + + $(AzureVersionDefault)-$(BuildSuffix) + $(AzureVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(AzureFileVersion.Split('.')[0]).0.0.0 + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj index 7d2a0c56a5..1c020c2ae4 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -4,23 +4,8 @@ Microsoft.Data.SqlClient.Extensions.Azure.Test Microsoft.Data.SqlClient.Extensions.Azure.Test - enable - true - enable false - - - - Category!=Interactive + true @@ -34,6 +19,12 @@ $(TargetFrameworks);net462 + + + enable + enable + + @@ -56,7 +47,6 @@ Condition="'$(ReferenceType)' == 'Package'"/> - diff --git a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj index 1146e92c8f..f8461d8b92 100644 --- a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj +++ b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj @@ -1,51 +1,21 @@ - - - - - + - netstandard2.0 + Microsoft.Data.SqlClient.Internal.Logging + netstandard2.0 - - - - - - + + true enable enable - - - - - Microsoft.Data.SqlClient.Internal.Logging - - - - - - $(LoggingDefaultMajorVersion).0.0.0 - $(LoggingAssemblyFileVersion) - $(LoggingAssemblyFileVersion) - $(LoggingPackageVersion) + + Microsoft.Data.SqlClient.Internal + + @@ -53,36 +23,30 @@ - + + + $(LoggingAssemblyVersion) + $(LoggingFileVersion) + $(LoggingPackageVersion) + - - $(Artifacts)/obj/$(Configuration)/$(AssemblyName) - - - $(Artifacts)/bin/$(Configuration)/$(AssemblyName) - - - true + + + + + + + + $(RepoRoot)artifacts/ + $(ArtifactPath)$(AssemblyName)/$(Configuration)/ - + + $(AssemblyName) - $(LoggingPackageVersion) - $(PackagesDir) true snupkg @@ -111,18 +75,7 @@ - - - - $(AssemblyName) - + diff --git a/src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props b/src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props deleted file mode 100644 index 9205b88793..0000000000 --- a/src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - 1 - - - <_OurPackageVersion Condition="'$(LoggingPackageVersion)' != ''">$(LoggingPackageVersion) - <_OurPackageVersion Condition="'$(LoggingPackageVersion)' == ''">$(LoggingDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev - - - - <_OurAssemblyFileVersion Condition="'$(LoggingAssemblyFileVersion)' != ''">$(LoggingAssemblyFileVersion) - - <_OurAssemblyFileVersion Condition="'$(LoggingAssemblyFileVersion)' == '' and '$(LoggingPackageVersion)' != ''">$(LoggingPackageVersion.Split('-')[0]) - - <_OurAssemblyFileVersion Condition="'$(LoggingAssemblyFileVersion)' == '' and '$(LoggingPackageVersion)' == ''">$(LoggingDefaultMajorVersion).0.0.$(AssemblyBuildNumber) - - - $(_OurPackageVersion) - $(_OurAssemblyFileVersion) - - - - diff --git a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props new file mode 100644 index 0000000000..3278bdc2e9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props @@ -0,0 +1,87 @@ + + + + + + + 1.0.0 + + + + + + + + $(LoggingPackageVersion.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(LoggingVersionDefault)-$(BuildSuffix)$(BuildNumber) + $(LoggingVersionDefault) + + $(LoggingVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + dev + + $(LoggingVersionDefault)-$(BuildSuffix) + $(LoggingVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(LoggingFileVersion.Split('.')[0]).0.0.0 + + diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index e08e693479..1b6b3440f8 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -128,7 +128,6 @@ - @@ -137,7 +136,7 @@ - + @@ -151,7 +150,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/MdsVersions.props b/src/Microsoft.Data.SqlClient/Versions.props similarity index 65% rename from src/Microsoft.Data.SqlClient/MdsVersions.props rename to src/Microsoft.Data.SqlClient/Versions.props index 97063881d9..ea6b557467 100644 --- a/src/Microsoft.Data.SqlClient/MdsVersions.props +++ b/src/Microsoft.Data.SqlClient/Versions.props @@ -3,31 +3,28 @@ This file contains versioning information for the Microsoft.Data.SqlClient package. We will calculate three different version variables, used for different purposes: - * MdsPackageVersion - this will be used as the property, the version of the NuGet - package. It should be in the form: (Major).(Minor).(Patch)[-(Suffix)(BuildNumber)] - * MdsFileVersion - this will be used as the property. It should be in the form: - (Major).(Minor).(Patch).(BuildNumber) - * MdsAssemblyVersion - this will be used as the property. This is the version + * SqlClientAssemblyVersion - this will be used as the property. This is the version reported to user if they open the assembly properties. It *must not* change within major versions in order for minor versions to be considered backwards compatible via strong naming. It must be in the form: (Major).0.0.0 + * SqlClientFileVersion - this will be used as the property. It should be in the form: + (Major).(Minor).(Patch).(BuildNumber) + * SqlClientPackageVersion - this will be used as the property, the version of the NuGet + package. It should be in the form: (Major).(Minor).(Patch)[-(Suffix)(BuildNumber)] --> - - - 7.0.0 - + 7.0.0 - + - $(MdsPackageVersion.Split('-')[0]).$(AssemblyBuildNumber) + $(SqlClientPackageVersion.Split('-')[0]).$(FileVersionBuildNumber) @@ -54,10 +51,10 @@ build pipelines to generate production-ready builds. --> - $(MdsVersionDefault)-$(BuildSuffix)$(AssemblyBuildNumber) - $(MdsVersionDefault) + $(SqlClientVersionDefault)-$(BuildSuffix)$(BuildNumber) + $(SqlClientVersionDefault) - $(MdsVersionDefault.Split('-')[0]).$(AssemblyBuildNumber) + $(SqlClientVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) @@ -71,8 +68,8 @@ dev - $(MdsVersionDefault)-$(BuildSuffix) - $(MdsVersionDefault.Split('-')[0]).$(AssemblyBuildNumber) + $(SqlClientVersionDefault)-$(BuildSuffix) + $(SqlClientVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) @@ -80,9 +77,10 @@ - $(MdsFileVersion.Split('.')[0]).0.0.0 + $(SqlClientFileVersion.Split('.')[0]).0.0.0 - diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props deleted file mode 100644 index 47982a6478..0000000000 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - 7 - - - <_AkvPackageVersion Condition="'$(AkvPackageVersion)' != ''">$(AkvPackageVersion) - <_AkvPackageVersion Condition="'$(AkvPackageVersion)' == ''">$(AkvDefaultMajorVersion).0.0.$(AssemblyBuildNumber)-dev - - - - <_AkvAssemblyFileVersion Condition="'$(AkvAssemblyFileVersion)' != ''">$(AkvAssemblyFileVersion) - - <_AkvAssemblyFileVersion Condition="'$(AkvAssemblyFileVersion)' == '' and '$(AkvPackageVersion)' != ''">$(AkvPackageVersion.Split('-')[0]) - - <_AkvAssemblyFileVersion Condition="'$(AkvAssemblyFileVersion)' == '' and '$(AkvPackageVersion)' == ''">$(AkvDefaultMajorVersion).0.0.$(AssemblyBuildNumber) - - - $(_AkvPackageVersion) - $(_AkvAssemblyFileVersion) - - - - diff --git a/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj index 4099207511..ca4c73a6ec 100644 --- a/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj @@ -38,9 +38,9 @@ adding a reference from this project to the GenAPI project. Although this is possible, it is not a clean solution (due to reasons related to target frameworks). Thus, building this project assumes the GenAPI project has already been built in a previous step. Instead, use the - following command in build2.proj to build the "not supported" binaries: + following command in build.proj to build the "not supported" binaries: - > msbuild build2.proj /t:BuildMdsNotSupported + > msbuild build.proj /t:BuildSqlClientNotSupported @TODO: Remove this project The primary prerequisite for removing this project is making the "common" MDS project OS- @@ -55,6 +55,11 @@ Microsoft.Data.SqlClient net462;net8.0;net9.0;netstandard2.0 + + + + + enable - + - $(MdsAssemblyVersion) - $(MdsFileVersion) - $(MdsPackageVersion) + $(SqlClientAssemblyVersion) + $(SqlClientFileVersion) + $(SqlClientPackageVersion) + + + diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index dfad9e601c..2895ce5a4a 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -1,16 +1,21 @@ + Microsoft.Data.SqlClient net462;net8.0;net9.0;netstandard2.0 + + + + true - + - $(MdsAssemblyVersion) - $(MdsFileVersion) - $(MdsPackageVersion) + $(SqlClientAssemblyVersion) + $(SqlClientFileVersion) + $(SqlClientPackageVersion) @@ -47,6 +52,7 @@ + powershell.exe pwsh diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 31a13c5801..cffc9fe96f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -2,37 +2,8 @@ Microsoft.Data.SqlClient - Debug;Release; - - true - - true - - - - $(MdsAssemblyVersion) - $(MdsFileVersion) - $(MdsPackageVersion) - - - - - - <_Parameter1>true - - - - - - - - - <_Parameter1>UnitTests - - - @@ -64,9 +35,39 @@ $(TargetFrameworks);net462 + + + true + true + + + + + + + <_Parameter1>true + + + + + + + $(SqlClientAssemblyVersion) + $(SqlClientFileVersion) + $(SqlClientPackageVersion) + + + + + + + + <_Parameter1>UnitTests + + + - $(RepoRoot)artifacts/ @@ -112,6 +113,30 @@ + + + + + + + + + + + + + + + + + + @@ -148,39 +173,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs index 55bc3b8208..a8fcf7ca88 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs @@ -14,7 +14,7 @@ namespace Microsoft.Data.SqlClient; /// This class uses runtime environment information to produce a value /// suitable for use in the TDS LOGIN7 USERAGENT Feature Extension /// payload. -/// +/// /// See the spec here: /// /// @@ -24,13 +24,13 @@ namespace Microsoft.Data.SqlClient; internal static class UserAgent { #region Properties - + /// /// /// The pipe-delimited payload as a string, never null, never empty, and /// never larger than 256 characters. /// - /// + /// /// The format is pipe ('|') delimited into 7 parts: /// /// 1|MS-MDS|{Driver Version}|{Arch}|{OS Type}|{OS Info}|{Runtime Info} @@ -63,7 +63,7 @@ internal static class UserAgent /// The {OS Info} part will be sourced from the /// /// OSDescription - /// + /// /// value, or "Unknown" if that value is empty or all whitespace. /// Maximum length is 44 characters. /// @@ -105,7 +105,7 @@ internal static class UserAgent internal static ReadOnlyMemory Ucs2Bytes { get; } #endregion Properties - + #region Helpers /// @@ -145,12 +145,12 @@ static UserAgent() MaxLenOverall, PayloadVersion, DriverName, - System.ThisAssembly.NuGetPackageVersion, + ThisAssembly.NuGetPackageVersion, RuntimeInformation.ProcessArchitecture, osType, RuntimeInformation.OSDescription, RuntimeInformation.FrameworkDescription); - + // Convert it to UCS-2 bytes. // // The default Unicode instance doesn't throw if encoding fails, so @@ -170,25 +170,25 @@ static UserAgent() /// The maximum length of the returned value. /// /// - /// The value of the payload version part. + /// The value of the payload version part. /// /// - /// The value of the driver name part. + /// The value of the driver name part. /// /// - /// The value of the driver version part. + /// The value of the driver version part. /// /// /// The value of the Architecture part. /// /// - /// The value of the OS Type part. + /// The value of the OS Type part. /// /// - /// The value of the OS Info part. + /// The value of the OS Info part. /// /// - /// The value of the Runtime Info part. + /// The value of the Runtime Info part. /// /// /// The payload string value, never null, never empty, and never longer @@ -238,11 +238,11 @@ internal static string Build( // Add the OS Type, truncating to its max length. name.Append(Truncate(Clean(osType), MaxLenOsType)); name.Append('|'); - + // Add the OS Info, truncating to its max length. name.Append(Truncate(Clean(osInfo), MaxLenOsInfo)); name.Append('|'); - + // Add the Runtime Info, truncating to its max length. name.Append(Truncate(Clean(runtimeInfo), MaxLenRuntimeInfo)); @@ -320,7 +320,7 @@ internal static string Clean(string? value) { return Unknown; } - + // Remove any leading and trailing whitespace. value = value.Trim(); @@ -345,7 +345,7 @@ internal static string Clean(string? value) (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') - #endif + #endif || c == ' ' || c == '.' || c == '+' @@ -406,7 +406,7 @@ internal static string Truncate(string value, ushort maxLength) // The overall maximum length of Value. private const ushort MaxLenOverall = 256; - + // Maximum part lengths as promised in our API. private const ushort MaxLenPayloadVersion = 2; private const ushort MaxLenDriverName = 12; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 7045eecc65..3bc6ea9fa7 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -41,11 +41,6 @@ PreserveNewest xunit.runner.json - - - PreserveNewest - CodeCoverage.runsettings - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 110547a5de..1c1553e6c1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -73,10 +73,6 @@ PreserveNewest xunit.runner.json - - PreserveNewest - CodeCoverage.runsettings - @@ -347,7 +343,7 @@ - + @@ -421,5 +417,5 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props index e6df2da836..1d19b81910 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props @@ -3,12 +3,6 @@ - - - true diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index 3c35a1a205..96c3c052b1 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -34,10 +34,6 @@ PreserveNewest xunit.runner.json - - PreserveNewest - CodeCoverage.runsettings - diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/UserAgentTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/UserAgentTests.cs index 49bd92b399..8331fdb654 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/UserAgentTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/UserAgentTests.cs @@ -78,12 +78,12 @@ public void Value_Runtime_Parts() Assert.Equal(7, parts.Length); Assert.Equal("1", parts[0]); Assert.Equal("MS-MDS", parts[1]); - Assert.Equal(System.ThisAssembly.NuGetPackageVersion, parts[2]); + Assert.Equal(ThisAssembly.NuGetPackageVersion, parts[2]); // Architecture must be non-empty and 10 characters or less. Assert.True(parts[3] == "Unknown" || parts[3].Length > 0); Assert.True(parts[3].Length <= 10); - + // Check the OS Type against the guaranteed values. var osType = parts[4]; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -112,7 +112,7 @@ public void Value_Runtime_Parts() // OS Info must be non-empty and 44 characters or less. Assert.True(parts[5] == "Unknown" || parts[5].Length > 0); Assert.True(parts[5].Length <= 44); - + // Runtime Info must be non-empty and 44 characters or less. Assert.True(parts[6] == "Unknown" || parts[6].Length > 0); Assert.True(parts[6].Length <= 44); @@ -145,7 +145,7 @@ public void Ucs2Bytes_Runtime_Parts() #else Encoding.Unicode.GetString(bytes.ToArray()); #endif - + Assert.Equal(UserAgent.Value, value); } @@ -197,7 +197,7 @@ public void Build_Truncate_Payload_Version() "P", UserAgent.Build( 1, "PV", "A", "B", Architecture.X64, "C", "D", "E")); - + // The payload version is longer than its per-field max length of 2. Assert.Equal( "12|A|B|X64|C|D|E", @@ -216,7 +216,7 @@ public void Build_Truncate_Driver_Name() "2|DriverNa", UserAgent.Build( 10, "2", "DriverName", "B", Architecture.X64, "C", "D", "E")); - + // The driver name is longer than its per-field max length of 12. Assert.Equal( "2|LongDriverNa|B|X64|C|D|E", @@ -237,7 +237,7 @@ public void Build_Truncate_Driver_Version() UserAgent.Build( 12, "2", "A", "DriverVersion", Architecture.X64, "C", "D", "E")); - + // The driver version is longer than its per-field max length of 24. Assert.Equal( "2|A|ReallyLongDriverVersionS|X64|C|D|E", @@ -281,7 +281,7 @@ public void Build_Truncate_OS_Type() "2|A|B|X64|LongOs", UserAgent.Build( 16, "2", "A", "B", Architecture.X64, "LongOsName", "D", "E")); - + // The OS Type is longer than its per-field max length of 10. Assert.Equal( "2|A|B|X64|VeryLongOs|D|E", @@ -301,7 +301,7 @@ public void Build_Truncate_OS_Info() "2|A|B|X64|C|LongOsI", UserAgent.Build( 19, "2", "A", "B", Architecture.X64, "C", "LongOsInfo", "E")); - + // The OS Type is longer than its per-field max length of 44. Assert.Equal( "2|A|B|X64|C|01234567890123456789012345678901234567890123|E", @@ -323,7 +323,7 @@ public void Build_Truncate_Runtime_Info() UserAgent.Build( 22, "2", "A", "B", Architecture.X64, "C", "D", "LongRuntimeInfo")); - + // The Runtime Type is longer than its per-field max length of 44. Assert.Equal( "2|A|B|X64|C|D|01234567890123456789012345678901234567890123", @@ -339,7 +339,7 @@ public void Build_Truncate_Runtime_Info() [Fact] public void Build_Truncate_Most() { - var name = + var name = UserAgent.Build( 192, // Payload version > 2 chars. @@ -379,7 +379,7 @@ public void Build_Truncate_Most() [Fact] public void Build_Truncate_All() { - var name = + var name = UserAgent.Build( 192, // Payload version > 2 chars. @@ -536,19 +536,19 @@ public void Truncate() Assert.Equal("", UserAgent.Truncate(" ", 0)); Assert.Equal("", UserAgent.Truncate("A", 0)); Assert.Equal("", UserAgent.Truncate("ABCDE FGHIJ", 0)); - + // Max length of 1. Assert.Equal("", UserAgent.Truncate("", 1)); Assert.Equal(" ", UserAgent.Truncate(" ", 1)); Assert.Equal("A", UserAgent.Truncate("A", 1)); Assert.Equal("A", UserAgent.Truncate("ABCDE FGHIJ", 1)); - + // Max length of 5. Assert.Equal("", UserAgent.Truncate("", 5)); Assert.Equal(" ", UserAgent.Truncate(" ", 5)); Assert.Equal("A", UserAgent.Truncate("A", 5)); Assert.Equal("ABCDE", UserAgent.Truncate("ABCDE FGHIJ", 5)); - + // Max length of 100. Assert.Equal("", UserAgent.Truncate("", 100)); Assert.Equal(" ", UserAgent.Truncate(" ", 100)); diff --git a/src/Microsoft.SqlServer.Server/.editorconfig b/src/Microsoft.SqlServer.Server/.editorconfig deleted file mode 100644 index f700e14ef5..0000000000 --- a/src/Microsoft.SqlServer.Server/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -# editorconfig.org - -# top-most EditorConfig file -root = false - -[*.cs] -# CA1310: Specify StringComparison for correctness -dotnet_diagnostic.CA1310.severity = error diff --git a/src/Microsoft.SqlServer.Server/IBinarySerialize.cs b/src/Microsoft.SqlServer.Server/IBinarySerialize.netstandard.cs similarity index 97% rename from src/Microsoft.SqlServer.Server/IBinarySerialize.cs rename to src/Microsoft.SqlServer.Server/IBinarySerialize.netstandard.cs index a975887161..a42c02146e 100644 --- a/src/Microsoft.SqlServer.Server/IBinarySerialize.cs +++ b/src/Microsoft.SqlServer.Server/IBinarySerialize.netstandard.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 NETSTANDARD + using System.IO; namespace Microsoft.SqlServer.Server @@ -19,3 +21,5 @@ public interface IBinarySerialize void Write(BinaryWriter w); } } + +#endif diff --git a/src/Microsoft.SqlServer.Server/InvalidUdtException.cs b/src/Microsoft.SqlServer.Server/InvalidUdtException.netstandard.cs similarity index 98% rename from src/Microsoft.SqlServer.Server/InvalidUdtException.cs rename to src/Microsoft.SqlServer.Server/InvalidUdtException.netstandard.cs index 395b02af61..87e12e64ac 100644 --- a/src/Microsoft.SqlServer.Server/InvalidUdtException.cs +++ b/src/Microsoft.SqlServer.Server/InvalidUdtException.netstandard.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 NETSTANDARD + using System; using System.Runtime.Serialization; @@ -49,3 +51,5 @@ public static InvalidUdtException Create(Type udtType, string resourceReason = " } } } + +#endif diff --git a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj index 74409ebf5e..ead90d9eac 100644 --- a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj +++ b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj @@ -1,40 +1,31 @@ + Microsoft.SqlServer.Server - This is the helper library for Microsoft.Data.SqlClient for cross framework compatibility support of UDT types. - Microsoft SqlServer Server net46;netstandard2.0 - portable - false - true - + + + $(SqlServerAssemblyVersion) + $(SqlServerFileVersion) + $(SqlServerPackageVersion) + - - $(Artifacts)/obj/$(Configuration)/$(AssemblyName) - - - $(Artifacts)/bin/$(Configuration)/$(AssemblyName) - - - $(Artifacts)/doc/$(TargetFramework)/$(AssemblyName).xml + + + + + $(RepoRoot)artifacts/ + $(ArtifactPath)$(AssemblyName)/$(Configuration)/ - + + $(AssemblyName) - $(SqlServerPackageVersion) - $(PackagesDir) true snupkg @@ -70,34 +61,9 @@ Microsoft.SqlServer.Server.Format - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - ResXFileCodeGenerator - Strings.Designer.cs - - - + + + + diff --git a/src/Microsoft.SqlServer.Server/SqlFacetAttribute.cs b/src/Microsoft.SqlServer.Server/SqlFacetAttribute.netstandard.cs similarity index 98% rename from src/Microsoft.SqlServer.Server/SqlFacetAttribute.cs rename to src/Microsoft.SqlServer.Server/SqlFacetAttribute.netstandard.cs index ba5cd82683..e2e3d555d8 100644 --- a/src/Microsoft.SqlServer.Server/SqlFacetAttribute.cs +++ b/src/Microsoft.SqlServer.Server/SqlFacetAttribute.netstandard.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 NETSTANDARD + using System; namespace Microsoft.SqlServer.Server @@ -49,3 +51,5 @@ public bool IsNullable } } } + +#endif diff --git a/src/Microsoft.SqlServer.Server/SqlFunctionAttribute.cs b/src/Microsoft.SqlServer.Server/SqlFunctionAttribute.netstandard.cs similarity index 99% rename from src/Microsoft.SqlServer.Server/SqlFunctionAttribute.cs rename to src/Microsoft.SqlServer.Server/SqlFunctionAttribute.netstandard.cs index 4d735cd77f..aed0b27d8b 100644 --- a/src/Microsoft.SqlServer.Server/SqlFunctionAttribute.cs +++ b/src/Microsoft.SqlServer.Server/SqlFunctionAttribute.netstandard.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 NETSTANDARD + using System; namespace Microsoft.SqlServer.Server @@ -102,3 +104,5 @@ public string FillRowMethodName } } } + +#endif diff --git a/src/Microsoft.SqlServer.Server/SqlMethodAttribute.cs b/src/Microsoft.SqlServer.Server/SqlMethodAttribute.netstandard.cs similarity index 98% rename from src/Microsoft.SqlServer.Server/SqlMethodAttribute.cs rename to src/Microsoft.SqlServer.Server/SqlMethodAttribute.netstandard.cs index a28afa2251..75af831b70 100644 --- a/src/Microsoft.SqlServer.Server/SqlMethodAttribute.cs +++ b/src/Microsoft.SqlServer.Server/SqlMethodAttribute.netstandard.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 NETSTANDARD + using System; namespace Microsoft.SqlServer.Server @@ -45,3 +47,5 @@ public bool InvokeIfReceiverIsNull } } } + +#endif diff --git a/src/Microsoft.SqlServer.Server/SqlUserDefinedAggregateAttribute.cs b/src/Microsoft.SqlServer.Server/SqlUserDefinedAggregateAttribute.netstandard.cs similarity index 99% rename from src/Microsoft.SqlServer.Server/SqlUserDefinedAggregateAttribute.cs rename to src/Microsoft.SqlServer.Server/SqlUserDefinedAggregateAttribute.netstandard.cs index 6e3e949f16..9a35e6b50d 100644 --- a/src/Microsoft.SqlServer.Server/SqlUserDefinedAggregateAttribute.cs +++ b/src/Microsoft.SqlServer.Server/SqlUserDefinedAggregateAttribute.netstandard.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 NETSTANDARD + using System; using System.Globalization; @@ -132,3 +134,5 @@ public string Name } } } + +#endif diff --git a/src/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.cs b/src/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.netstandard.cs similarity index 99% rename from src/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.cs rename to src/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.netstandard.cs index 0f24b89150..3f3103ee85 100644 --- a/src/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.cs +++ b/src/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.netstandard.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 NETSTANDARD + using System; using System.Globalization; @@ -138,3 +140,5 @@ public string Name } } } + +#endif diff --git a/src/Microsoft.SqlServer.Server/StringsHelper.cs b/src/Microsoft.SqlServer.Server/StringsHelper.netstandard.cs similarity index 99% rename from src/Microsoft.SqlServer.Server/StringsHelper.cs rename to src/Microsoft.SqlServer.Server/StringsHelper.netstandard.cs index 8f3915e18c..54d536ffb8 100644 --- a/src/Microsoft.SqlServer.Server/StringsHelper.cs +++ b/src/Microsoft.SqlServer.Server/StringsHelper.netstandard.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 NETSTANDARD + using System.Globalization; using System.Resources; using System.Runtime.CompilerServices; @@ -32,7 +34,7 @@ private static StringsHelper GetLoader() public static ResourceManager Resources => GetLoader()._resources; - // This method is used to decide if we need to append the exception message parameters to the message when calling Strings.Format. + // This method is used to decide if we need to append the exception message parameters to the message when calling Strings.Format. // by default it returns false. // Native code generators can replace the value this returns based on user input at the time of native code generation. // Marked as NoInlining because if this is used in an AoT compiled app that is not compiled into a single file, the user @@ -129,3 +131,5 @@ public static string Format(string resourceFormat, object p1, object p2, object } } } + +#endif diff --git a/src/Microsoft.SqlServer.Server/TypeForwards.cs b/src/Microsoft.SqlServer.Server/TypeForwards.netfx.cs similarity index 97% rename from src/Microsoft.SqlServer.Server/TypeForwards.cs rename to src/Microsoft.SqlServer.Server/TypeForwards.netfx.cs index 8f1161bce9..6c3cc7c2b3 100644 --- a/src/Microsoft.SqlServer.Server/TypeForwards.cs +++ b/src/Microsoft.SqlServer.Server/TypeForwards.netfx.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 NETFRAMEWORK + [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.SqlServer.Server.IBinarySerialize))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.SqlServer.Server.InvalidUdtException))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.SqlServer.Server.Format))] @@ -12,4 +14,5 @@ [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.SqlServer.Server.DataAccessKind))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Microsoft.SqlServer.Server.SystemDataAccessKind))] -namespace Microsoft.SqlServer.Server { } + +#endif diff --git a/src/Microsoft.SqlServer.Server/Versions.props b/src/Microsoft.SqlServer.Server/Versions.props new file mode 100644 index 0000000000..ec959cafbf --- /dev/null +++ b/src/Microsoft.SqlServer.Server/Versions.props @@ -0,0 +1,87 @@ + + + + + + + 1.0.0 + + + + + + + + $(SqlServerPackageVersion.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(SqlServerVersionDefault)-$(BuildSuffix)$(BuildNumber) + $(SqlServerVersionDefault) + + $(SqlServerVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + dev + + $(SqlServerVersionDefault)-$(BuildSuffix) + $(SqlServerVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) + + + + + + + $(SqlServerFileVersion.Split('.')[0]).0.0.0 + + diff --git a/tools/props/Versions.props b/tools/props/Versions.props deleted file mode 100644 index f2132677c9..0000000000 --- a/tools/props/Versions.props +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - 0 - - $(BuildNumber.Split('.')[0]) - - - - - - - - 1.0.0.0 - 1.0.0.0 - 1.0.0.$(AssemblyBuildNumber)-dev - $(SqlServerPackageVersion) - - - - - - - - - - - - - - - - diff --git a/tools/targets/GenerateThisAssemblyCs.targets b/tools/targets/GenerateThisAssemblyCs.targets index 8632d80dc4..6ed66d74ed 100644 --- a/tools/targets/GenerateThisAssemblyCs.targets +++ b/tools/targets/GenerateThisAssemblyCs.targets @@ -11,8 +11,8 @@ $(FileVersion) - - System + + $(AssemblyName) // Licensed to the .NET Foundation under one or more agreements. From 771b16cd95018814e15d6decaa3e68f0a8575eef Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Mon, 27 Apr 2026 23:03:51 -0500 Subject: [PATCH 069/298] [v7.1.0-preview1] Release Notes and vBump (#4241) * vbumpo * :robot: Release notes and updates to related agent stuff --- .../instructions/architecture.instructions.md | 15 +- .github/instructions/testing.instructions.md | 20 +- .../skills/generate-mstest-filter/SKILL.md | 2 +- CHANGELOG.md | 67 +++++- .../libraries/ci-build-variables.yml | 2 +- .../onebranch/variables/package-variables.yml | 6 +- release-notes/7.1/7.1.0-preview1.md | 193 ++++++++++++++++++ release-notes/7.1/README.md | 7 + release-notes/README.md | 1 + src/Microsoft.Data.SqlClient/Versions.props | 2 +- 10 files changed, 291 insertions(+), 24 deletions(-) create mode 100644 release-notes/7.1/7.1.0-preview1.md create mode 100644 release-notes/7.1/README.md diff --git a/.github/instructions/architecture.instructions.md b/.github/instructions/architecture.instructions.md index 3314babb7c..e1578592fe 100644 --- a/.github/instructions/architecture.instructions.md +++ b/.github/instructions/architecture.instructions.md @@ -34,10 +34,11 @@ src/ ## Unified Project Model ### Architecture Goal -The driver is transitioning away from separate `netfx/` and `netcore/` project files toward a **single unified project** at `src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj`. This project multi-targets all supported frameworks from one codebase: +The driver is transitioning away from separate `netfx/` and `netcore/` project files toward a **single unified project** at `src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj`. This project targets the modern .NET TFMs on every host and conditionally adds .NET Framework on Windows: ```xml -net462;net8.0;net9.0 +net8.0;net9.0 +$(TargetFrameworks);net462 ``` **All new code MUST go into `src/Microsoft.Data.SqlClient/src/`**. Do NOT add files to the legacy `netcore/src/` or `netfx/src/` directories. @@ -82,7 +83,7 @@ When writing code that differs by platform, use these preprocessor directives: | `#if _UNIX` | Code for Unix/Linux/macOS OS (any framework) | Guidelines: -1. All code must compile for all target frameworks (`net462`, `net8.0`, `net9.0`) +1. All code must compile for the TFMs supported by the current target OS: `net8.0`/`net9.0` everywhere, plus `net462` on Windows builds 2. Use `#if NETFRAMEWORK` or `#if NET` for framework-specific code paths 3. Use `#if _WINDOWS` or `#if _UNIX` for OS-specific code paths 4. Avoid APIs that don't exist on a target platform without conditional compilation @@ -104,11 +105,15 @@ The `ref/` directories define the public API surface: **IMPORTANT**: Any public API changes MUST update the corresponding reference assembly in the appropriate `ref/` directory. ### Build Output -Build artifacts are organized by framework and OS: +Build artifacts are organized by reference mode, configuration, OS, and framework: ``` -artifacts/Microsoft.Data.SqlClient/{Configuration}/{TargetOs}/{TargetFramework}/ +artifacts/Microsoft.Data.SqlClient/{ReferenceType}-{Configuration}/{NormalizedTargetOs}/{TargetFramework}/ ``` +`ReferenceType` is a first-class build dimension in this branch. Local and CI builds may run in: +- `Project` mode — sibling packages referenced as projects +- `Package` mode — sibling packages restored from locally produced NuGet packages + ## SNI (SQL Server Network Interface) Layer Two implementations exist: diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index 4f651a7104..b5e7d28479 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -9,6 +9,8 @@ applyTo: "**/tests/**,**/*Test*.cs" src/Microsoft.Data.SqlClient/tests/ ├── FunctionalTests/ # Tests without SQL Server dependency ├── ManualTests/ # Integration tests requiring SQL Server +├── PerformanceTests/ # Benchmark-style perf validation +├── StressTests/ # Long-running stress coverage ├── UnitTests/ # Unit tests with minimal dependencies └── tools/ └── Microsoft.Data.SqlClient.TestUtilities/ @@ -109,11 +111,11 @@ public async Task OpenAsync_TimingDependent_MayFail() { ... } All test runs use `--blame-hang-timeout 10m` to kill tests that hang for more than 10 minutes. This is configured in `build.proj` and applied to all test targets. If a test is expected to run longer than 10 minutes, it must be restructured or split. ### Test Filter Configuration -The default test filter is defined in `build.proj`: +The default test filter is defined in `build.proj` via `TestFilters`: ```xml -category!=failing&category!=flaky +category!=failing&category!=flaky&category!=interactive ``` -This can be overridden via MSBuild property: `msbuild -p:FilterStatement="your_filter"`. +This can be overridden via MSBuild property: `msbuild build.proj -t:TestSqlClientUnit -p:TestFilters="your_filter"`. ### Test Attributes ```csharp @@ -140,16 +142,16 @@ public void TestIntermittentlyFails() { ... } ### Using MSBuild (Recommended) ```bash # Build and run all unit tests -msbuild -t:RunUnitTests +msbuild build.proj -t:TestSqlClientUnit # Run functional tests only -msbuild -t:RunFunctionalTests +msbuild build.proj -t:TestSqlClientFunctional # Run manual tests for specific framework -msbuild -t:RunManualTests -p:TF=net8.0 +msbuild build.proj -t:TestSqlClientManual -p:TestFramework=net8.0 # Run specific test set -msbuild -t:RunManualTests -p:TestSet=1 +msbuild build.proj -t:TestSqlClientManual -p:TestSet=1 ``` ### Using dotnet CLI @@ -159,7 +161,7 @@ dotnet test "src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClie -p:Configuration=Release # Functional tests with filter (excludes failing, flaky, and interactive tests) -dotnet test "src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj" \ +dotnet test "src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj" \ --filter "category!=failing&category!=flaky&category!=interactive" # Run ONLY quarantined flaky tests (for investigation) @@ -323,7 +325,7 @@ AssertExtensions.ThrowsContains(() => action(), "expected message" ### Running with Coverage ```bash -msbuild -t:RunTests -p:CollectCoverage=true +msbuild build.proj -t:TestSqlClientUnit -p:TestCodeCoverage=true ``` ### Coverage Targets diff --git a/.github/skills/generate-mstest-filter/SKILL.md b/.github/skills/generate-mstest-filter/SKILL.md index 8441823347..3d2039259b 100644 --- a/.github/skills/generate-mstest-filter/SKILL.md +++ b/.github/skills/generate-mstest-filter/SKILL.md @@ -187,7 +187,7 @@ dotnet test --list-tests --filter "" --framework < ```bash # Generate filter for "ChannelDbConnectionPoolTest" class -dotnet test tests/UnitTests/UnitTests.csproj --list-tests --filter "FullyQualifiedName~ChannelDbConnectionPoolTest" --framework net9.0 +dotnet test src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj --list-tests --filter "FullyQualifiedName~ChannelDbConnectionPoolTest" --framework net9.0 # Expected output shows matching tests: # The following Tests are available: diff --git a/CHANGELOG.md b/CHANGELOG.md index c73574f086..a4682ab2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,65 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) > **Note:** Releases are sorted in reverse chronological order (newest first). +## [Preview Release 7.1.0-preview1] - 2026-04-29 + +This update brings the following changes since the [7.0.0](release-notes/7.0/7.0.0.md) release. +See the [full release notes](release-notes/7.1/7.1.0-preview1.md) for detailed descriptions. + +### Added + +- Added `SqlBatch` support on .NET Framework so the batching API is available across the full supported platform matrix. + ([#3926](https://github.com/dotnet/SqlClient/pull/3926)) + +- Added additional accepted connection-string synonyms for cross-driver alignment, including `ColumnEncryption`, `ConnectTimeout`, `FailoverPartner`, `PacketSize`, and `WorkstationId`. + ([#4192](https://github.com/dotnet/SqlClient/pull/4192)) + +### Changed + +- `SqlConnection.ClearPool` and `SqlConnection.ClearAllPools` now work correctly with the channel-based pool implementation. + ([#4194](https://github.com/dotnet/SqlClient/pull/4194)) + +- Added type forwards for public authentication abstractions moved into `Microsoft.Data.SqlClient.Extensions.Abstractions`. + ([#4067](https://github.com/dotnet/SqlClient/pull/4067), + [#4117](https://github.com/dotnet/SqlClient/pull/4117)) + +- Enabled the User Agent feature extension by default. + ([#4124](https://github.com/dotnet/SqlClient/pull/4124), + [#4154](https://github.com/dotnet/SqlClient/pull/4154)) + +- Reduced allocations when sending large string values, plus related source-build and packaging consolidation updates. + ([#4072](https://github.com/dotnet/SqlClient/pull/4072), + [#4033](https://github.com/dotnet/SqlClient/pull/4033), + [#4068](https://github.com/dotnet/SqlClient/pull/4068), + [#4204](https://github.com/dotnet/SqlClient/pull/4204)) + +### Fixed + +- Fixed `SqlBulkCopy` on SQL Server 2016 graph tables by extracting graph metadata with dynamic SQL. + ([#3714](https://github.com/dotnet/SqlClient/issues/3714), + [#4092](https://github.com/dotnet/SqlClient/pull/4092), + [#4147](https://github.com/dotnet/SqlClient/pull/4147)) + +- Fixed `SqlBulkCopy` support for Azure Synapse Analytics dedicated SQL pools. + ([#4149](https://github.com/dotnet/SqlClient/issues/4149), + [#4176](https://github.com/dotnet/SqlClient/pull/4176), + [#4182](https://github.com/dotnet/SqlClient/pull/4182)) + +- Fixed vector float32 metadata so `GetFieldType()` and `GetProviderSpecificFieldType()` return the expected vector type. + ([#4104](https://github.com/dotnet/SqlClient/issues/4104), + [#4105](https://github.com/dotnet/SqlClient/pull/4105), + [#4152](https://github.com/dotnet/SqlClient/pull/4152)) + +- Added the missing `System.Data.Common` dependency for .NET Framework consumers. + ([#4063](https://github.com/dotnet/SqlClient/pull/4063), + [#4074](https://github.com/dotnet/SqlClient/pull/4074)) + +- Fixed a `SqlDataReader` streaming bug triggered by calling `IsDBNull()` before reading streamed column data. + ([#4082](https://github.com/dotnet/SqlClient/pull/4082)) + +- Fixed a `NullReferenceException` in `SqlDataReader`. + ([#4159](https://github.com/dotnet/SqlClient/pull/4159)) + ## [Stable Release 7.0.1] - 2026-04-23 This update brings the following changes since the [7.0.0](release-notes/7.0/7.0.0.md) release. @@ -480,7 +539,7 @@ This update brings the following changes over the previous preview release: *What Changed:* -- Updated pipelines and test suites to compile the driver using the .NET 10 SDK. Cleaned up unnecessary dependency references. +- Updated pipelines and test suites to compile the driver using the .NET 10 SDK. Cleaned up unnecessary dependency references. ([#3686](https://github.com/dotnet/SqlClient/pull/3686)) *Who Benefits:* @@ -687,7 +746,7 @@ This update brings the following changes since [7.0.0-preview1.25257.1] #### Other changes -- Improve performance in `SqlStatistics` by using `Environment.TickCount` for calculating execution timing +- Improve performance in `SqlStatistics` by using `Environment.TickCount` for calculating execution timing ([#3609](https://github.com/dotnet/SqlClient/pull/3609)) - Improve performance in Always Encrypted scenarios by using lower-allocation primitives @@ -1359,7 +1418,7 @@ This update brings the below changes over the previous release: - Added support for Georgian collation [#2194](https://github.com/dotnet/SqlClient/pull/2194) - Added Localization support on .NET [#2210](https://github.com/dotnet/SqlClient/pull/2110) - Added .NET 8 support [#2230](https://github.com/dotnet/SqlClient/pull/2230) -- Added explicit version for major .NET version dependencies on System.Runtime.Caching 8.0.0, System.Configuration.ConfigurationManager 8.0.0, and System.Diagnostics. +- Added explicit version for major .NET version dependencies on System.Runtime.Caching 8.0.0, System.Configuration.ConfigurationManager 8.0.0, and System.Diagnostics. - DiagnosticSource 8.0.0 [#2303](https://github.com/dotnet/SqlClient/pull/2303) ### Fixed @@ -1597,7 +1656,7 @@ This update brings the below changes over the previous release: - Moved to new System.Data.SqlTypes APIs in **.NET 7** and upper. [1934](https://github.com/dotnet/SqlClient/pull/1934) and [#1981](https://github.com/dotnet/SqlClient/pull/1981) - Changed **[UseOneSecFloorInTimeoutCalculationDuringLogin](https://learn.microsoft.com/sql/connect/ado-net/appcontext-switches#enable-a-minimum-timeout-during-login)** App Context switch default to **true** and extended its effect to .NET and .NET Standard. [#2012](https://github.com/dotnet/SqlClient/pull/2012) -- Updated `Microsoft.Identity.Client` version from 4.47.2 to 4.53.0. [#2031](https://github.com/dotnet/SqlClient/pull/2031), [#2055](https://github.com/dotnet/SqlClient/pull/2055) +- Updated `Microsoft.Identity.Client` version from 4.47.2 to 4.53.0. [#2031](https://github.com/dotnet/SqlClient/pull/2031), [#2055](https://github.com/dotnet/SqlClient/pull/2055) - Code health improvement: [#1985](https://github.com/dotnet/SqlClient/pull/1985) ## [Stable Release 2.1.6] - 2023-04-27 diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 6d39d085b7..142e5e21fe 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -59,7 +59,7 @@ variables: # package version and build an assembly version from it. If the build number is included # before the "-" the build number will be appended again, generating an invalid file version. - name: mdsPackageVersion - value: 7.0.0-ci$(AssemblyBuildNumber) + value: 7.1.0-preview1ci$(AssemblyBuildNumber) # SqlServer library NuGet package version # NOTE: SqlServer's version props derive file version from the package version by appending the diff --git a/eng/pipelines/onebranch/variables/package-variables.yml b/eng/pipelines/onebranch/variables/package-variables.yml index b1466b06b9..0a84bc3a70 100644 --- a/eng/pipelines/onebranch/variables/package-variables.yml +++ b/eng/pipelines/onebranch/variables/package-variables.yml @@ -113,13 +113,13 @@ variables: value: 'drop_build_dependent_build_package_SqlClient' - name: sqlClientFileVersion - value: '7.0.0.$(_assemblyBuildNumber)' + value: '7.1.0.$(_assemblyBuildNumber)' - name: sqlClientPackageVersion ${{ if parameters.isPreview }}: - value: '7.0.0-preview4.$(Build.BuildNumber)' + value: '7.1.0-preview1.$(Build.BuildNumber)' ${{ else }}: - value: '7.0.0' + value: '7.1.0' # ------------------------------------------------------------------------ # SqlServer Package diff --git a/release-notes/7.1/7.1.0-preview1.md b/release-notes/7.1/7.1.0-preview1.md new file mode 100644 index 0000000000..beb95c6a7b --- /dev/null +++ b/release-notes/7.1/7.1.0-preview1.md @@ -0,0 +1,193 @@ +# Release Notes + +## Preview Release 7.1.0-preview1 - 2026-04-29 + +This update brings the following changes since the [7.0.0](../7.0/7.0.0.md) release: + +### Added + +#### `SqlBatch` Support on .NET Framework + +*What Changed:* + +- Added `SqlBatch` and related batch-command support for the .NET Framework target so the batching API is now available across the full supported platform matrix, including `net462`. + ([#3926](https://github.com/dotnet/SqlClient/pull/3926)) + +*Who Benefits:* + +- Applications that target .NET Framework but also want to use the newer batching APIs no longer need a separate implementation strategy from .NET 8/9 applications. +- Libraries that multi-target .NET Framework and modern .NET can use a more consistent data-access surface area. + +*Impact:* + +- `SqlBatch`, `SqlBatchCommand`, and the related execution methods are now usable on .NET Framework builds in addition to .NET. + +#### Cross-Driver Connection-String Synonym Alignment + +*What Changed:* + +- Added additional accepted connection-string synonyms for better compatibility with other SQL Server drivers and existing connection-string conventions. Newly accepted synonyms include `ColumnEncryption`, `ConnectTimeout`, `FailoverPartner`, `PacketSize`, and `WorkstationId`. + ([#4192](https://github.com/dotnet/SqlClient/pull/4192)) + +*Who Benefits:* + +- Applications migrating connection strings from other SQL Server drivers or shared infrastructure can reuse more existing keywords without rewriting them first. + +*Impact:* + +- Existing canonical keywords continue to work unchanged; this preview simply accepts more equivalent aliases during parsing. + +### Changed + +#### Connection Pool Clearing Now Works With Pool v2 (Experimental) + +*What Changed:* + +- Implemented the channel-based pool clear path so `SqlConnection.ClearPool(SqlConnection)` and `SqlConnection.ClearAllPools()` now work correctly when the newer pool v2 implementation is active. + ([#4194](https://github.com/dotnet/SqlClient/pull/4194)) + +*Who Benefits:* + +- Applications that rely on pool clearing after failover, credential rotation, or server-side configuration changes now get the same semantics regardless of which internal pool implementation is active. + +*Impact:* + +- Idle pooled connections are closed immediately during a clear operation, while in-use connections are invalidated and discarded when returned to the pool. + +*Please Note:* Connection Pooling v2 is still experiemental. + +#### Type Forwards for Authentication Abstractions + +*What Changed:* + +- Added type forwards from the core `Microsoft.Data.SqlClient` assembly to public authentication-related types that were moved into `Microsoft.Data.SqlClient.Extensions.Abstractions`, including `SqlAuthenticationMethod`, `SqlAuthenticationParameters`, `SqlAuthenticationProvider`, `SqlAuthenticationProviderException`, and `SqlAuthenticationToken`. + ([#4067](https://github.com/dotnet/SqlClient/pull/4067), + [#4117](https://github.com/dotnet/SqlClient/pull/4117)) + +*Who Benefits:* + +- Applications and libraries compiled against earlier package layouts retain binary compatibility when those authentication types are resolved from the core assembly name. + +*Impact:* + +- No application code changes are required; the type forwards preserve existing compiled references. + +#### User Agent Feature Extension Enabled by Default + +*What Changed:* + +- Enabled the User Agent TDS feature extension by default and removed the opt-in requirement to toggle it through `Switch.Microsoft.Data.SqlClient.EnableUserAgent`. + ([#4124](https://github.com/dotnet/SqlClient/pull/4124), + [#4154](https://github.com/dotnet/SqlClient/pull/4154)) + +*Who Benefits:* + +- Operators and diagnostic tooling gain more consistent client-identification metadata during connection establishment without extra application configuration. + +*Impact:* + +- User-agent metadata is now always sent during login. Applications that previously enabled the switch explicitly can remove that configuration with no functional loss. + +#### Other changes + +- Reduced allocations when sending large string values to SQL Server. + ([#4072](https://github.com/dotnet/SqlClient/pull/4072)) + +- Internal source-build and packaging consolidation updates: + ([#4033](https://github.com/dotnet/SqlClient/pull/4033), + [#4068](https://github.com/dotnet/SqlClient/pull/4068), + [#4204](https://github.com/dotnet/SqlClient/pull/4204)) + +### Fixed + +- Fixed `SqlBulkCopy` failing on SQL Server 2016 with `Invalid column name 'graph_type'` by extracting graph-column metadata with dynamic SQL instead of referencing the column directly. + ([#3714](https://github.com/dotnet/SqlClient/issues/3714), + [#4092](https://github.com/dotnet/SqlClient/pull/4092), + [#4147](https://github.com/dotnet/SqlClient/pull/4147)) + +- Fixed `SqlBulkCopy` support for Azure Synapse Analytics dedicated SQL pools by using a Synapse-compatible column-list query path. + ([#4149](https://github.com/dotnet/SqlClient/issues/4149), + [#4176](https://github.com/dotnet/SqlClient/pull/4176), + [#4182](https://github.com/dotnet/SqlClient/pull/4182)) + +- Fixed `SqlDataReader.GetFieldType()` and `GetProviderSpecificFieldType()` to return `SqlVector` metadata for vector float32 columns instead of `byte[]`. + ([#4104](https://github.com/dotnet/SqlClient/issues/4104), + [#4105](https://github.com/dotnet/SqlClient/pull/4105), + [#4152](https://github.com/dotnet/SqlClient/pull/4152)) + +- Added the missing `System.Data.Common` NuGet dependency for .NET Framework consumers to avoid `CS0012` compile failures against newer ADO.NET surface area. + ([#4063](https://github.com/dotnet/SqlClient/pull/4063), + [#4074](https://github.com/dotnet/SqlClient/pull/4074)) + +- Fixed a `SqlDataReader` streaming bug where calling `IsDBNull()` before reading a streamed value could skip column data. + ([#4082](https://github.com/dotnet/SqlClient/pull/4082)) + +- Fixed a `NullReferenceException` in `SqlDataReader` during specific reader state transitions. + ([#4159](https://github.com/dotnet/SqlClient/pull/4159)) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64, Windows ARM64) +- .NET 8.0+ (Windows x86, Windows x64, Windows ARM, Windows ARM64, Linux, macOS) + +### Dependencies + +#### .NET 9.0 + +- Microsoft.Bcl.Cryptography 9.0.13 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.13 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.13 +- System.Security.Cryptography.Pkcs 9.0.13 + +#### .NET 8.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET Standard 2.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 + +#### .NET Framework 4.6.2+ + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0 +- Microsoft.Data.SqlClient.Internal.Logging 1.0.0 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- System.Buffers 4.6.1 +- System.Data.Common 4.3.0 +- System.Diagnostics.DiagnosticSource 10.0.3 +- System.Memory 4.6.3 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 +- System.ValueTuple 4.6.2 diff --git a/release-notes/7.1/README.md b/release-notes/7.1/README.md new file mode 100644 index 0000000000..7bb05b1a70 --- /dev/null +++ b/release-notes/7.1/README.md @@ -0,0 +1,7 @@ +# Microsoft.Data.SqlClient 7.1 Releases + +The following Microsoft.Data.SqlClient 7.1 releases have been shipped: + +| Release Date | Version | Notes | +|:-------------|:---------------|:----------------------------------:| +| 2026-04-29 | 7.1.0-preview1 | [Release Notes](7.1.0-preview1.md) | diff --git a/release-notes/README.md b/release-notes/README.md index a511a589bc..f75cc4dfb9 100644 --- a/release-notes/README.md +++ b/release-notes/README.md @@ -4,6 +4,7 @@ The latest stable release is [Microsoft.Data.SqlClient 7.0](7.0). ## Release Information +- [Microsoft.Data.SqlClient 7.1](7.1) - [Microsoft.Data.SqlClient 7.0](7.0) - [Microsoft.Data.SqlClient 6.1](6.1) - [Microsoft.Data.SqlClient 6.0](6.0) diff --git a/src/Microsoft.Data.SqlClient/Versions.props b/src/Microsoft.Data.SqlClient/Versions.props index ea6b557467..5ec3b8b9ac 100644 --- a/src/Microsoft.Data.SqlClient/Versions.props +++ b/src/Microsoft.Data.SqlClient/Versions.props @@ -19,7 +19,7 @@ This is the *next* version to release of SqlClient. Update this after a version is released. @TODO: Determine how versions should be updated in release branches vs main --> - 7.0.0 + 7.1.0-preview1 From 407db24f97edf9298159a7ea5a277a2d07b3e08e Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 28 Apr 2026 07:55:41 -0300 Subject: [PATCH 070/298] Write release notes for 6.1.5 (#4169) --- CHANGELOG.md | 17 ++++++- release-notes/6.1/6.1.5.md | 88 +++++++++++++++++++++++++++++++++++++ release-notes/6.1/README.md | 1 + 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 release-notes/6.1/6.1.5.md diff --git a/CHANGELOG.md b/CHANGELOG.md index a4682ab2cb..f5fd80d19e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ 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/) - > **Note:** Releases are sorted in reverse chronological order (newest first). ## [Preview Release 7.1.0-preview1] - 2026-04-29 @@ -66,6 +65,22 @@ See the [full release notes](release-notes/7.1/7.1.0-preview1.md) for detailed d - Fixed a `NullReferenceException` in `SqlDataReader`. ([#4159](https://github.com/dotnet/SqlClient/pull/4159)) +## [Stable Release 6.1.5] - 2026-04-27 + +This update brings the following changes since the [6.1.4](release-notes/6.1/6.1.4.md) release. +See the [full release notes](release-notes/6.1/6.1.5.md) for target platform support and dependency information. + +### Fixed + +- Fixed a connection performance regression where SPN (Service Principal Name) generation was triggered for non-integrated authentication modes (e.g., SQL authentication) on the native SNI path, causing unnecessary DNS lookups and significantly slower connection times. + ([#3523](https://github.com/dotnet/SqlClient/issues/3523), [#3946](https://github.com/dotnet/SqlClient/pull/3946)) + +- Fixed `ExecuteScalar` to properly propagate errors when the server sends data followed by an error token. Previously, errors such as conversion failures during `WHERE` clause evaluation were silently consumed during `SqlDataReader.Close()` instead of being thrown to the caller, which could result in transactions being unexpectedly zombied. + ([#3736](https://github.com/dotnet/SqlClient/issues/3736), [#3947](https://github.com/dotnet/SqlClient/pull/3947)) + +- Fixed `SqlDataReader.GetFieldType` and `SqlDataReader.GetProviderSpecificFieldType` to return the correct type (`SqlVector`) for vector float32 columns. + ([#4104](https://github.com/dotnet/SqlClient/issues/4104), [#4151](https://github.com/dotnet/SqlClient/pull/4151)) + ## [Stable Release 7.0.1] - 2026-04-23 This update brings the following changes since the [7.0.0](release-notes/7.0/7.0.0.md) release. diff --git a/release-notes/6.1/6.1.5.md b/release-notes/6.1/6.1.5.md new file mode 100644 index 0000000000..5241f68a58 --- /dev/null +++ b/release-notes/6.1/6.1.5.md @@ -0,0 +1,88 @@ +# Release Notes + +## Stable Release 6.1.5 - 2026-04-27 + +This update brings the following changes since the [6.1.4](6.1.4.md) release: + +### Fixed + +- Fixed a connection performance regression where SPN (Service Principal Name) generation was triggered for non-integrated authentication modes (e.g., SQL authentication) on the native SNI path, causing unnecessary DNS lookups and significantly slower connection times. + ([#3523](https://github.com/dotnet/SqlClient/issues/3523), [#3946](https://github.com/dotnet/SqlClient/pull/3946)) + +- Fixed `ExecuteScalar` to properly propagate errors when the server sends data followed by an error token. Previously, errors such as conversion failures during `WHERE` clause evaluation were silently consumed during `SqlDataReader.Close()` instead of being thrown to the caller, which could result in transactions being unexpectedly zombied. + ([#3736](https://github.com/dotnet/SqlClient/issues/3736), [#3947](https://github.com/dotnet/SqlClient/pull/3947)) + +- Fixed `SqlDataReader.GetFieldType` and `SqlDataReader.GetProviderSpecificFieldType` to return the correct type (`SqlVector`) for vector float32 columns. Previously, these methods did not follow the same type-resolution logic as `GetValue`, returning an incorrect type for vector columns. + ([#4104](https://github.com/dotnet/SqlClient/issues/4104), [#4151](https://github.com/dotnet/SqlClient/pull/4151)) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64, Windows ARM64) +- .NET 8.0+ (Windows x86, Windows x64, Windows ARM64, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Linux, macOS) + +### Dependencies + +#### .NET Framework 4.6.2 + +- Azure.Core 1.50.0 +- Azure.Identity 1.17.1 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.80.0 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- System.Buffers 4.6.1 +- System.Data.Common 4.3.0 +- System.Diagnostics.DiagnosticSource 8.0.1 +- System.IdentityModel.Tokens.Jwt 7.7.1 +- System.Memory 4.6.3 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 8.0.6 +- System.Text.RegularExpressions 4.3.1 + +#### .NET 8.0 + +- Azure.Core 1.50.0 +- Azure.Identity 1.17.1 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.80.0 +- 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.Diagnostics.DiagnosticSource 8.0.1 +- System.IdentityModel.Tokens.Jwt 7.7.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET 9.0 + +- Azure.Core 1.50.0 +- Azure.Identity 1.17.1 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.11 +- Microsoft.Identity.Client 4.80.0 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.11 +- System.Diagnostics.DiagnosticSource 9.0.11 +- System.IdentityModel.Tokens.Jwt 7.7.1 +- System.Security.Cryptography.Pkcs 9.0.11 + +#### .NET Standard 2.0 + +- Azure.Core 1.50.0 +- Azure.Identity 1.17.1 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.80.0 +- 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.Diagnostics.DiagnosticSource 8.0.1 +- System.IdentityModel.Tokens.Jwt 7.7.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 8.0.6 diff --git a/release-notes/6.1/README.md b/release-notes/6.1/README.md index b068a545cc..ed5770b672 100644 --- a/release-notes/6.1/README.md +++ b/release-notes/6.1/README.md @@ -4,6 +4,7 @@ The following Microsoft.Data.SqlClient 6.1 stable releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2026-04-27 | 6.1.5 | [Release Notes](6.1.5.md) | | 2026-01-15 | 6.1.4 | [Release Notes](6.1.4.md) | | 2025-11-12 | 6.1.3 | [Release Notes](6.1.3.md) | | 2025-10-07 | 6.1.2 | [Release Notes](6.1.2.md) | From 86324fd11c1800d7a6e8e97edffdca869f984235 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Tue, 28 Apr 2026 21:00:28 +1000 Subject: [PATCH 071/298] Avoid redundant array alloc when TryReadSqlValueInternal for SQLVECTOR (#4156) --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 2d1807e7b5..29352c5460 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -7447,8 +7447,7 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp { // Note: Better not come here with plp data!! Debug.Assert(length <= TdsEnums.MAXSIZE); - byte[] b = new byte[length]; - result = stateObj.TryReadByteArrayWithContinue(length, isPlp: false, out b); + result = stateObj.TryReadByteArrayWithContinue(length, isPlp: false, out byte[] b); if (result != TdsOperationStatus.Done) { return result; From 3af6e09257eedd607f445c5647cf62550bb8aee9 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Tue, 28 Apr 2026 14:34:18 -0500 Subject: [PATCH 072/298] Add glob to ESRP search pattern (#4245) --- eng/pipelines/onebranch/jobs/build-buildproj-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml index c700a57701..7f12caaee4 100644 --- a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml +++ b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml @@ -155,7 +155,7 @@ jobs: authSignCertName: '${{ parameters.signingAuthSignCertName }}' esrpClientId: '${{ parameters.signingEsrpClientId }}' esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - pattern: '${{ parameters.packageFullName }}.dll' + pattern: '**/${{ parameters.packageFullName }}.dll' # Copy signed/unsigned DLLs and PDBs to APIScan folders. - task: CopyFiles@2 From f5942fd51f1ac2578cf286bd66eff1ad09222471 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Wed, 29 Apr 2026 14:14:15 +0530 Subject: [PATCH 073/298] Add float16 vector backward compatibility tests and refactor float32 vectors as varchar(max) test suite. (#4234) This commit refactors test suite for testing float32 vectors as varchar(max). Adds testcases for testing float16 vectors as varchar(max). Avoids test duplication and use RAII classes. --- .../ManualTests/DataCommon/DataTestUtility.cs | 63 ++ ...icrosoft.Data.SqlClient.ManualTests.csproj | 2 + ...t16VectorTypeBackwardCompatibilityTests.cs | 106 +++ .../VectorBackwardCompatTestBase.cs | 464 +++++++++++++ .../VectorTypeBackwardCompatibilityTests.cs | 638 ++---------------- 5 files changed, 698 insertions(+), 575 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/Float16VectorTypeBackwardCompatibilityTests.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorBackwardCompatTestBase.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 0145978d72..b66c72774e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -95,6 +95,7 @@ public static class DataTestUtility // SQL Server capabilities private static bool? s_isDataClassificationSupported; private static bool? s_isVectorSupported; + private static bool? s_isVectorFloat16Supported; // 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 @@ -156,6 +157,68 @@ public static string SQLServerVersion s_isVectorSupported ??= IsTCPConnStringSetup() && IsTypePresent("vector"); + /// + /// Determines whether the SQL Server supports the 'float16' base type for the 'vector' data type. + /// + /// + /// Probes the server by first executing + /// ALTER DATABASE SCOPED CONFIGURATION SET PREVIEW_FEATURES = ON; and then executing + /// DECLARE @v AS VECTOR(5, float16) = '[1.0, 1.0, 1.0, 1.0, 1.0]'; SELECT @v;. + /// As a side effect, this enables preview features for the current database. If the server does not + /// recognize float16 as a vector base type, if the server does not support the required syntax, + /// if the current principal lacks permission to change the database scoped configuration, or if another + /// server-side error occurs while running the probe, this method returns + /// . Implies . + /// + /// if the 'float16' vector base type is supported; otherwise, . + public static bool IsSqlVectorFloat16Supported => + s_isVectorFloat16Supported ??= IsTCPConnStringSetup() && + IsSqlVectorSupported && + CheckVectorFloat16Supported(); + + private static bool CheckVectorFloat16Supported() + { + try + { + using SqlConnection connection = new(TCPConnectionString); + // Enable preview features (required while float16 is in preview), then declare a + // float16 vector and select it back. Without a negotiated float16 feature extension, + // the server returns the value as a varchar(max) JSON string, which the client can + // safely read. We use 1.0 (exactly representable in IEEE-754 binary16) so the + // round-tripped JSON matches the input bit-for-bit. Any failure (server parse + // error such as Msg 195 "'float16' is not a recognized vector base type.", lack of + // permission to set PREVIEW_FEATURES, etc.) means we cannot exercise the float16 + // path. + float[] expected = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + using SqlCommand command = new( + "ALTER DATABASE SCOPED CONFIGURATION SET PREVIEW_FEATURES = ON;" + + "DECLARE @v AS VECTOR(5, float16) = '[1.0, 1.0, 1.0, 1.0, 1.0]'; SELECT @v;", + connection); + connection.Open(); + using SqlDataReader reader = command.ExecuteReader(); + if (!reader.Read()) + { + return false; + } + string json = reader.GetString(0); + float[] actual = System.Text.Json.JsonSerializer.Deserialize(json); + return actual != null && actual.Length == expected.Length + && actual.AsSpan().SequenceEqual(expected); + } + catch (SqlException) + { + // Server-side failure (e.g. Msg 195 "'float16' is not a recognized vector base + // type.", Msg 102 syntax errors on older servers, lack of permission to set + // PREVIEW_FEATURES) — float16 path is unavailable. + return false; + } + catch (System.Text.Json.JsonException) + { + // Server returned a payload we can't parse as a float[] JSON array. + return false; + } + } + static DataTestUtility() { Config c = Config.Load(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 1c1553e6c1..cf7bbf236a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -243,8 +243,10 @@ + + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/Float16VectorTypeBackwardCompatibilityTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/Float16VectorTypeBackwardCompatibilityTests.cs new file mode 100644 index 0000000000..2ea6a10ab8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/Float16VectorTypeBackwardCompatibilityTests.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest +{ + /// + /// Provides parameterized test data for backward compatibility tests that exchange + /// float16 vector data as varchar(max) JSON strings. + /// + public static class Float16VarcharVectorTestData + { + // Values chosen to be exactly representable in IEEE-754 binary16 (float16), + // so JSON round-trips through a vector(N, float16) column without precision loss. + public static readonly float[] TestData = { 1.0f, 2.0f, 3.0f }; + + /// + /// Generates test cases for all 4 SqlParameter construction patterns x 2 value types (non-null + null). + /// Each case yields: [int pattern, string jsonOrNull, float[] expectedData] + /// where jsonOrNull is null when testing DBNull insertion. + /// + public static IEnumerable GetVarcharVectorInsertTestData() + { + string json = JsonSerializer.Serialize(TestData); + + // Pattern 1-4 with non-null JSON value + yield return new object[] { 1, json, TestData }; + yield return new object[] { 2, json, TestData }; + yield return new object[] { 3, json, TestData }; + yield return new object[] { 4, json, TestData }; + + // Pattern 1-4 with null value + yield return new object[] { 1, null, null }; + yield return new object[] { 2, null, null }; + yield return new object[] { 3, null, null }; + yield return new object[] { 4, null, null }; + } + } + + public sealed class Float16VectorTypeBackwardCompatibilityTests : VectorBackwardCompatTestBase + { + public Float16VectorTypeBackwardCompatibilityTests(ITestOutputHelper output) + : base(output, columnDefinition: "vector(3, float16)", namePrefix: "VectorF16") + { + } + + protected override float[] GetPrepareTestValues(int i) => + new float[] { i + 1, i + 2, i + 3 }; + + #region Insert Tests + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorFloat16Supported))] + [MemberData(nameof(Float16VarcharVectorTestData.GetVarcharVectorInsertTestData), MemberType = typeof(Float16VarcharVectorTestData), DisableDiscoveryEnumeration = true)] + public void TestVectorDataInsertionAsVarchar(int pattern, string jsonValue, float[] expectedData) + => InsertAndValidateAsVarchar(pattern, jsonValue, expectedData); + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorFloat16Supported))] + [MemberData(nameof(Float16VarcharVectorTestData.GetVarcharVectorInsertTestData), MemberType = typeof(Float16VarcharVectorTestData), DisableDiscoveryEnumeration = true)] + public async Task TestVectorDataInsertionAsVarcharAsync(int pattern, string jsonValue, float[] expectedData) + => await InsertAndValidateAsVarcharAsync(pattern, jsonValue, expectedData); + + #endregion + + #region Stored Procedure Tests + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorFloat16Supported))] + public void TestStoredProcParamsForVectorAsVarchar() + => StoredProcRoundTrip(Float16VarcharVectorTestData.TestData); + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorFloat16Supported))] + public async Task TestStoredProcParamsForVectorAsVarcharAsync() + => await StoredProcRoundTripAsync(Float16VarcharVectorTestData.TestData); + + #endregion + + #region Bulk Copy Tests + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorFloat16Supported))] + [InlineData(1)] + [InlineData(2)] + public void TestSqlBulkCopyForVectorAsVarchar(int bulkCopySourceMode) + => BulkCopyRoundTrip(bulkCopySourceMode, Float16VarcharVectorTestData.TestData); + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorFloat16Supported))] + [InlineData(1)] + [InlineData(2)] + public async Task TestSqlBulkCopyForVectorAsVarcharAsync(int bulkCopySourceMode) + => await BulkCopyRoundTripAsync(bulkCopySourceMode, Float16VarcharVectorTestData.TestData); + + #endregion + + #region Prepared Statement Tests + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorFloat16Supported))] + public void TestInsertVectorsAsVarcharWithPrepare() + => PreparedInsertRoundTrip(); + + #endregion + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorBackwardCompatTestBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorBackwardCompatTestBase.cs new file mode 100644 index 0000000000..1b2d4c792f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorBackwardCompatTestBase.cs @@ -0,0 +1,464 @@ +// 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.Text.Json; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest +{ + /// + /// Abstract base class for vector backward compatibility tests that exchange + /// vector data as varchar(max) JSON strings. Eliminates duplication between + /// float32 and float16 test classes while allowing each to specify its own + /// ConditionalFact/ConditionalTheory predicates. + /// + public abstract class VectorBackwardCompatTestBase : IDisposable + { + private static readonly string s_connectionString = DataTestUtility.TCPConnectionString; + private static readonly string s_vectorParamName = "@VectorData"; + + private readonly SqlConnection _connection; + private readonly Table _vectorTable; + private readonly Table _bulkCopySrcTable; + private readonly StoredProcedure _storedProc; + + private readonly string _selectCmdString; + private readonly string _insertCmdString; + + protected ITestOutputHelper Output { get; } + + /// + /// Generates the test data values to use with Prepare tests. + /// Float32 uses fractional values (e.g. i+0.1f), float16 uses whole numbers (e.g. i+1). + /// + protected abstract float[] GetPrepareTestValues(int i); + + protected VectorBackwardCompatTestBase( + ITestOutputHelper output, + string columnDefinition, + string namePrefix) + { + Output = output; + _connection = new SqlConnection(s_connectionString); + _connection.Open(); + + _vectorTable = new Table(_connection, namePrefix + "TestTable", + $"(Id INT PRIMARY KEY IDENTITY, VectorData {columnDefinition} NULL)"); + + _bulkCopySrcTable = new Table(_connection, namePrefix + "BulkCopyTestTable", + "(Id INT PRIMARY KEY IDENTITY, VectorData varchar(max) NULL)"); + + string 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 {_vectorTable.Name} (VectorData) + VALUES (@InputVectorJson); + + -- Retrieve latest entry (assumes auto-incrementing ID) + SELECT TOP 1 @OutputVectorJson = VectorData + FROM {_vectorTable.Name} + ORDER BY Id DESC; + END;"; + + _storedProc = new StoredProcedure(_connection, namePrefix + "AsVarcharSp", storedProcBody); + + _selectCmdString = $"SELECT VectorData FROM {_vectorTable.Name} ORDER BY Id DESC"; + _insertCmdString = $"INSERT INTO {_vectorTable.Name} (VectorData) VALUES (@VectorData)"; + } + + public void Dispose() + { + // RAII objects drop themselves on Dispose in reverse order. + _storedProc?.Dispose(); + _bulkCopySrcTable?.Dispose(); + _vectorTable?.Dispose(); + _connection?.Dispose(); + } + + #region Shared Helpers + + /// + /// Creates a SqlParameter with varchar(max) type based on the specified pattern. + /// Mirrors the 4 parameter construction patterns tested in NativeVectorFloat32Tests + /// but uses SqlDbType.VarChar instead of SqlDbTypeExtensions.Vector. + /// + private static SqlParameter CreateVarcharParameter(int pattern, string jsonValue) + { + object value = jsonValue != null ? (object)jsonValue : DBNull.Value; + + return pattern switch + { + // Pattern 1: Default constructor + property setters + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = SqlDbType.VarChar, + Size = -1, // varchar(max) + Value = value + }, + // Pattern 2: Name + value constructor + 2 => new SqlParameter(s_vectorParamName, value), + // Pattern 3: Name + SqlDbType constructor + 3 => new SqlParameter(s_vectorParamName, SqlDbType.VarChar) { Value = value }, + // Pattern 4: Name + SqlDbType + Size constructor + 4 => new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + } + + private void ValidateInsertedData(SqlConnection connection, float[] expectedData) + { + using var selectCmd = new SqlCommand(_selectCmdString, connection); + using var reader = selectCmd.ExecuteReader(); + Assert.True(reader.Read(), "No data found in the table."); + + if (expectedData != null) + { + // Validate non-null data through all string read APIs + string result = reader.GetString(0); + Assert.Equal(expectedData, JsonSerializer.Deserialize(result)); + + result = reader.GetSqlString(0).Value; + Assert.Equal(expectedData, JsonSerializer.Deserialize(result)); + + result = reader.GetFieldValue(0); + Assert.Equal(expectedData, JsonSerializer.Deserialize(result)); + } + else + { + Assert.True(reader.IsDBNull(0), "Expected null but got non-null value."); + Assert.Equal(DBNull.Value, reader.GetValue(0)); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetSqlString(0).Value); + Assert.Throws(() => reader.GetFieldValue(0)); + } + } + + private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] expectedData) + { + using var selectCmd = new SqlCommand(_selectCmdString, connection); + using var reader = await selectCmd.ExecuteReaderAsync(); + Assert.True(await reader.ReadAsync(), "No data found in the table."); + + if (expectedData != null) + { + // Validate non-null data through all string read APIs + string result = reader.GetString(0); + Assert.Equal(expectedData, JsonSerializer.Deserialize(result)); + + result = reader.GetSqlString(0).Value; + Assert.Equal(expectedData, JsonSerializer.Deserialize(result)); + + result = await reader.GetFieldValueAsync(0); + Assert.Equal(expectedData, JsonSerializer.Deserialize(result)); + } + else + { + Assert.True(await reader.IsDBNullAsync(0), "Expected null but got non-null value."); + Assert.Equal(DBNull.Value, reader.GetValue(0)); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetSqlString(0).Value); + await Assert.ThrowsAsync(async () => await reader.GetFieldValueAsync(0)); + } + } + + #endregion + + #region Test Implementations + + protected void InsertAndValidateAsVarchar(int pattern, string jsonValue, float[] expectedData) + { + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + + using var insertCmd = new SqlCommand(_insertCmdString, conn); + SqlParameter param = CreateVarcharParameter(pattern, jsonValue); + insertCmd.Parameters.Add(param); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + + ValidateInsertedData(conn, expectedData); + } + + protected async Task InsertAndValidateAsVarcharAsync(int pattern, string jsonValue, float[] expectedData) + { + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + + using var insertCmd = new SqlCommand(_insertCmdString, conn); + SqlParameter param = CreateVarcharParameter(pattern, jsonValue); + insertCmd.Parameters.Add(param); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + + await ValidateInsertedDataAsync(conn, expectedData); + } + + protected void StoredProcRoundTrip(float[] data) + { + string dataAsJson = JsonSerializer.Serialize(data); + + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + + using var command = new SqlCommand(_storedProc.Name, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Test with non-null value + 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); + + command.ExecuteNonQuery(); + + var dbDataAsJson = outputParam.Value as string; + Assert.NotNull(dbDataAsJson); + Assert.Equal(data, JsonSerializer.Deserialize(dbDataAsJson)); + + // Test with null value + command.Parameters.Clear(); + inputParam.Value = DBNull.Value; + command.Parameters.Add(inputParam); + command.Parameters.Add(outputParam); + command.ExecuteNonQuery(); + + Assert.Equal(DBNull.Value, outputParam.Value); + } + + protected async Task StoredProcRoundTripAsync(float[] data) + { + string dataAsJson = JsonSerializer.Serialize(data); + + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + + using var command = new SqlCommand(_storedProc.Name, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Test with non-null value + 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); + + await command.ExecuteNonQueryAsync(); + + var dbDataAsJson = outputParam.Value as string; + Assert.NotNull(dbDataAsJson); + Assert.Equal(data, JsonSerializer.Deserialize(dbDataAsJson)); + + // Test with null value + command.Parameters.Clear(); + inputParam.Value = DBNull.Value; + command.Parameters.Add(inputParam); + command.Parameters.Add(outputParam); + + await command.ExecuteNonQueryAsync(); + + Assert.Equal(DBNull.Value, outputParam.Value); + } + + protected void BulkCopyRoundTrip(int bulkCopySourceMode, float[] testData) + { + string testDataAsJson = JsonSerializer.Serialize(testData); + + using var sourceConnection = new SqlConnection(s_connectionString); + sourceConnection.Open(); + using var destinationConnection = new SqlConnection(s_connectionString); + destinationConnection.Open(); + + DataTable table = null; + switch (bulkCopySourceMode) + { + case 1: + // Use SQL Server table as source with varchar(max) data + using (var insertCmd = new SqlCommand($"insert into {_bulkCopySrcTable.Name} 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()); + } + break; + case 2: + // Use DataTable as source + table = new DataTable(_bulkCopySrcTable.Name); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("VectorData", typeof(string)); + table.Rows.Add(1, testDataAsJson); + table.Rows.Add(2, DBNull.Value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + DestinationTableName = _vectorTable.Name, + }; + + switch (bulkCopySourceMode) + { + case 1: + using (SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {_bulkCopySrcTable.Name}", sourceConnection)) + using (SqlDataReader reader = sourceDataCommand.ExecuteReader()) + { + bulkCopy.WriteToServer(reader); + } + break; + case 2: + bulkCopy.WriteToServer(table); + break; + } + + // Verify that 2 rows were copied + Assert.Equal(2, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Read data from destination table and verify + using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {_vectorTable.Name}", destinationConnection); + using SqlDataReader verifyReader = verifyCommand.ExecuteReader(); + + Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy."); + Assert.False(verifyReader.IsDBNull(0), "First row in the table is null."); + Assert.Equal(testData, JsonSerializer.Deserialize(verifyReader.GetString(0))); + + Assert.True(verifyReader.Read(), "Second row not found in the table"); + Assert.True(verifyReader.IsDBNull(0)); + } + + protected async Task BulkCopyRoundTripAsync(int bulkCopySourceMode, float[] testData) + { + string testDataAsJson = JsonSerializer.Serialize(testData); + + using var sourceConnection = new SqlConnection(s_connectionString); + await sourceConnection.OpenAsync(); + using var destinationConnection = new SqlConnection(s_connectionString); + await destinationConnection.OpenAsync(); + + DataTable table = null; + switch (bulkCopySourceMode) + { + case 1: + // Use SQL Server table as source with varchar(max) data + using (var insertCmd = new SqlCommand($"insert into {_bulkCopySrcTable.Name} 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()); + } + break; + case 2: + // Use DataTable as source + table = new DataTable(_bulkCopySrcTable.Name); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("VectorData", typeof(string)); + table.Rows.Add(1, testDataAsJson); + table.Rows.Add(2, DBNull.Value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + DestinationTableName = _vectorTable.Name, + }; + + switch (bulkCopySourceMode) + { + case 1: + using (SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {_bulkCopySrcTable.Name}", sourceConnection)) + using (SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync()) + { + await bulkCopy.WriteToServerAsync(reader); + } + break; + case 2: + await bulkCopy.WriteToServerAsync(table); + break; + } + + // Verify that 2 rows were copied + Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Read data from destination table and verify + using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {_vectorTable.Name}", destinationConnection); + using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(); + + Assert.True(await verifyReader.ReadAsync(), "No data found in destination table after bulk copy."); + Assert.False(verifyReader.IsDBNull(0), "First row in the table is null."); + Assert.Equal(testData, JsonSerializer.Deserialize(verifyReader.GetString(0))); + + Assert.True(await verifyReader.ReadAsync(), "Second row not found in the table"); + Assert.True(await verifyReader.IsDBNullAsync(0)); + } + + protected void PreparedInsertRoundTrip() + { + using SqlConnection conn = new SqlConnection(s_connectionString); + conn.Open(); + using SqlCommand command = new SqlCommand(_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(GetPrepareTestValues(i)); + command.ExecuteNonQuery(); + } + using SqlCommand validateCommand = new SqlCommand($"SELECT VectorData FROM {_vectorTable.Name}", conn); + using SqlDataReader reader = validateCommand.ExecuteReader(); + int rowcnt = 0; + while (reader.Read()) + { + float[] expectedData = GetPrepareTestValues(rowcnt); + float[] dbData = JsonSerializer.Deserialize(reader.GetString(0))!; + Assert.Equal(expectedData, dbData); + rowcnt++; + } + Assert.Equal(10, rowcnt); + } + + #endregion + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs index b3f42a5617..9db91d767f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs @@ -2,10 +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.Collections.Generic; -using System.Data; -using System.Data.SqlTypes; using System.Text.Json; using System.Threading.Tasks; using Xunit; @@ -13,604 +10,95 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest { - public sealed class VectorTypeBackwardCompatibilityTests : IDisposable + /// + /// Provides parameterized test data for backward compatibility tests that exchange + /// vector data as varchar(max) JSON strings. + /// + public static class VarcharVectorTestData { - private readonly ITestOutputHelper _output; - private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; - private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); - private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); - private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData varchar(max) NULL)"; - private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector(3) NULL)"; - private static readonly string s_selectCmdString = $"SELECT VectorData FROM {s_tableName} ORDER BY Id DESC"; - private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} (VectorData) VALUES (@VectorData)"; - private static readonly string s_vectorParamName = $"@VectorData"; - private static readonly string s_storedProcName = DataTestUtility.GetShortName("VectorsAsVarcharSp"); - private static readonly string s_storedProcBody = $@" - @InputVectorJson VARCHAR(MAX), -- Input: Serialized float[] as JSON string - @OutputVectorJson VARCHAR(MAX) OUTPUT -- Output: Echoed back from latest inserted row - 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.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] - public void TestVectorDataInsertionAsVarchar() + public static readonly float[] TestData = { 1.1f, 2.2f, 3.3f }; + + /// + /// Generates test cases for all 4 SqlParameter construction patterns x 2 value types (non-null + null). + /// Each case yields: [int pattern, string jsonOrNull, float[] expectedData] + /// where jsonOrNull is null when testing DBNull insertion. + /// + public static IEnumerable GetVarcharVectorInsertTestData() { - 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); - } + string json = JsonSerializer.Serialize(TestData); + + // Pattern 1-4 with non-null JSON value + yield return new object[] { 1, json, TestData }; + yield return new object[] { 2, json, TestData }; + yield return new object[] { 3, json, TestData }; + yield return new object[] { 4, json, TestData }; + + // Pattern 1-4 with null value + yield return new object[] { 1, null, null }; + yield return new object[] { 2, null, null }; + yield return new object[] { 3, null, null }; + yield return new object[] { 4, null, null }; } + } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] - public async Task TestVectorParameterInitializationAsync() + public sealed class VectorTypeBackwardCompatibilityTests : VectorBackwardCompatTestBase + { + public VectorTypeBackwardCompatibilityTests(ITestOutputHelper output) + : base(output, columnDefinition: "vector(3)", namePrefix: "Vector") { - 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.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] - 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); + protected override float[] GetPrepareTestValues(int i) => + new float[] { i + 0.1f, i + 0.2f, i + 0.3f }; - //Read using GetFieldValue - result = reader.GetFieldValue(0); - dbData = JsonSerializer.Deserialize(result)!; - Assert.Equal(data, dbData); + #region Insert Tests - reader.Close(); + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] + [MemberData(nameof(VarcharVectorTestData.GetVarcharVectorInsertTestData), MemberType = typeof(VarcharVectorTestData), DisableDiscoveryEnumeration = true)] + public void TestVectorDataInsertionAsVarchar(int pattern, string jsonValue, float[] expectedData) + => InsertAndValidateAsVarchar(pattern, jsonValue, expectedData); - // Validate For Null Value - insertCmd.Parameters.Clear(); - p1.Value = DBNull.Value; - insertCmd.Parameters.Add(p1); - Assert.Equal(1, insertCmd.ExecuteNonQuery()); + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] + [MemberData(nameof(VarcharVectorTestData.GetVarcharVectorInsertTestData), MemberType = typeof(VarcharVectorTestData), DisableDiscoveryEnumeration = true)] + public async Task TestVectorDataInsertionAsVarcharAsync(int pattern, string jsonValue, float[] expectedData) + => await InsertAndValidateAsVarcharAsync(pattern, jsonValue, expectedData); - //Validate Reader for null value - reader = verifyCommand.ExecuteReader(); - Assert.True(reader.Read(), "No data found in the table."); + #endregion - //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.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] - 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)); - } + #region Stored Procedure Tests [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] 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); - } + => StoredProcRoundTrip(VarcharVectorTestData.TestData); [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] 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 StoredProcRoundTripAsync(VarcharVectorTestData.TestData); - await command.ExecuteNonQueryAsync(); + #endregion - // Validate output parameter for null value - Assert.True(outputParam.Value == DBNull.Value); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] - 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.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] - 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, - }; + #region Bulk Copy Tests - 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}"); - } + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] + [InlineData(1)] + [InlineData(2)] + public void TestSqlBulkCopyForVectorAsVarchar(int bulkCopySourceMode) + => BulkCopyRoundTrip(bulkCopySourceMode, VarcharVectorTestData.TestData); - // Verify that the 2 rows from the source table have been copied into the destination table. - Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] + [InlineData(1)] + [InlineData(2)] + public async Task TestSqlBulkCopyForVectorAsVarcharAsync(int bulkCopySourceMode) + => await BulkCopyRoundTripAsync(bulkCopySourceMode, VarcharVectorTestData.TestData); - // 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(); + #endregion - // 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)); - } + #region Prepared Statement Tests [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSqlVectorSupported))] 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); - } + => PreparedInsertRoundTrip(); + + #endregion } } From 061c45bc4610473e67a01b863828e6888279211e Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Wed, 29 Apr 2026 12:39:03 -0500 Subject: [PATCH 074/298] ESRP sign resources files, too (#4248) --- eng/pipelines/onebranch/jobs/build-buildproj-job.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml index 7f12caaee4..6eda6a3d1c 100644 --- a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml +++ b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml @@ -155,7 +155,9 @@ jobs: authSignCertName: '${{ parameters.signingAuthSignCertName }}' esrpClientId: '${{ parameters.signingEsrpClientId }}' esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - pattern: '**/${{ parameters.packageFullName }}.dll' + pattern: | + **/${{ parameters.packageFullName }}.dll + **/${{ parameters.packageFullName }}.resources.dll # Copy signed/unsigned DLLs and PDBs to APIScan folders. - task: CopyFiles@2 From d6e20fc845eb8bdb6ab9ebe0345ddcccb423fbf7 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Wed, 29 Apr 2026 23:20:20 -0500 Subject: [PATCH 075/298] [v7.1.0-preview1] ESRP Nuget Signing Path Fix (#4249) --- .../onebranch/jobs/build-buildproj-job.yml | 4 ++-- .../onebranch/steps/pack-buildproj-step.yml | 13 +------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml index 6eda6a3d1c..3fa44c3ce6 100644 --- a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml +++ b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml @@ -201,8 +201,8 @@ jobs: authSignCertName: '${{ parameters.signingAuthSignCertName }}' esrpClientId: '${{ parameters.signingEsrpClientId }}' esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - searchPath: '$(PACK_OUTPUT)' - searchPattern: '${{ parameters.packageFullName}}.*nupkg' + searchPath: '$(BUILD_OUTPUT)' + searchPattern: '**/${{ parameters.packageFullName}}.*nupkg' # Copy the contents of the build output for this package to the artifacts folder - task: CopyFiles@2 diff --git a/eng/pipelines/onebranch/steps/pack-buildproj-step.yml b/eng/pipelines/onebranch/steps/pack-buildproj-step.yml index a0e6e02426..96bba59c20 100644 --- a/eng/pipelines/onebranch/steps/pack-buildproj-step.yml +++ b/eng/pipelines/onebranch/steps/pack-buildproj-step.yml @@ -5,7 +5,7 @@ ################################################################################# # Generic pack step for build.proj packages. This step runs the Pack* target to generate a NuGet -# package of the target package. The generated NuGet packages will then be copied to $(PACK_OUTPUT). +# package of the target package. # # Note: This step assumes that the package has been built previously using a Build* target. # `-p:PackBuild=false` will be passed to msbuild to ensure the previously built assemblies are @@ -71,14 +71,3 @@ steps: - script: tree /a /f $(BUILD_OUTPUT) displayName: Output Build Output Tree - - - task: CopyFiles@2 - displayName: 'Copy NuGet Packages to PACK_OUTPUT' - inputs: - contents: '**/${{ parameters.packageFullName }}*.*nupkg' - flattenFolders: true - sourceFolder: '$(BUILD_OUTPUT)/${{ parameters.packageFullName }}/' - targetFolder: '$(PACK_OUTPUT)' - - - script: tree /a /f $(PACK_OUTPUT) - displayName: Output Package Output Tree From 7a01dbe66eb55ee8e5310fd13e7e8c4dfcea4f1f Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Thu, 30 Apr 2026 22:42:45 +0530 Subject: [PATCH 076/298] Fix non-deterministic NuGet packages by enabling ContinuousIntegrationBuild for OneBranch builds (#4251) --- .../onebranch/sqlclient-non-official.yml | 8 ++++++ .../onebranch/sqlclient-official.yml | 7 +++++ src/Directory.Build.props | 26 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index 4ae301020f..882e7e0658 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -96,6 +96,14 @@ variables: parameters: isPreview: ${{ parameters.isPreview }} + # Drives ContinuousIntegrationBuild=true in src/Directory.Build.props so this + # pipeline produces deterministic packages identical (modulo signing) to the + # official pipeline. Keeping this enabled here ensures the deterministic + # build path is exercised on every non-official run, catching regressions + # before they reach the official release pipeline. + - name: BuildForRelease + value: true + resources: repositories: - repository: templates diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index 96fb0dc5f5..07b8da1b17 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -118,6 +118,13 @@ variables: parameters: isPreview: ${{ parameters.isPreview }} + # Drives ContinuousIntegrationBuild=true in src/Directory.Build.props so the shipped + # DLLs/PDBs embed portable /_/... source paths and the produced NuGet packages pass + # NuGet Package Explorer's "Deterministic (build)" check. Set on both OneBranch + # pipelines; PR/CI builds leave it unset (false). + - name: BuildForRelease + value: true + resources: repositories: - repository: templates diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 6ec3fba9a8..3ed4e8cfb0 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -126,6 +126,32 @@ true true false + + + false + + + true + + + $(BuildForRelease) From 11293878debfa177c6b63daf87e0653bdb250767 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Fri, 1 May 2026 07:33:39 -0500 Subject: [PATCH 077/298] [7.1.0-preview1] Fix TFM Attributes (#4254) --- src/Directory.Build.props | 5 ----- src/Microsoft.Data.SqlClient.slnx | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3ed4e8cfb0..e760ebe9f3 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -57,11 +57,6 @@ $(BuildNumber.Split('.')[0]) - - - $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) - - diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index 1b6b3440f8..d65c3edc90 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -134,6 +134,7 @@ + From 6d26a9e6c9c26a31678db5d179a2e4f57f8f5eba Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 4 May 2026 12:22:44 -0700 Subject: [PATCH 078/298] Fix package path for release (#4258) --- .../onebranch/jobs/publish-nuget-package-job.yml | 2 +- eng/pipelines/onebranch/stages/release-stages.yml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml b/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml index 21d36b9640..25f1586f09 100644 --- a/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml +++ b/eng/pipelines/onebranch/jobs/publish-nuget-package-job.yml @@ -74,7 +74,7 @@ jobs: value: $(Pipeline.Workspace)/${{ parameters.artifactName }} - name: packageToPush - value: $(artifactPath)/${{ coalesce(parameters.packagePath, format('packages/{0}.*.nupkg', parameters.packageName)) }} + value: $(artifactPath)/${{ coalesce(parameters.packagePath, format('Package-Release/{0}.*.nupkg', parameters.packageName)) }} # Template context inputs are used to pass parameters to the deployment job since it doesn't # automatically download pipeline artifacts. diff --git a/eng/pipelines/onebranch/stages/release-stages.yml b/eng/pipelines/onebranch/stages/release-stages.yml index 6ee3fbc88e..45309a77c6 100644 --- a/eng/pipelines/onebranch/stages/release-stages.yml +++ b/eng/pipelines/onebranch/stages/release-stages.yml @@ -193,7 +193,7 @@ stages: parameters: packageName: Microsoft.SqlServer.Server artifactName: '${{ parameters.sqlServerArtifactsName }}' - packagePath: 'packages/Microsoft.SqlServer.Server.${{ parameters.sqlServerPackageVersion }}.nupkg' + packagePath: 'Package-Release/Microsoft.SqlServer.Server.${{ parameters.sqlServerPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -203,7 +203,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Internal.Logging artifactName: '${{ parameters.loggingArtifactsName }}' - packagePath: 'packages/Microsoft.Data.SqlClient.Internal.Logging.${{ parameters.loggingPackageVersion }}.nupkg' + packagePath: 'Package-Release/Microsoft.Data.SqlClient.Internal.Logging.${{ parameters.loggingPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -213,7 +213,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Extensions.Abstractions artifactName: '${{ parameters.abstractionsArtifactsName }}' - packagePath: 'packages/Microsoft.Data.SqlClient.Extensions.Abstractions.${{ parameters.abstractionsPackageVersion }}.nupkg' + packagePath: 'Package-Release/Microsoft.Data.SqlClient.Extensions.Abstractions.${{ parameters.abstractionsPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -223,7 +223,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient artifactName: '${{ parameters.sqlClientArtifactsName }}' - packagePath: 'packages/Microsoft.Data.SqlClient.${{ parameters.sqlClientPackageVersion }}.nupkg' + packagePath: 'Package-Release/Microsoft.Data.SqlClient.${{ parameters.sqlClientPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -233,7 +233,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Extensions.Azure artifactName: '${{ parameters.azureArtifactsName }}' - packagePath: 'packages/Microsoft.Data.SqlClient.Extensions.Azure.${{ parameters.azurePackageVersion }}.nupkg' + packagePath: 'Package-Release/Microsoft.Data.SqlClient.Extensions.Azure.${{ parameters.azurePackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -243,7 +243,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider artifactName: '${{ parameters.akvProviderArtifactsName }}' - packagePath: 'packages/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.${{ parameters.akvProviderPackageVersion }}.nupkg' + packagePath: 'Package-Release/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.${{ parameters.akvProviderPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} From b1c082e5c137d70667e2f451c5f6b2e60c109430 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 4 May 2026 20:56:09 -0700 Subject: [PATCH 079/298] Remove unnecessary inheritance structure from pool key class. (#4235) --- ...nectionPoolKey.cs => ConnectionPoolKey.cs} | 37 ++++++------ .../ConnectionPool/DbConnectionPoolGroup.cs | 6 +- .../ConnectionPool/DbConnectionPoolKey.cs | 58 ------------------- .../Microsoft/Data/SqlClient/SqlConnection.cs | 18 +++--- .../Data/SqlClient/SqlConnectionFactory.cs | 28 ++++----- .../ConnectionPoolHelper.cs | 2 +- .../ChannelDbConnectionPoolTest.cs | 16 ++--- ...itHandleDbConnectionPoolTransactionTest.cs | 4 +- 8 files changed, 56 insertions(+), 113 deletions(-) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/{SqlConnectionPoolKey.cs => ConnectionPoolKey.cs} (75%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolKey.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ConnectionPoolKey.cs similarity index 75% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolKey.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ConnectionPoolKey.cs index 31da3521df..1499154cf0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolKey.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ConnectionPoolKey.cs @@ -6,14 +6,14 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -using Microsoft.Data.Common; 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 - internal class SqlConnectionPoolKey : DbConnectionPoolKey + // ConnectionPoolKey: Key to connection pool groups for SqlConnection + // Connection string, SqlCredential, access token, access token callback, and SSPI context provider are used as a key + internal class ConnectionPoolKey : ICloneable { + private string _connectionString; private int _hashValue; private readonly SqlCredential _credential; private readonly string _accessToken; @@ -25,24 +25,25 @@ internal class SqlConnectionPoolKey : DbConnectionPoolKey internal Func> AccessTokenCallback => _accessTokenCallback; internal SspiContextProvider SspiContextProvider => _sspiContextProvider; - internal override string ConnectionString + internal string ConnectionString { - get => base.ConnectionString; + get => _connectionString; set { - base.ConnectionString = value; + _connectionString = value; CalculateHashCode(); } } - internal SqlConnectionPoolKey( + internal ConnectionPoolKey( string connectionString, SqlCredential credential, string accessToken, Func> accessTokenCallback, - SspiContextProvider sspiContextProvider) : base(connectionString) + SspiContextProvider sspiContextProvider) { Debug.Assert(credential == null || accessToken == null || accessTokenCallback == null, "Credential, AccessToken, and Callback can't have a value at the same time."); + _connectionString = connectionString; _credential = credential; _accessToken = accessToken; _accessTokenCallback = accessTokenCallback; @@ -50,26 +51,26 @@ internal SqlConnectionPoolKey( CalculateHashCode(); } - private SqlConnectionPoolKey(SqlConnectionPoolKey key) : base(key) + private ConnectionPoolKey(ConnectionPoolKey key) { - _credential = key.Credential; - _accessToken = key.AccessToken; + _connectionString = key._connectionString; + _credential = key._credential; + _accessToken = key._accessToken; _accessTokenCallback = key._accessTokenCallback; _sspiContextProvider = key._sspiContextProvider; - CalculateHashCode(); } - public override object Clone() + public object Clone() { - return new SqlConnectionPoolKey(this); + return new ConnectionPoolKey(this); } public override bool Equals(object obj) { - return (obj is SqlConnectionPoolKey key + return (obj is ConnectionPoolKey key && _credential == key._credential - && ConnectionString == key.ConnectionString + && _connectionString == key._connectionString && _accessTokenCallback == key._accessTokenCallback && string.CompareOrdinal(_accessToken, key._accessToken) == 0 && _sspiContextProvider == key._sspiContextProvider); @@ -82,7 +83,7 @@ public override int GetHashCode() private void CalculateHashCode() { - _hashValue = base.GetHashCode(); + _hashValue = _connectionString == null ? 0 : _connectionString.GetHashCode(); if (_credential != null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index fc14e62d58..f1bf850582 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -34,7 +34,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool sealed internal class DbConnectionPoolGroup { private readonly DbConnectionOptions _connectionOptions; - private readonly DbConnectionPoolKey _poolKey; + private readonly ConnectionPoolKey _poolKey; private readonly DbConnectionPoolGroupOptions _poolGroupOptions; private ConcurrentDictionary _poolCollection; @@ -50,7 +50,7 @@ sealed internal class DbConnectionPoolGroup private const int PoolGroupStateIdle = 2; // all pools are pruned via Clear private const int PoolGroupStateDisabled = 4; // factory pool entry pruning method - internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, DbConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions) + internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, ConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions) { Debug.Assert(connectionOptions != null, "null connection options"); @@ -68,7 +68,7 @@ internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, DbConnecti internal DbConnectionOptions ConnectionOptions => _connectionOptions; - internal DbConnectionPoolKey PoolKey => _poolKey; + internal ConnectionPoolKey PoolKey => _poolKey; internal DbConnectionPoolGroupProviderInfo ProviderInfo { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolKey.cs deleted file mode 100644 index bfcd4cd2ef..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolKey.cs +++ /dev/null @@ -1,58 +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.ConnectionPool -{ - // DbConnectionPoolKey: Base class implementation of a key to connection pool groups - // Only connection string is used as a key - internal class DbConnectionPoolKey : ICloneable - { - private string _connectionString; - - internal DbConnectionPoolKey(string connectionString) - { - _connectionString = connectionString; - } - - protected DbConnectionPoolKey(DbConnectionPoolKey key) - { - _connectionString = key.ConnectionString; - } - - public virtual object Clone() - { - return new DbConnectionPoolKey(this); - } - - internal virtual string ConnectionString - { - get - { - return _connectionString; - } - - set - { - _connectionString = value; - } - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - return (obj is DbConnectionPoolKey key && _connectionString == key._connectionString); - } - - public override int GetHashCode() - { - return _connectionString == null ? 0 : _connectionString.GetHashCode(); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index b96e6f654d..127f47e19d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -708,7 +708,7 @@ public override string ConnectionString CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback(connectionOptions); } } - ConnectionString_Set(new SqlConnectionPoolKey(value, _credential, _accessToken, _accessTokenCallback, _sspiContextProvider)); + ConnectionString_Set(new ConnectionPoolKey(value, _credential, _accessToken, _accessTokenCallback, _sspiContextProvider)); _connectionString = value; // Change _connectionString value only after value is validated CacheConnectionStringProperties(); } @@ -768,7 +768,7 @@ public string AccessToken } // Need to call ConnectionString_Set to do proper pool group check - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: value, accessTokenCallback: null, sspiContextProvider: null)); + ConnectionString_Set(new ConnectionPoolKey(_connectionString, credential: _credential, accessToken: value, accessTokenCallback: null, sspiContextProvider: null)); _accessToken = value; } } @@ -791,7 +791,7 @@ public Func _poolGroupsToRelease; private readonly List _poolsToRelease; private readonly Timer _pruningTimer; - private Dictionary _connectionPoolGroups; + private Dictionary _connectionPoolGroups; #endregion @@ -53,7 +53,7 @@ internal class SqlConnectionFactory protected SqlConnectionFactory() { - _connectionPoolGroups = new Dictionary(); + _connectionPoolGroups = new Dictionary(); _poolsToRelease = new List(); _poolGroupsToRelease = new List(); _pruningTimer = ADP.UnsafeCreateTimer( @@ -97,7 +97,7 @@ internal void ClearPool(DbConnection connection) poolGroup?.Clear(); } - internal void ClearPool(DbConnectionPoolKey key) + internal void ClearPool(ConnectionPoolKey key) { ADP.CheckArgumentNull(key.ConnectionString, $"{nameof(key)}.{nameof(key.ConnectionString)}"); @@ -174,7 +174,7 @@ internal DbConnectionInternal CreatePooledConnection( } internal DbConnectionPoolGroup GetConnectionPoolGroup( - DbConnectionPoolKey key, + ConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, ref DbConnectionOptions userConnectionOptions) { @@ -202,7 +202,7 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup( if ((object)expandedConnectionString != (object)key.ConnectionString) { // CONSIDER: caching the original string to reduce future parsing - DbConnectionPoolKey newKey = (DbConnectionPoolKey)key.Clone(); + ConnectionPoolKey newKey = (ConnectionPoolKey)key.Clone(); newKey.ConnectionString = expandedConnectionString; return GetConnectionPoolGroup(newKey, null, ref userConnectionOptions); } @@ -233,9 +233,9 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup( }; // build new dictionary with space for new connection string - Dictionary newConnectionPoolGroups = - new Dictionary(1 + _connectionPoolGroups.Count); - foreach (KeyValuePair entry in _connectionPoolGroups) + Dictionary newConnectionPoolGroups = + new Dictionary(1 + _connectionPoolGroups.Count); + foreach (KeyValuePair entry in _connectionPoolGroups) { newConnectionPoolGroups.Add(entry.Key, entry.Value); } @@ -469,7 +469,7 @@ internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo DbConnectionOptions connectionOptions) => new SqlConnectionPoolGroupProviderInfo((SqlConnectionString)connectionOptions); - internal SqlConnectionString FindSqlConnectionOptions(SqlConnectionPoolKey key) + internal SqlConnectionString FindSqlConnectionOptions(ConnectionPoolKey key) { Debug.Assert(key is not null, "Key cannot be null"); @@ -576,14 +576,14 @@ internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionIntern // @TODO: I think this could be broken down into methods more specific to use cases above protected virtual DbConnectionInternal CreateConnection( DbConnectionOptions options, - DbConnectionPoolKey poolKey, + ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) { SqlConnectionString opt = (SqlConnectionString)options; - SqlConnectionPoolKey key = (SqlConnectionPoolKey)poolKey; + ConnectionPoolKey key = poolKey; SessionData recoverySessionData = null; SqlConnection sqlOwningConnection = owningConnection as SqlConnection; @@ -899,10 +899,10 @@ private void PruneConnectionPoolGroups(object state) // one. This will cause any empty pools to be put into the release list. lock (this) { - Dictionary connectionPoolGroups = _connectionPoolGroups; - Dictionary newConnectionPoolGroups = new Dictionary(connectionPoolGroups.Count); + Dictionary connectionPoolGroups = _connectionPoolGroups; + Dictionary newConnectionPoolGroups = new Dictionary(connectionPoolGroups.Count); - foreach (KeyValuePair entry in connectionPoolGroups) + foreach (KeyValuePair entry in connectionPoolGroups) { if (entry.Value != null) { 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 acaf348a52..adfb0bbb6c 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 @@ -20,7 +20,7 @@ internal static class ConnectionPoolHelper 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_sqlConnectionFactory = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.SqlConnectionFactory"); - private static Type s_dbConnectionPoolKey = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolKey"); + private static Type s_dbConnectionPoolKey = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.ConnectionPoolKey"); private static Type s_dictStringPoolGroup = typeof(Dictionary<,>).MakeGenericType(s_dbConnectionPoolKey, s_dbConnectionPoolGroup); private static Type s_dictPoolIdentityPool = typeof(ConcurrentDictionary<,>).MakeGenericType(s_dbConnectionPoolIdentity, s_dbConnectionPool); // Resolve Count from the interface so it works with both pool implementations diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index c7df9dc2c6..48bcc87ad2 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -37,7 +37,7 @@ private ChannelDbConnectionPool ConstructPool(SqlConnectionFactory connectionFac ); dbConnectionPoolGroup ??= new DbConnectionPoolGroup( new DbConnectionOptions("DataSource=localhost;", null), - new DbConnectionPoolKey("TestDataSource"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions ); return new ChannelDbConnectionPool( @@ -615,7 +615,7 @@ public void TestPoolGroup() { var dbConnectionPoolGroup = new DbConnectionPoolGroup( new DbConnectionOptions("DataSource=localhost;", null), - new DbConnectionPoolKey("TestDataSource"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, @@ -936,7 +936,7 @@ internal class SuccessfulSqlConnectionFactory : SqlConnectionFactory { protected override DbConnectionInternal CreateConnection( DbConnectionOptions options, - DbConnectionPoolKey poolKey, + ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, @@ -950,7 +950,7 @@ internal class TimeoutSqlConnectionFactory : SqlConnectionFactory { protected override DbConnectionInternal CreateConnection( DbConnectionOptions options, - DbConnectionPoolKey poolKey, + ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, @@ -1007,7 +1007,7 @@ public void Constructor_WithZeroMaxPoolSize_ThrowsArgumentOutOfRangeException() ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( new DbConnectionOptions("DataSource=localhost;", null), - new DbConnectionPoolKey("TestDataSource"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions ); @@ -1038,7 +1038,7 @@ public void Constructor_WithLargeMaxPoolSize() ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( new DbConnectionOptions("DataSource=localhost;", null), - new DbConnectionPoolKey("TestDataSource"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions ); @@ -1079,7 +1079,7 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() ); var dbConnectionPoolGroup1 = new DbConnectionPoolGroup( new DbConnectionOptions("DataSource=localhost;", null), - new DbConnectionPoolKey("TestDataSource"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions1 ); @@ -1105,7 +1105,7 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() ); var dbConnectionPoolGroup2 = new DbConnectionPoolGroup( new DbConnectionOptions("DataSource=localhost;", null), - new DbConnectionPoolKey("TestDataSource"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions2 ); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs index cafea0dad4..bc637087a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs @@ -60,7 +60,7 @@ private WaitHandleDbConnectionPool CreatePool( var dbConnectionPoolGroup = new DbConnectionPoolGroup( new DbConnectionOptions("DataSource=localhost;", null), - new DbConnectionPoolKey("TestDataSource"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions ); @@ -901,7 +901,7 @@ internal class MockSqlConnectionFactory : SqlConnectionFactory { protected override DbConnectionInternal CreateConnection( DbConnectionOptions options, - DbConnectionPoolKey poolKey, + ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, From 4820ba4323449a16fb890cf0041e47f0086431ec Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Mon, 4 May 2026 22:05:53 -0700 Subject: [PATCH 080/298] Minor touch-ups to BuildGuide and build.proj (#4246) --- BUILDGUIDE.md | 34 +++++++++++++++++----------------- build.proj | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 7c8e4acb93..715923d1b3 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -16,11 +16,11 @@ on operating systems that do not support .NET Framework. As such, it is not nece ### Miscellaneous -**Powershell** is required to run several miscellaneous tasks as part of building and packaging. On Windows systems, -no action is required. On Linux and MacOS systems, the `pwsh` command is required to be in the `$PATH` environment +**PowerShell** is required to run several miscellaneous tasks as part of building and packaging. On Windows systems, +no action is required. On Linux and macOS systems, the `pwsh` command is required to be in the `$PATH` environment variable. For specific instructions see: [Install PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell) -The **NuGet** binary is required to package the Microsoft.Data.SqlClient project. For convenience, this can be be done +The **NuGet** binary is required to package the Microsoft.Data.SqlClient project. For convenience, this can be done via the PowerShell script [tools/scripts/downloadLatestNuget.ps1](tools/scripts/downloadLatestNuget.ps1), however, any `nuget.exe` binary can be used. @@ -35,12 +35,12 @@ package the project. The `build.proj` file provides convenient targets to accomp > is recommended that `build.proj` is used for local development, as well. > [!TIP] -> `build.proj` was written with the intention of being called from `msbuild`. As such, the following examples below +> `build.proj` was written with the intention of being called from `msbuild`. As such, the examples below > use `msbuild`. On systems where `msbuild` is not available, simply replace `msbuild` with `dotnet msbuild` to get the > same behavior. > [!TIP] -> This section is not exhaustive of all targets or parameters to `build.proj`. Complete documentation is avilable in +> This section is not exhaustive of all targets or parameters to `build.proj`. Complete documentation is available in > [`build.proj`](build.proj). ### Building Projects @@ -73,7 +73,7 @@ A selection of parameters for build targets in `build.proj` can be found below: | `[optional_parameter]` | Allowed Values | Default | Description | |-----------------------------------|----------------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------| | `-p:Configuration=` | `Debug`, `Release` | `Debug` | Build configuration | -| `-p:PackageVersion | `major.minor.patch[-prerelease]` | `[blank]` | Version to assign to the target package. Assembly and file versions are derived from this, if it is provided. See Versioning for more details | +| `-p:PackageVersion=` | `major.minor.patch[-prerelease]` | `[blank]` | Version to assign to the target package, where `` can be one of: `['Abstractions', 'Azure', 'AkvProvider', 'Logging', 'SqlClient', 'SqlServer']`. Assembly and file versions are derived from this, if it is provided. See Versioning for more details | For most projects, build output is placed in `artifacts//Project-/`. `` is the full name of the package, `` is the build configuration, and `` is the target framework @@ -97,7 +97,7 @@ msbuild build.proj -t:BuildSqlClient -p:Configuration=Release Build v1.2.3 of Microsoft.Data.SqlClient.Extensions.Abstractions: ```bash -msbuild build.proj -t:BuildAbstractions -p:PackageVersion=1.2.3 +msbuild build.proj -t:BuildAbstractions -p:PackageVersionAbstractions=1.2.3 ``` ### Testing Projects @@ -165,15 +165,15 @@ msbuild build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 ### Packaging Projects -Just like bulding and testing the various projects in this repository, packaging the projects into NuGet packages is -also handle by `build.proj`. From the root of your repository, run `msbuild` against `build.proj` with a test target, +Just like building and testing the various projects in this repository, packaging the projects into NuGet packages is +also handled by `build.proj`. From the root of your repository, run `msbuild` against `build.proj` with a pack target, following this pattern: ```bash msbuild build.proj -t: [optional_parameters] ``` -| `` | Desription | +| `` | Description | |--------------------|-------------------------------------------------------------------------------------| | `Pack` | Packages all projects in the repository. | | `PackAbstractions` | Packages the Microsoft.Data.SqlClient.Extensions.Abstractions package | @@ -191,9 +191,9 @@ A selection of parameters for pack targets in `build.proj` relevant to common de | `[optional_parameter]` | Default Value | Allowed Values | Description | |------------------------------------|---------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| | `-p:Configuration=` | `Debug` | `Debug`, `Release` | Build configuration. Only applies if project and dependencies are being built. | -| `-p:NugetPath=` | `[blank]` | eg. `C:\my\nuget.exe` | _Only applies to `PackSqlClient`._ Path to `nuget.exe` that to use. If not provided, defaults to `nuget.exe` in the PATH. | +| `-p:NugetPath=` | `[blank]` | eg. `C:\my\nuget.exe` | _Only applies to `PackSqlClient`._ Path to `nuget.exe` to use. If not provided, defaults to `nuget.exe` in the PATH. | | `-p:PackBuild=` | `true` | `true`, `false` | Whether or not to build the project before packing. If `false`, project must be built using the same parameters. | -| `-p:PackageVersion` | `[blank]` | eg. `1.2.3-dev123` | Version to assign to the package. If `PackBuild` is `true`, the assembly and file versions will be derived from this version. See Versioning for more details. | +| `-p:PackageVersion=` | `[blank]` | eg. `1.2.3-dev123` | Version to assign to the package, where `` can be one of: `['Abstractions', 'Azure', 'AkvProvider', 'Logging', 'SqlClient', 'SqlServer']`. If `PackBuild` is `true`, the assembly and file versions will be derived from this version. See Versioning for more details. | #### Examples @@ -212,7 +212,7 @@ Package version 1.2.3 of Microsoft.Data.SqlClient.Extensions.Abstractions: msbuild build.proj -t:PackAbstractions -p:PackageVersionAbstractions=1.2.3 ``` -Package version Microsoft.Data.SqlClient.Extensions.Azure without building it beforehand: +Package Microsoft.Data.SqlClient.Extensions.Azure without building it beforehand: ```bash msbuild build.proj -t:PackAzure -p:PackBuild=false ``` @@ -236,7 +236,7 @@ as the latest released version of the package. For the table below, we assume th |------------------|---------------|---------------|------------------|------------------|---------------|------------------------------------------------------------| | N/A | N/A | N/A | `1.2.3-dev` | `1.0.0` | `1.2.3.0` | Standard developer scenario | | `9.8.7` | N/A | N/A | `9.8.7` | `9.0.0` | `9.8.7.0` | Developer is building a specific version of the package | -| `9.8.7-preview1` | N/A | N/A | `9.8.7-preview1` | `9.0.0` | `9.8.7.0` | Developer is buildins a pre-release version of the package | +| `9.8.7-preview1` | N/A | N/A | `9.8.7-preview1` | `9.0.0` | `9.8.7.0` | Developer is building a pre-release version of the package | | N/A | `1234` | N/A | `1.2.3` | `1.0.0` | `1.2.3.1234` | Automated pipelines building GA releases | | N/A | `1234` | `ci` | `1.2.3-ci1234` | `1.0.0` | `1.2.3.1234` | Automated pipelines building non-prod releases | @@ -247,7 +247,7 @@ as the latest released version of the package. For the table below, we assume th The above documentation is the default mode of operation, and is the recommended mode for most developers. However, `build.proj` supports "package mode" builds. In this mode, instead of projects depending on other projects, they depend on NuGet packages. This mode is useful for verifying that packages work with each other, especially in automated -build scenarios. For completeness, and debugging of autoamted builds, this section documents behavior of "package mode". +build scenarios. For completeness, and debugging of automated builds, this section documents behavior of "package mode". To switch to "package mode", set the `ReferenceType` parameter in `build.proj` to `Package`. And, optionally, include one or more of the following parameters: @@ -297,7 +297,7 @@ msbuild -t:PackSqlClient \ cp artifacts/Microsoft.Data.SqlClient/Package-Debug/*.*pkg packages/ ``` -Run Microsoft.Data.SqlClient functional tests against the versions build above: +Run Microsoft.Data.SqlClient functional tests against the versions built above: ```bash msbuild build.proj -t:TestSqlClientFunctional \ -p:ReferenceType=Package \ @@ -422,7 +422,7 @@ 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 +Optionally, to avoid polluting your git workspace and requiring 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. diff --git a/build.proj b/build.proj index e32caef276..a0ef562934 100644 --- a/build.proj +++ b/build.proj @@ -155,7 +155,7 @@ --> - -p:AzurePackageVersion=$(PackageVersionAbstractions) + -p:AzurePackageVersion=$(PackageVersionAzure) + + + "$(DotnetPath)dotnet" msbuild "$(AbstractionsProjectPath)" + -nologo + -verbosity:quiet + -getProperty:AbstractionsPackageVersion + $(BuildNumberArgument) + $(BuildSuffixArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(GetAbstractionsPackageVersionCommand), "\s+", " ")) + + + + + + + + + + + "$(DotnetPath)dotnet" msbuild "$(LoggingProjectPath)" + -nologo + -verbosity:quiet + -getProperty:LoggingPackageVersion + $(BuildNumberArgument) + $(BuildSuffixArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(GetLoggingPackageVersionCommand), "\s+", " ")) + + + + + + + <_EvaluatedSqlClientPackageVersion>$([System.Text.RegularExpressions.Regex]::Replace($(_EvaluatedSqlClientPackageVersion), "\s", "")) + $([System.Text.RegularExpressions.Regex]::Replace($(PackageVersionAbstractions), "\s", "")) + $([System.Text.RegularExpressions.Regex]::Replace($(PackageVersionLogging), "\s", "")) $([System.Text.RegularExpressions.Regex]::Replace($(CommitId), "\s", "")) "$(NugetPath)" pack "$(SqlClientNuspecPath)" From 34b400ea544c5ff9edde8d89024dc74a2350134c Mon Sep 17 00:00:00 2001 From: Shreya Laxminarayana Rao <147193825+ShreyaLaxminarayan@users.noreply.github.com> Date: Tue, 5 May 2026 11:00:28 +0530 Subject: [PATCH 081/298] Fix Kerberos SSPI authentication failure when using TCP proxy in manual tests (#4146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: CreateAndStartProxy used in manual tests rewrites the connection string address to 127.0.0.1, causing kerberos to construct the wrong SPN (MSSQLSvc/localhost) instead of the real server name. Since localhost is not registered in Active Directory, SSPI authentication fails with Microsoft.Data.SqlClient.SqlException: The target principal name is incorrect. Cannot generate SSPI context. Fix: Capture the original server name before the proxy rewrites the address. After rewriting, explicitly set ServerSPN in the connection string to the real server's SPN using DataTestUtility.GetMachineFQDN, but only if ServerSPN is not already set by the caller. Tests Affected: SqlCommandCancelTest.TimeOutDuringRead_Tcp— previously failing with SSPI error, now passing --- .../tests/ManualTests/DataCommon/ProxyServer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ProxyServer.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ProxyServer.cs index c53b554f18..4c8376ef9f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ProxyServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ProxyServer.cs @@ -321,6 +321,7 @@ public static ProxyServer CreateAndStartProxy(string connectionString, out strin // Build builders SqlConnectionStringBuilder connStringbuilder = new SqlConnectionStringBuilder(connectionString); DataSourceBuilder dataSourceBuilder = new DataSourceBuilder(connStringbuilder.DataSource); + string originalServerName = dataSourceBuilder.ServerName; // Setup proxy Task ipEntryTask = Dns.GetHostEntryAsync(dataSourceBuilder.ServerName); @@ -338,6 +339,11 @@ public static ProxyServer CreateAndStartProxy(string connectionString, out strin connStringbuilder.DataSource = dataSourceBuilder.ToString(); connStringbuilder.Remove("Network Library"); + if (string.IsNullOrEmpty(connStringbuilder.ServerSPN)) + { + connStringbuilder.ServerSPN = $"MSSQLSvc/{DataTestUtility.GetMachineFQDN(originalServerName)}"; + } + newConnectionString = connStringbuilder.ToString(); return proxy; } From fdbc56e7b9b87d8a7043603b22ebf4a4bab35be8 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Tue, 5 May 2026 15:48:02 +0100 Subject: [PATCH 082/298] Fix | SqlVector: Explicitly perform little-endian multibyte writes (#3861) --- .../src/Microsoft/Data/SqlTypes/SqlVector.cs | 88 +++++++++++++------ .../Microsoft/Data/SqlTypes/SqlVectorTest.cs | 9 +- 2 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs index 2e83b8daff..d87a539c37 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -39,13 +39,12 @@ namespace Microsoft.Data.SqlTypes; private SqlVector(int length) { - if (length < 0) + (_elementType, _elementSize, int maxElements) = GetTypeFieldsOrThrow(); + if (length < 0 || length > maxElements) { throw ADP.InvalidArraySize(nameof(length)); } - (_elementType, _elementSize) = GetTypeFieldsOrThrow(); - IsNull = true; Length = length; @@ -61,7 +60,11 @@ private SqlVector(int length) /// public SqlVector(ReadOnlyMemory memory) { - (_elementType, _elementSize) = GetTypeFieldsOrThrow(); + (_elementType, _elementSize, int maxElements) = GetTypeFieldsOrThrow(); + if (memory.Length > maxElements) + { + throw ADP.InvalidArraySize(nameof(memory)); + } IsNull = false; @@ -74,7 +77,7 @@ public SqlVector(ReadOnlyMemory memory) internal SqlVector(byte[] tdsBytes) { - (_elementType, _elementSize) = GetTypeFieldsOrThrow(); + (_elementType, _elementSize, _) = GetTypeFieldsOrThrow(); (Length, _size) = GetCountsOrThrow(tdsBytes); @@ -125,10 +128,11 @@ internal string GetString() #region Helpers - private (byte, byte) GetTypeFieldsOrThrow() + private static (byte, byte, int) GetTypeFieldsOrThrow() { byte elementType; byte elementSize; + int maxSize; if (typeof(T) == typeof(float)) { @@ -139,12 +143,17 @@ internal string GetString() { throw SQL.VectorTypeNotSupported(typeof(T).FullName); } + // The size of a vector (including its header) must not exceed the maximum size of a TDS packet. + // Calculate the maximum number of elements to simplify the validation of input sizes in constructors. + maxSize = (TdsEnums.MAXSIZE - TdsEnums.VECTOR_HEADER_SIZE) / elementSize; - return (elementType, elementSize); + return (elementType, elementSize, maxSize); } private byte[] MakeTdsBytes(ReadOnlyMemory values) { + Debug.Assert(Length <= ushort.MaxValue); + //Refer to TDS section 2.2.5.5.7 for vector header format // +------------------------+-----------------+----------------------+------------------+----------------------------+--------------+ // | Field | Size (bytes) | Example Value | Description | @@ -158,32 +167,42 @@ private byte[] MakeTdsBytes(ReadOnlyMemory values) // +------------------------+-----------------+----------------------+--------------------------------------------------------------+ byte[] result = new byte[_size]; + ReadOnlySpan valueSpan = values.Span; // Header Bytes result[0] = VecHeaderMagicNo; result[1] = VecVersionNo; - result[2] = (byte)(Length & 0xFF); - result[3] = (byte)((Length >> 8) & 0xFF); + BinaryPrimitives.WriteUInt16LittleEndian(result.AsSpan(2), (ushort)Length); 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)) + // If .NET is running on a little-endian architecture, cast directly to a byte array and proceed. + // This optimisation relies upon the base type of the vector transporting values in a format and + // endianness which is identical to the client. This is true for all little-endian clients reading + // float32-based vectors. + if (BitConverter.IsLittleEndian) { - Buffer.BlockCopy(segment.Array, segment.Offset * _elementSize, result, TdsEnums.VECTOR_HEADER_SIZE, segment.Count * _elementSize); + ReadOnlySpan valuesAsBytes = MemoryMarshal.AsBytes(valueSpan); + + valuesAsBytes.CopyTo(result.AsSpan(TdsEnums.VECTOR_HEADER_SIZE)); } else { - Buffer.BlockCopy(values.ToArray(), 0, result, TdsEnums.VECTOR_HEADER_SIZE, values.Length * _elementSize); + if (typeof(T) == typeof(float)) + { + for (int i = 0, currPosition = TdsEnums.VECTOR_HEADER_SIZE; i < values.Length; i++, currPosition += _elementSize) + { + #if NET + BinaryPrimitives.WriteSingleLittleEndian(result.AsSpan(currPosition), (float)(object)valueSpan[i]); + #else + BinaryPrimitives.WriteInt32LittleEndian(result.AsSpan(currPosition), BitConverterCompatible.SingleToInt32Bits((float)(object)valueSpan[i])); + #endif + } + } } -#else - // Fast span-based copy. - var byteSpan = MemoryMarshal.AsBytes(values.Span); - byteSpan.CopyTo(result.AsSpan(TdsEnums.VECTOR_HEADER_SIZE)); -#endif + return result; } @@ -227,15 +246,32 @@ private T[] MakeArray() 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); + + // See the comment in MakeTdsBytes for more information on this optimisation. + if (BitConverter.IsLittleEndian) + { + Span valuesAsBytes = MemoryMarshal.AsBytes(result.AsSpan()); + + _tdsBytes.AsSpan(TdsEnums.VECTOR_HEADER_SIZE).CopyTo(valuesAsBytes); + } + else + { + if (typeof(T) == typeof(float)) + { + for (int i = 0, currPosition = TdsEnums.VECTOR_HEADER_SIZE; i < Length; i++, currPosition += _elementSize) + { + #if NET + result[i] = (T)(object)BinaryPrimitives.ReadSingleLittleEndian(_tdsBytes.AsSpan(currPosition)); + #else + result[i] = (T)(object)BitConverterCompatible.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(_tdsBytes.AsSpan(currPosition))); + #endif + } + } + } + return result; -#else - ReadOnlySpan dataSpan = _tdsBytes.AsSpan(8, _elementSize * Length); - return MemoryMarshal.Cast(dataSpan).ToArray(); -#endif } #endregion diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs index 1b34c66298..545f8af083 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -28,6 +28,13 @@ public void Construct_Length_Negative() Assert.Throws(() => SqlVector.CreateNull(-1)); } + [Fact] + public void Construct_Length_Exceeds_8000() + { + Assert.Throws(() => SqlVector.CreateNull(1999)); + Assert.Throws(() => SqlVector.CreateNull(int.MaxValue / 2)); + } + [Fact] public void Construct_Length() { From 49f640e6a6cf597fbe391973150f53f9892ba60b Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 5 May 2026 12:20:07 -0300 Subject: [PATCH 083/298] Added instructions for identifying changes specific to a subset of TFMs and OSes, where applicable. (#4243) --- .github/prompts/release-notes.prompt.md | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/.github/prompts/release-notes.prompt.md b/.github/prompts/release-notes.prompt.md index 0370e2b720..523fe3524c 100644 --- a/.github/prompts/release-notes.prompt.md +++ b/.github/prompts/release-notes.prompt.md @@ -58,6 +58,52 @@ This prompt uses the following skill: - Identify the contributors for the "Contributors" section. - **Assign each PR to one or more packages** using the identification rules in the Package Registry table. A PR may be relevant to multiple packages. PRs not matching any non-core package belong to `Microsoft.Data.SqlClient`. +### 2.1. Determine Target Framework (TFM) Scope Per Change + +For each PR included in release notes, determine whether it applies to all supported TFMs for the package or only a subset. + +Use source-level evidence (not assumptions) to classify scope: + +- **TFM-specific files** indicate scoped impact (for example, `.netfx.cs`, `.netcore.cs`). +- **Conditional compilation** indicates scoped impact (for example, `#if NETFRAMEWORK`, `#if NET`). +- **Project or build conditions** indicate scoped impact (for example, `Condition` expressions on `TargetFramework` or `TargetFrameworks`). +- **Tests-only TFM changes** should not be called out as customer-facing unless the behavior change is also present in product code. + +When writing notes: + +- If the change affects **all supported TFMs** for that package, do not add a TFM qualifier. +- If the change affects **only some TFMs**, include an explicit qualifier in the relevant bullet or section title. +- Use concise qualifiers like: + - `(net462 only)` + - `(net8.0/net9.0 only)` + +Do not infer TFM scope from labels alone; verify from changed files and code paths. + +### 2.2. Determine Operating System (OS) Scope Per Change + +For each PR included in release notes, determine whether it applies to all supported OS targets for the package or only a subset. + +Use source-level evidence (not assumptions) to classify scope: + +- **OS-specific files** indicate scoped impact (for example, `.windows.cs`, `.unix.cs`). +- **OS preprocessor symbols** indicate scoped impact (for example, `#if _WINDOWS`, `#if _UNIX`). +- **Project/build conditions** indicate scoped impact (for example, `TargetOs`, `NormalizedTargetOs`, or OS-conditional `ItemGroup`/`PropertyGroup` entries). +- **SNI implementation or native dependency gates** can imply OS scope when behavior changes only apply to native Windows SNI vs managed cross-platform paths. +- **Tests-only OS changes** should not be called out as customer-facing unless the behavior change is also present in product code. + +When writing notes: + +- If the change affects **all supported OS targets**, do not add an OS qualifier. +- If the change affects **only some OS targets**, include an explicit qualifier in the relevant bullet or section title. +- Use concise qualifiers like: + - `(Windows only)` + - `(Unix only)` + - `(Linux only)` + - `(macOS only)` +- If both TFM and OS are scoped, combine them in one qualifier, for example: `(net8.0/net9.0 on Windows only)`. + +Do not infer OS scope from labels alone; verify from changed files and code paths. + ### 3. Enrich Feature Sections with Issue Context For significant features or bug fixes that reference a GitHub issue: @@ -84,6 +130,7 @@ For each package that has relevant PRs in the milestone: - Use the template from [release-notes/template/release-notes-template.md](release-notes/template/release-notes-template.md). - Fill in the template following the instructions in each section. - Only include sections (Added, Changed, Fixed, Removed) that have entries. + - For each Added/Changed/Fixed/Removed item, include TFM and OS scope qualifiers when Step 2.1 or Step 2.2 determines the change is not universal across the package's supported targets. - Look up dependencies using the Dependency Sources from the lookup table above. Resolve concrete versions from [Directory.Packages.props](Directory.Packages.props). - List dependencies per target framework. Use the project file's `` to determine which frameworks to list. - Omit the Contributors section for packages with no public contributors. From 8ed0162f26a847ee9537b0fcb59bdee8daed3216 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Tue, 5 May 2026 16:27:25 +0100 Subject: [PATCH 084/298] Feature | Implement SqlConnection.GetSchemaAsync (#3005) --- .../SqlConnection.xml | 69 ++++++++++++++- .../ref/Microsoft.Data.SqlClient.cs | 15 ++++ .../Data/ProviderBase/DbConnectionClosed.cs | 10 +++ .../Data/ProviderBase/DbConnectionInternal.cs | 16 ++++ .../Microsoft/Data/SqlClient/SqlConnection.cs | 31 +++++++ .../Data/SqlClient/SqlMetaDataFactory.cs | 66 ++++++++++----- .../FunctionalTests/SqlConnectionTest.cs | 42 ++++++++++ .../ConnectionSchemaTest.cs | 80 ++++++++++-------- .../SqlSchemaInfoTest/SqlSchemaInfoTest.cs | 84 +++++++++++++++++-- .../tests/ManualTests/SQL/UdtTest/UdtTest2.cs | 12 ++- .../SimulatedServerTests/ConnectionTests.cs | 3 +- 11 files changed, 356 insertions(+), 72 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index f2e03ebf26..55b60b6120 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -1371,12 +1371,28 @@ For more information on working with events, see [Connection Events](https://lea - Returns schema information for the data source of this . For more information about scheme, see SQL Server Schema Collections. + Returns schema information for the data source of this . For more information about schemas, see SQL Server Schema Collections. A that contains schema information. + + + The cancellation token. + + + An asynchronous version of , which returns schema information for the data source of this . For more information about schemas, see SQL Server Schema Collections. + + + A task representing the asynchronous operation. + + + + For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. + + + Specifies the name of the schema to return. @@ -1662,6 +1678,28 @@ For more information on working with events, see [Connection Events](https://lea is specified as null. + + + Specifies the name of the schema to return. + + + The cancellation token. + + + An asynchronous version of , which returns schema information for the data source of this using the specified string for the schema name. + + + A task representing the asynchronous operation. + + + + For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. + + + + is specified as null. + + Specifies the name of the schema to return. @@ -1691,6 +1729,35 @@ For more information on working with events, see [Connection Events](https://lea + + + Specifies the name of the schema to return. + + + A set of restriction values for the requested schema. + + + The cancellation token. + + + An asynchronous version of , which returns schema information for the data source of this using the specified string for the schema name and the specified string array for the restriction values. + + + A task representing the asynchronous operation. + + + + The parameter can supply n depth of values, which are specified by the restrictions collection for a specific collection. In order to set values on a given restriction, and not set the values of other restrictions, you need to set the preceding restrictions to and then put the appropriate value in for the restriction that you would like to specify a value for. + + + An example of this is the "Tables" collection. If the "Tables" collection has three restrictions--database, owner, and table name--and you want to get back only the tables associated with the owner "Carl", you need to pass in the following values: null, "Carl". If a restriction value is not passed in, the default values are used for that restriction. This is the same mapping as passing in , which is different from passing in an empty string for the parameter value. In that case, the empty string ("") is considered to be the value for the specified parameter. + + + + is specified as null. + + + Occurs when SQL Server returns a warning or informational message. diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs index de581dbb0e..e52cbdc8d3 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs @@ -1099,6 +1099,21 @@ public event Microsoft.Data.SqlClient.SqlInfoMessageEventHandler InfoMessage { a public override System.Data.DataTable GetSchema(string collectionName) { throw null; } /// public override System.Data.DataTable GetSchema(string collectionName, string[] restrictionValues) { throw null; } + #if NET + /// + public override System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; } + /// + public override System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; } + /// + public override System.Threading.Tasks.Task GetSchemaAsync(string collectionName, string[] restrictionValues, System.Threading.CancellationToken cancellationToken = default) { throw null; } + #else + /// + public System.Threading.Tasks.Task GetSchemaAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; } + /// + public System.Threading.Tasks.Task GetSchemaAsync(string collectionName, System.Threading.CancellationToken cancellationToken = default) { throw null; } + /// + public System.Threading.Tasks.Task GetSchemaAsync(string collectionName, string[] restrictionValues, System.Threading.CancellationToken cancellationToken = default) { throw null; } + #endif /// public override void Open() { } /// 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 15c32053d6..91b9be9b6a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -5,6 +5,7 @@ using System.Data; using System.Data.Common; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; using Microsoft.Data.Common.ConnectionString; @@ -48,6 +49,15 @@ protected internal override DataTable GetSchema( throw ADP.ClosedConnectionError(); } + protected internal override Task GetSchemaAsync( + SqlConnectionFactory factory, + DbConnectionPoolGroup poolGroup, + DbConnection outerConnection, + string collectionName, + string[] restrictions, + CancellationToken cancellationToken) + => throw ADP.ClosedConnectionError(); + protected override DbReferenceCollection CreateReferenceCollection() => throw ADP.ClosedConnectionError(); internal override bool TryOpenConnection( 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 5d3143783f..67ef4a533a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -878,6 +878,22 @@ protected internal virtual DataTable GetSchema( return metaDataFactory.GetSchema(outerConnection, collectionName, restrictions); } + protected internal virtual Task GetSchemaAsync( + SqlConnectionFactory factory, + DbConnectionPoolGroup poolGroup, + DbConnection outerConnection, + string collectionName, + string[] restrictions, + CancellationToken cancellationToken) + { + Debug.Assert(outerConnection is not null, "outerConnection may not be null."); + + SqlMetaDataFactory metaDataFactory = factory.GetMetaDataFactory(poolGroup, this); + Debug.Assert(metaDataFactory is not null, "metaDataFactory may not be null."); + + return metaDataFactory.GetSchemaAsync(outerConnection, collectionName, restrictions, cancellationToken); + } + protected virtual bool ObtainAdditionalLocksForClose() { // No additional locks in default implementation diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 127f47e19d..fa7a456e1a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -2026,12 +2026,32 @@ public override DataTable GetSchema() return GetSchema(DbMetaDataCollectionNames.MetaDataCollections, null); } + /// +#if NET + public override Task GetSchemaAsync(CancellationToken cancellationToken = default) +#else + public Task GetSchemaAsync(CancellationToken cancellationToken = default) +#endif + { + return GetSchemaAsync(DbMetaDataCollectionNames.MetaDataCollections, cancellationToken); + } + /// public override DataTable GetSchema(string collectionName) { return GetSchema(collectionName, null); } + /// +#if NET + public override Task GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default) +#else + public Task GetSchemaAsync(string collectionName, CancellationToken cancellationToken = default) +#endif + { + return GetSchemaAsync(collectionName, null, cancellationToken); + } + /// public override DataTable GetSchema(string collectionName, string[] restrictionValues) { @@ -2042,6 +2062,17 @@ public override DataTable GetSchema(string collectionName, string[] restrictionV return InnerConnection.GetSchema(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues); } + /// +#if NET + public override Task GetSchemaAsync(string collectionName, string[] restrictionValues, CancellationToken cancellationToken = default) +#else + public Task GetSchemaAsync(string collectionName, string[] restrictionValues, CancellationToken cancellationToken = default) +#endif + { + SqlClientEventSource.Log.TryTraceEvent("SqlConnection.GetSchemaAsync | Info | Object Id {0}, Collection Name '{1}'", ObjectID, collectionName); + return InnerConnection.GetSchemaAsync(ConnectionFactory, PoolGroup, this, collectionName, restrictionValues, cancellationToken); + } + #if NET /// public override bool CanCreateBatch => true; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs index ca3dec0658..0c659579e7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -10,6 +10,8 @@ using System.Globalization; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; using System.Xml; using Microsoft.Data.Common; @@ -159,13 +161,20 @@ private DataRow FindMetaDataCollectionRow(string collectionName) } - public DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) + public DataTable GetSchema(DbConnection connection, string collectionName, string[] restrictions) => + GetSchemaCore(connection, collectionName, restrictions, isAsync: false, default).Result; + + public async Task GetSchemaAsync(DbConnection connection, string collectionName, string[] restrictions, CancellationToken cancellationToken) => + await GetSchemaCore(connection, collectionName, restrictions, isAsync: true, cancellationToken).ConfigureAwait(false); + + public async ValueTask GetSchemaCore(DbConnection connection, string collectionName, string[] restrictions, bool isAsync, CancellationToken cancellationToken) { const string DataTableKey = "DataTable"; const string SqlCommandKey = "SQLCommand"; const string PrepareCollectionKey = "PrepareCollection"; Debug.Assert(_collectionDataSet is not null); + cancellationToken.ThrowIfCancellationRequested(); DataTable metaDataCollectionsTable = _collectionDataSet.Tables[DbMetaDataCollectionNames.MetaDataCollections]; DataColumn populationMechanismColumn = metaDataCollectionsTable.Columns[PopulationMechanismKey]; @@ -187,6 +196,7 @@ public DataTable GetSchema(DbConnection connection, string collectionName, strin } } + cancellationToken.ThrowIfCancellationRequested(); string populationMechanism = requestedCollectionRow[populationMechanismColumn, DataRowVersion.Current] as string; DataTable requestedSchema; @@ -207,11 +217,11 @@ public DataTable GetSchema(DbConnection connection, string collectionName, strin break; case SqlCommandKey: - requestedSchema = ExecuteCommand(requestedCollectionRow, restrictions, connection); + requestedSchema = await ExecuteCommandAsync(requestedCollectionRow, restrictions, connection, isAsync, cancellationToken); break; case PrepareCollectionKey: - requestedSchema = PrepareCollection(exactCollectionName, restrictions, connection); + requestedSchema = await PrepareCollectionAsync(exactCollectionName, restrictions, connection, isAsync, cancellationToken); break; default: @@ -360,7 +370,7 @@ private string GetParameterName(string neededCollectionName, int neededRestricti return result; } - private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection) + private async ValueTask ExecuteCommandAsync(DataRow requestedCollectionRow, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken) { Debug.Assert(requestedCollectionRow is not null); @@ -410,7 +420,7 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri { try { - reader = command.ExecuteReader(); + reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken) : command.ExecuteReader(); } catch (Exception e) { @@ -427,16 +437,26 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri Locale = CultureInfo.InvariantCulture }; + // reader.GetColumnSchema waits synchronously for the reader to receive its type metadata. + // Instead, we invoke reader.ReadAsync outside of the while loop, which will implicitly ensure that + // the metadata is available. ReadAsync/Read will throw an exception if necessary, so we can trust + // that the list of fields is available if the call returns. + bool firstResultAvailable = isAsync ? await reader.ReadAsync(cancellationToken) : reader.Read(); System.Collections.ObjectModel.ReadOnlyCollection colSchema = reader.GetColumnSchema(); + foreach (DbColumn col in colSchema) { resultTable.Columns.Add(col.ColumnName, col.DataType); } - object[] values = new object[resultTable.Columns.Count]; - while (reader.Read()) + + if (firstResultAvailable) { - reader.GetValues(values); - resultTable.Rows.Add(values); + object[] values = new object[resultTable.Columns.Count]; + do + { + reader.GetValues(values); + resultTable.Rows.Add(values); + } while (isAsync ? await reader.ReadAsync(cancellationToken) : reader.Read()); } } finally @@ -448,7 +468,7 @@ private DataTable ExecuteCommand(DataRow requestedCollectionRow, string[] restri #endregion #region GetSchema Helpers: PrepareCollection Population Method - private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) + private static async ValueTask AddUDTsToDataTypesTableAsync(DataTable dataTypesTable, SqlConnection connection, string serverVersion, bool isAsync, CancellationToken cancellationToken) { const string GetEngineEditionSqlCommand = "SELECT SERVERPROPERTY('EngineEdition');"; const string ListUdtSqlCommand = @@ -487,7 +507,7 @@ private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnect using SqlCommand command = connection.CreateCommand(); command.CommandText = GetEngineEditionSqlCommand; - int engineEdition = (int)command.ExecuteScalar(); + int engineEdition = (int)(isAsync ? await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteScalar()); if (s_assemblyPropertyUnsupportedEngines.Contains(engineEdition)) { @@ -516,10 +536,10 @@ private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnect // Execute the SELECT statement command.CommandText = ListUdtSqlCommand; - using SqlDataReader reader = command.ExecuteReader(); + using SqlDataReader reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteReader(); object[] values = new object[11]; - while (reader.Read()) + while (isAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : reader.Read()) { reader.GetValues(values); DataRow newRow = dataTypesTable.NewRow(); @@ -577,10 +597,12 @@ private static void AddUDTsToDataTypesTable(DataTable dataTypesTable, SqlConnect dataTypesTable.Rows.Add(newRow); newRow.AcceptChanges(); } // if assembly name + + cancellationToken.ThrowIfCancellationRequested(); }//end while } - private static void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnection connection, string serverVersion) + private static async ValueTask AddTVPsToDataTypesTableAsync(DataTable dataTypesTable, SqlConnection connection, string serverVersion, bool isAsync, CancellationToken cancellationToken) { const string ListTvpsSqlCommand = "select " + @@ -618,10 +640,10 @@ private static void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnect Debug.Assert(typeName is not null, "typeName column not found"); Debug.Assert(isNullable is not null, "isNullable column not found"); - using SqlDataReader reader = command.ExecuteReader(); + using SqlDataReader reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false) : command.ExecuteReader(); object[] values = new object[11]; - while (reader.Read()) + while (isAsync ? await reader.ReadAsync(cancellationToken) : reader.Read()) { reader.GetValues(values); @@ -641,10 +663,12 @@ private static void AddTVPsToDataTypesTable(DataTable dataTypesTable, SqlConnect dataTypesTable.Rows.Add(newRow); newRow.AcceptChanges(); + + cancellationToken.ThrowIfCancellationRequested(); }//end while } - private DataTable GetDataTypesTable(SqlConnection connection) + private async ValueTask GetDataTypesTable(SqlConnection connection, bool isAsync, CancellationToken cancellationToken) { // verify the existence of the table in the data set Debug.Assert(_collectionDataSet.Tables.Contains(DbMetaDataCollectionNames.DataTypes), "DataTypes collection not found in the DataSet"); @@ -652,14 +676,14 @@ private DataTable GetDataTypesTable(SqlConnection connection) // copy the table filtering out any rows that don't apply to tho current version of the provider DataTable dataTypesTable = CloneAndFilterCollection(DbMetaDataCollectionNames.DataTypes, []); - AddUDTsToDataTypesTable(dataTypesTable, connection, _serverVersion); - AddTVPsToDataTypesTable(dataTypesTable, connection, _serverVersion); + await AddUDTsToDataTypesTableAsync(dataTypesTable, connection, _serverVersion, isAsync, cancellationToken).ConfigureAwait(false); + await AddTVPsToDataTypesTableAsync(dataTypesTable, connection, _serverVersion, isAsync, cancellationToken).ConfigureAwait(false); dataTypesTable.AcceptChanges(); return dataTypesTable; } - private DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) + private async ValueTask PrepareCollectionAsync(string collectionName, string[] restrictions, DbConnection connection, bool isAsync, CancellationToken cancellationToken) { SqlConnection sqlConnection = (SqlConnection)connection; DataTable resultTable = null; @@ -670,7 +694,7 @@ private DataTable PrepareCollection(string collectionName, string[] restrictions { throw ADP.TooManyRestrictions(DbMetaDataCollectionNames.DataTypes); } - resultTable = GetDataTypesTable(sqlConnection); + resultTable = await GetDataTypesTable(sqlConnection, isAsync, cancellationToken).ConfigureAwait(false); } if (resultTable == null) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index d61866f176..f203920df1 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using Xunit; using System.Reflection; +using System.Threading.Tasks; namespace Microsoft.Data.SqlClient.Tests { @@ -354,6 +355,47 @@ public void GetSchema_Connection_Closed() Assert.NotNull(ex.Message); } + [Fact] + public async Task GetSchemaAsync_Connection_Closed() + { + SqlConnection cn = new SqlConnection(); + + InvalidOperationException ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync()); + // Invalid operation. The connection is closed + Assert.Null(ex.InnerException); + Assert.NotNull(ex.Message); + + ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync("Tables")); + // Invalid operation. The connection is closed + Assert.Null(ex.InnerException); + Assert.NotNull(ex.Message); + + ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync(null)); + // Invalid operation. The connection is closed + Assert.Null(ex.InnerException); + Assert.NotNull(ex.Message); + + ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync("Tables", new string[] { "master" })); + // Invalid operation. The connection is closed + Assert.Null(ex.InnerException); + Assert.NotNull(ex.Message); + + ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync(null, new string[] { "master" })); + // Invalid operation. The connection is closed + Assert.Null(ex.InnerException); + Assert.NotNull(ex.Message); + + ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync("Tables", null)); + // Invalid operation. The connection is closed + Assert.Null(ex.InnerException); + Assert.NotNull(ex.Message); + + ex = await Assert.ThrowsAsync(() => cn.GetSchemaAsync(null, null)); + // Invalid operation. The connection is closed + Assert.Null(ex.InnerException); + Assert.NotNull(ex.Message); + } + [Theory] [InlineData("Authentication = ActiveDirectoryIntegrated;Password = ''")] [InlineData("Authentication = ActiveDirectoryIntegrated;PWD = ''")] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs index 41e6ecb451..e765f56ce8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Data; +using System.Threading.Tasks; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -18,96 +19,96 @@ public static bool CanRunSchemaTests() } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetTablesFromSchema() + public static async Task GetTablesFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.Tables, new string[] { "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "TABLE_TYPE" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.Tables, new string[] { "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "TABLE_TYPE" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetProceduresFromSchema() + public static async Task GetProceduresFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.Procedures, new string[] { "ROUTINE_SCHEMA", "ROUTINE_NAME", "ROUTINE_TYPE" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.Procedures, new string[] { "ROUTINE_SCHEMA", "ROUTINE_NAME", "ROUTINE_TYPE" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetProcedureParametersFromSchema() + public static async Task GetProcedureParametersFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.ProcedureParameters, new string[] { "PARAMETER_MODE", "PARAMETER_NAME" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.ProcedureParameters, new string[] { "PARAMETER_MODE", "PARAMETER_NAME" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetDatabasesFromSchema() + public static async Task GetDatabasesFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.Databases, new string[] { "database_name", "dbid", "create_date" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.Databases, new string[] { "database_name", "dbid", "create_date" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetForeignKeysFromSchema() + public static async Task GetForeignKeysFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.ForeignKeys, new string[] { "CONSTRAINT_TYPE", "IS_DEFERRABLE", "INITIALLY_DEFERRED" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.ForeignKeys, new string[] { "CONSTRAINT_TYPE", "IS_DEFERRABLE", "INITIALLY_DEFERRED" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetIndexesFromSchema() + public static async Task GetIndexesFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.Indexes, new string[] { "index_name", "constraint_name" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.Indexes, new string[] { "index_name", "constraint_name" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetIndexColumnsFromSchema() + public static async Task GetIndexColumnsFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.IndexColumns, new string[] { "index_name", "KeyType", "column_name" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.IndexColumns, new string[] { "index_name", "KeyType", "column_name" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetColumnsFromSchema() + public static async Task GetColumnsFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.Columns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.Columns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetAllColumnsFromSchema() + public static async Task GetAllColumnsFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.AllColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.AllColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetColumnSetColumnsFromSchema() + public static async Task GetColumnSetColumnsFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.ColumnSetColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.ColumnSetColumns, new string[] { "IS_NULLABLE", "COLUMN_DEFAULT", "IS_FILESTREAM", "IS_SPARSE", "IS_COLUMN_SET" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetUsersFromSchema() + public static async Task GetUsersFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.Users, new string[] { "uid", "user_name" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.Users, new string[] { "uid", "user_name" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetViewsFromSchema() + public static async Task GetViewsFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.Views, new string[] { "TABLE_NAME", "CHECK_OPTION", "IS_UPDATABLE" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.Views, new string[] { "TABLE_NAME", "CHECK_OPTION", "IS_UPDATABLE" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetViewColumnsFromSchema() + public static async Task GetViewColumnsFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.ViewColumns, new string[] { "VIEW_CATALOG", "VIEW_SCHEMA", "VIEW_NAME" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.ViewColumns, new string[] { "VIEW_CATALOG", "VIEW_SCHEMA", "VIEW_NAME" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetUserDefinedTypesFromSchema() + public static async Task GetUserDefinedTypesFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.UserDefinedTypes, new string[] { "assembly_name", "version_revision", "culture_info" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.UserDefinedTypes, new string[] { "assembly_name", "version_revision", "culture_info" }); } [ConditionalFact(nameof(CanRunSchemaTests))] - public static void GetStructuredTypeMembersFromSchema() + public static async Task GetStructuredTypeMembersFromSchema() { - VerifySchemaTable(SqlClientMetaDataCollectionNames.StructuredTypeMembers, new string[] { "TYPE_CATALOG", "TYPE_SCHEMA", "TYPE_NAME", "MEMBER_NAME", "ORDINAL_POSITION" }); + await VerifySchemaTable(SqlClientMetaDataCollectionNames.StructuredTypeMembers, new string[] { "TYPE_CATALOG", "TYPE_SCHEMA", "TYPE_NAME", "MEMBER_NAME", "ORDINAL_POSITION" }); } - private static void VerifySchemaTable(string schemaItemName, string[] testColumnNames) + private static async Task VerifySchemaTable(string schemaItemName, string[] testColumnNames) { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { @@ -118,17 +119,24 @@ private static void VerifySchemaTable(string schemaItemName, string[] testColumn { // Connect to the database then retrieve the schema information connection.Open(); - DataTable table = connection.GetSchema(schemaItemName); + DataTable syncTable = connection.GetSchema(schemaItemName); + DataTable asyncTable = await connection.GetSchemaAsync(schemaItemName); // Get all table columns - HashSet columnNames = new HashSet(); + HashSet syncColumnNames = new HashSet(); + HashSet asyncColumnNames = new HashSet(); - foreach (DataColumn column in table.Columns) + foreach (DataColumn column in syncTable.Columns) { - columnNames.Add(column.ColumnName); + syncColumnNames.Add(column.ColumnName); + } + foreach (DataColumn column in asyncTable.Columns) + { + asyncColumnNames.Add(column.ColumnName); } - Assert.All(testColumnNames, column => Assert.Contains(column, columnNames)); + Assert.All(testColumnNames, column => Assert.Contains(column, syncColumnNames)); + Assert.All(testColumnNames, column => Assert.Contains(column, asyncColumnNames)); } } } 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 742a4fbbad..6f4c602c27 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs @@ -8,6 +8,7 @@ using System.Data; using System.Data.Common; using System.Linq; +using System.Threading.Tasks; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -22,23 +23,35 @@ public static void TestGetSchema(bool openTransaction) { using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) { - conn.Open(); - DataTable dataBases; + SqlTransaction transaction = null; - if (openTransaction) + conn.Open(); + try { - using (SqlTransaction transaction = conn.BeginTransaction()) + if (openTransaction) { - dataBases = conn.GetSchema("DATABASES"); + transaction = conn.BeginTransaction(); } + + DataTable dataBases = conn.GetSchema("DATABASES"); + Assert.True(dataBases.Rows.Count > 0, "At least one database is expected"); + + string firstDatabaseName = dataBases.Rows[0]["database_name"] as string; + dataBases = conn.GetSchema("DATABASES", [firstDatabaseName]); + + Assert.Equal(1, dataBases.Rows.Count); + Assert.Equal(firstDatabaseName, dataBases.Rows[0]["database_name"] as string); + + string nonexistentDatabaseName = DataTestUtility.GenerateRandomCharacters("NonExistentDatabase_"); + dataBases = conn.GetSchema("DATABASES", [nonexistentDatabaseName]); + + Assert.Equal(0, dataBases.Rows.Count); } - else + finally { - dataBases = conn.GetSchema("DATABASES"); + transaction?.Dispose(); } - Assert.True(dataBases.Rows.Count > 0, "At least one database is expected"); - DataTable metaDataCollections = conn.GetSchema(DbMetaDataCollectionNames.MetaDataCollections); Assert.True(metaDataCollections != null && metaDataCollections.Rows.Count > 0); @@ -57,6 +70,59 @@ public static void TestGetSchema(bool openTransaction) } } + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [InlineData(true)] + [InlineData(false)] + public static async Task TestGetSchemaAsync(bool openTransaction) + { + using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + SqlTransaction transaction = null; + + await conn.OpenAsync(); + try + { + if (openTransaction) + { + transaction = conn.BeginTransaction(); + } + + DataTable dataBases = await conn.GetSchemaAsync("DATABASES"); + Assert.True(dataBases.Rows.Count > 0, "At least one database is expected"); + + string firstDatabaseName = dataBases.Rows[0]["database_name"] as string; + dataBases = await conn.GetSchemaAsync("DATABASES", [firstDatabaseName]); + + Assert.Equal(1, dataBases.Rows.Count); + Assert.Equal(firstDatabaseName, dataBases.Rows[0]["database_name"] as string); + + string nonexistentDatabaseName = DataTestUtility.GenerateRandomCharacters("NonExistentDatabase_"); + dataBases = await conn.GetSchemaAsync("DATABASES", [nonexistentDatabaseName]); + + Assert.Equal(0, dataBases.Rows.Count); + } + finally + { + transaction?.Dispose(); + } + + DataTable metaDataCollections = await conn.GetSchemaAsync(DbMetaDataCollectionNames.MetaDataCollections); + Assert.True(metaDataCollections != null && metaDataCollections.Rows.Count > 0); + + DataTable metaDataSourceInfo = await conn.GetSchemaAsync(DbMetaDataCollectionNames.DataSourceInformation); + Assert.True(metaDataSourceInfo != null && metaDataSourceInfo.Rows.Count > 0); + + DataTable metaDataTypes = await conn.GetSchemaAsync(DbMetaDataCollectionNames.DataTypes); + Assert.True(metaDataTypes != null && metaDataTypes.Rows.Count > 0); + + var tinyintRow = metaDataTypes.Rows.OfType().Where(p => (string)p["TypeName"] == "tinyint"); + foreach (var row in tinyintRow) + { + Assert.True((String)row["TypeName"] == "tinyint" && (String)row["DataType"] == "System.Byte" && (bool)row["IsUnsigned"]); + } + } + } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void TestCommandBuilder() { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs index 1dae314730..2159589f9c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs @@ -518,18 +518,22 @@ public void TestSchemaTable() using (SqlDataReader reader = cmd.ExecuteReader()) { - DataTable t = reader.GetSchemaTable(); + DataTable syncSchemaTable = reader.GetSchemaTable(); string expectedSchemaTableValues = "ids, 0, 4, 10, 255, False, , , , ids, , , System.Int32, True, 8, , , False, False, False, , False, False, System.Data.SqlTypes.SqlInt32, int, , , , , 8, False, " + Environment.NewLine + "pos, 1, 20, 255, 255, False, , , , pos, , , Line, True, 29, , , False, False, False, , False, False, Line, UdtTestDb.dbo.Line, , , , Line, Shapes, Version=1.2.0.0, Culture=neutral, PublicKeyToken=a3e3aa32e6a16344, 29, False, " + Environment.NewLine; StringBuilder builder = new StringBuilder(); - foreach (DataRow row in t.Rows) + for (int i = 0; i < syncSchemaTable.Rows.Count; i++) { - foreach (DataColumn col in t.Columns) + DataRow syncRow = syncSchemaTable.Rows[i]; + + for (int j = 0; j < syncSchemaTable.Columns.Count; j++) { - builder.Append(row[col] + ", "); + DataColumn syncColumn = syncSchemaTable.Columns[j]; + + builder.Append(syncRow[syncColumn] + ", "); } builder.AppendLine(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index e3a4e26957..57e8d17632 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -352,10 +352,11 @@ public void SqlConnectionInvalidParameters(string connString) } [Fact] - public void ClosedConnectionSchemaRetrieval() + public async Task ClosedConnectionSchemaRetrieval() { using SqlConnection connection = new(string.Empty); Assert.Throws(() => connection.GetSchema()); + await Assert.ThrowsAsync(() => connection.GetSchemaAsync()); } [Theory] From be95ca2928f9947dd4a9c4c00dc839ad40cc66e3 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Tue, 5 May 2026 17:36:36 -0700 Subject: [PATCH 085/298] Remove connection options inheritance (#4237) * Rename SqlConnectionString to SqlConnectionOptions * Remove the DbConnectionOptions parent class. * Fix netfx compilation issues. * Add comments and missing using. * Fix compilation issues. * Fix compilation issues. * Remove stale file. * Clean up redundant casts. * Fix unit tests. * Fix compilation issue. * Fix test connection string. --- .../src/Microsoft/Data/Common/AdapterUtil.cs | 2 +- .../ConnectionString/DbConnectionOptions.cs | 782 ------------------ .../DbConnectionString.netfx.cs | 14 +- .../Data/ProviderBase/DbConnectionClosed.cs | 11 +- .../Data/ProviderBase/DbConnectionInternal.cs | 7 +- .../Data/SqlClient/Connection/ServerInfo.cs | 8 +- .../Connection/SqlConnectionInternal.cs | 26 +- .../ConnectionPool/ChannelDbConnectionPool.cs | 9 +- .../ConnectionPool/DbConnectionPoolGroup.cs | 7 +- .../ConnectionPool/IDbConnectionPool.cs | 5 +- .../SqlConnectionPoolGroupProviderInfo.cs | 8 +- .../WaitHandleDbConnectionPool.cs | 17 +- .../SqlClient/SSPI/SspiContextProvider.cs | 2 +- .../SqlClient/SqlClientPermission.netfx.cs | 10 +- .../Microsoft/Data/SqlClient/SqlConnection.cs | 163 ++-- .../Data/SqlClient/SqlConnectionFactory.cs | 50 +- .../SqlConnectionOptions.Debug.cs} | 39 +- ...ctionString.cs => SqlConnectionOptions.cs} | 770 ++++++++++++++++- .../SqlClient/SqlConnectionStringBuilder.cs | 34 +- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 74 +- .../Microsoft/Data/SqlClient/SqlDependency.cs | 4 +- .../Data/SqlClient/SqlDependencyListener.cs | 10 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- .../Data/SqlClient/TdsParserStaticMethods.cs | 4 +- .../tests/FunctionalTests/SqlExceptionTest.cs | 2 +- .../ChannelDbConnectionPoolTest.cs | 92 +-- .../TransactedConnectionPoolTest.cs | 4 +- ...itHandleDbConnectionPoolTransactionTest.cs | 10 +- ...ingTest.cs => SqlConnectionOptionsTest.cs} | 10 +- 29 files changed, 1069 insertions(+), 1107 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/{Common/ConnectionString/DbConnectionOptions.Debug.cs => SqlClient/SqlConnectionOptions.Debug.cs} (94%) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{SqlConnectionString.cs => SqlConnectionOptions.cs} (59%) rename src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/{SqlConnectionStringTest.cs => SqlConnectionOptionsTest.cs} (93%) 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 ecefa338c9..c0e170b3f8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -514,7 +514,7 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int internal static Exception CreateSqlException( SqlAuthenticationProviderException authException, - SqlConnectionString connectionOptions, + SqlConnectionOptions connectionOptions, SqlConnectionInternal sender, string username) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs deleted file mode 100644 index 57f1f0e0d3..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs +++ /dev/null @@ -1,782 +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 System.IO; -using System.Text; -using System.Text.RegularExpressions; - -#if NETFRAMEWORK -using System.Security; -#endif - -namespace Microsoft.Data.Common.ConnectionString -{ - 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 - - private const string ConnectionStringValidKeyPattern = "^(?![;\\s])[^\\p{Cc}]+(? _parsetable; - - internal Dictionary Parsetable => _parsetable; - public bool IsEmpty => _keyChain == null; - - public DbConnectionOptions(string connectionString, IReadOnlyDictionary synonyms) - { - _parsetable = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - _usersConnectionString = connectionString ?? ""; - - // first pass on parsing, initial syntax check - if (_usersConnectionString.Length > 0) - { - _keyChain = ParseInternal(_parsetable, _usersConnectionString, true, synonyms, false); - _hasPasswordKeyword = _parsetable.ContainsKey(DbConnectionStringKeywords.Password) || - _parsetable.ContainsKey(DbConnectionStringSynonyms.Pwd); - _hasUserIdKeyword = _parsetable.ContainsKey(DbConnectionStringKeywords.UserId) || - _parsetable.ContainsKey(DbConnectionStringSynonyms.Uid); - } - } - - protected DbConnectionOptions(DbConnectionOptions connectionOptions) - { // Clone used by SqlConnectionString - _usersConnectionString = connectionOptions._usersConnectionString; - _parsetable = connectionOptions._parsetable; - _keyChain = connectionOptions._keyChain; - _hasPasswordKeyword = connectionOptions._hasPasswordKeyword; - _hasUserIdKeyword = connectionOptions._hasUserIdKeyword; - } - - internal bool TryGetParsetableValue(string key, out string value) => _parsetable.TryGetValue(key, out value); - - // same as Boolean, but with SSPI thrown in as valid yes - public bool ConvertValueToIntegratedSecurity() - { - 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; - } - - if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no")) - { - return false; - } - - string tmp = stringValue.Trim(); // Remove leading & trailing whitespace. - if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes")) - { - return true; - } - - if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no")) - { - return false; - } - - throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.IntegratedSecurity); - } - - public int ConvertValueToInt32(string keyName, int defaultValue) - { - return _parsetable.TryGetValue(keyName, out string value) && value != null ? - ConvertToInt32Internal(keyName, value) : - defaultValue; - } - - internal static int ConvertToInt32Internal(string keyname, string stringValue) - { - try - { - return int.Parse(stringValue, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture); - } - catch (FormatException e) - { - throw ADP.InvalidConnectionOptionValue(keyname, e); - } - catch (OverflowException e) - { - throw ADP.InvalidConnectionOptionValue(keyname, e); - } - } - - public string ConvertValueToString(string keyName, string defaultValue) - => _parsetable.TryGetValue(keyName, out string value) && value != null ? value : defaultValue; - - public bool ContainsKey(string keyword) => _parsetable.ContainsKey(keyword); - - protected internal virtual string Expand() => _usersConnectionString; - - public string UsersConnectionString(bool hidePassword) => UsersConnectionString(hidePassword, false); - - internal string UsersConnectionStringForTrace() => UsersConnectionString(true, true); - - private string UsersConnectionString(bool hidePassword, bool forceHidePassword) - { - string connectionString = _usersConnectionString; - if (_hasPasswordKeyword && (forceHidePassword || (hidePassword && !HasPersistablePassword))) - { - ReplacePasswordPwd(out connectionString, false); - } - return connectionString ?? string.Empty; - } - - 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) - { - string value; - return _parsetable.TryGetValue(keyName, out value) ? - ConvertValueToBooleanInternal(keyName, value) : - defaultValue; - } - - internal static bool ConvertValueToBooleanInternal(string keyName, string stringValue) - { - if (CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes")) - { - return true; - } - else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no")) - { - return false; - } - else - { - string tmp = stringValue.Trim(); // Remove leading & trailing whitespace. - if (CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes")) - { - return true; - } - else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no")) - { - return false; - } - else - { - throw ADP.InvalidConnectionOptionValue(keyName); - } - } - } - - private static bool CompareInsensitiveInvariant(string strvalue, string strconst) - => (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst)); - - private static string GetKeyName(StringBuilder buffer) - { - int count = buffer.Length; - while ((0 < count) && char.IsWhiteSpace(buffer[count - 1])) - { - count--; // trailing whitespace - } - return buffer.ToString(0, count).ToLower(CultureInfo.InvariantCulture); - } - - private static string GetKeyValue(StringBuilder buffer, bool trimWhitespace) - { - int count = buffer.Length; - int index = 0; - if (trimWhitespace) - { - while ((index < count) && char.IsWhiteSpace(buffer[index])) - { - index++; // leading whitespace - } - while ((0 < count) && char.IsWhiteSpace(buffer[count - 1])) - { - count--; // trailing whitespace - } - } - return buffer.ToString(index, count - index); - } - - // transition states used for parsing - private enum ParserState - { - NothingYet = 1, //start point - Key, - KeyEqual, - KeyEnd, - UnquotedValue, - DoubleQuoteValue, - DoubleQuoteValueQuote, - SingleQuoteValue, - SingleQuoteValueQuote, - BraceQuoteValue, - BraceQuoteValueQuote, - QuotedValueEnd, - 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; - - buffer.Length = 0; - keyname = null; - keyvalue = null; - - char currentChar = '\0'; - - ParserState parserState = ParserState.NothingYet; - int length = connectionString.Length; - for (; currentPosition < length; ++currentPosition) - { - currentChar = connectionString[currentPosition]; - - switch (parserState) - { - case ParserState.NothingYet: // [\\s;]* - if ((';' == currentChar) || char.IsWhiteSpace(currentChar)) - { - continue; - } - if ('\0' == currentChar) - { parserState = ParserState.NullTermination; continue; } - if (char.IsControl(currentChar)) - { throw ADP.ConnectionStringSyntax(startposition); } - startposition = currentPosition; - if ('=' != currentChar) - { - parserState = ParserState.Key; - break; - } - else - { - parserState = ParserState.KeyEqual; - continue; - } - - case ParserState.Key: // (?([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+) - if ('=' == currentChar) - { parserState = ParserState.KeyEqual; continue; } - if (char.IsWhiteSpace(currentChar)) - { break; } - if (char.IsControl(currentChar)) - { throw ADP.ConnectionStringSyntax(startposition); } - break; - - case ParserState.KeyEqual: // \\s*=(?!=)\\s* - if (!useOdbcRules && '=' == currentChar) - { parserState = ParserState.Key; break; } - keyname = GetKeyName(buffer); - if (string.IsNullOrEmpty(keyname)) - { throw ADP.ConnectionStringSyntax(startposition); } - buffer.Length = 0; - parserState = ParserState.KeyEnd; - goto case ParserState.KeyEnd; - - case ParserState.KeyEnd: - if (char.IsWhiteSpace(currentChar)) - { continue; } - if (useOdbcRules) - { - if ('{' == currentChar) - { parserState = ParserState.BraceQuoteValue; break; } - } - else - { - if ('\'' == currentChar) - { parserState = ParserState.SingleQuoteValue; continue; } - if ('"' == currentChar) - { parserState = ParserState.DoubleQuoteValue; continue; } - } - if (';' == currentChar) - { goto ParserExit; } - if ('\0' == currentChar) - { goto ParserExit; } - if (char.IsControl(currentChar)) - { throw ADP.ConnectionStringSyntax(startposition); } - parserState = ParserState.UnquotedValue; - break; - - case ParserState.UnquotedValue: // "((?![\"'\\s])" + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" + "(? parsetable, - string connectionString, - bool buildChain, - IReadOnlyDictionary synonyms, - bool firstKey) - { - Debug.Assert(connectionString != null, "null connectionstring"); - StringBuilder buffer = new StringBuilder(); - NameValuePair localKeychain = null, keychain = null; - - #if DEBUG - try - { - #endif - int nextStartPosition = 0; - int endPosition = connectionString.Length; - while (nextStartPosition < endPosition) - { - int startPosition = nextStartPosition; - - string keyname, keyvalue; - nextStartPosition = GetKeyValuePair(connectionString, startPosition, buffer, firstKey, out keyname, out keyvalue); - if (string.IsNullOrEmpty(keyname)) - { - // if (nextStartPosition != endPosition) { throw; } - break; - } - - DebugTraceKeyValuePair(keyname, keyvalue, synonyms); - Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname"); - Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue"); - - string realkeyname = (synonyms is not 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) - } - - if (localKeychain != null) - { - localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition); - } - else if (buildChain) - { // first time only - don't contain modified chain from UDL file - keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition); - } - } - #if DEBUG - } - catch (ArgumentException e) - { - ParseComparison(parsetable, connectionString, synonyms, firstKey, e); - throw; - } - ParseComparison(parsetable, connectionString, synonyms, firstKey, null); - #endif - - return keychain; - } - - internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) - { - bool expanded = false; - int copyPosition = 0; - NameValuePair head = null, tail = null, next = null; - StringBuilder builder = new StringBuilder(_usersConnectionString.Length); - for (NameValuePair current = _keyChain; current != null; current = current.Next) - { - if (!CompareInsensitiveInvariant(DbConnectionStringKeywords.Password, current.Name) && - !CompareInsensitiveInvariant(DbConnectionStringSynonyms.Pwd, current.Name)) - { - builder.Append(_usersConnectionString, copyPosition, current.Length); - if (fakePassword) - { - next = new NameValuePair(current.Name, current.Value, current.Length); - } - } - else if (fakePassword) - { - // replace user password/pwd value with * - const string equalstar = "=*;"; - builder.Append(current.Name).Append(equalstar); - next = new NameValuePair(current.Name, "*", current.Name.Length + equalstar.Length); - expanded = true; - } - else - { - // drop the password/pwd completely in returning for user - expanded = true; - } - - if (fakePassword) - { - if (tail != null) - { - tail = tail.Next = next; - } - else - { - tail = head = next; - } - } - copyPosition += current.Length; - } - Debug.Assert(expanded, "password/pwd was not removed"); - 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. - 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/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs index dfb383d6b1..841369423b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs @@ -15,7 +15,9 @@ namespace Microsoft.Data.Common { - // @TODO: Theoretically this class could be replaced with SqlConnectionString. + // Note: This class exists exclusively for use in SqlClientPermission. It provides a stable serialization format when the permission set is serialized + // by Code Access Security (CAS). CAS itself is deprecated in all versions of .NET and .NET Framework, but it won't be dropped from .NET Framework. + // We should maintain this class and SqlClientPermission to ensure that existing CAS-based security checks continue to work correctly on .NET Framework. [Serializable] // MDAC 83147 internal sealed class DbConnectionString @@ -53,20 +55,20 @@ internal DbConnectionString( string restrictions, KeyRestrictionBehavior behavior, IReadOnlyDictionary synonyms) - : this(new DbConnectionOptions(value, synonyms), restrictions, behavior, synonyms, false) + : this(new SqlConnectionOptions(value), restrictions, behavior, synonyms, false) { // The IReadOnlyDictionary doesn't need to be cloned since it isn't shared with anything else } - internal DbConnectionString(DbConnectionOptions connectionOptions) + internal DbConnectionString(SqlConnectionOptions connectionOptions) : this(connectionOptions, (string)null, KeyRestrictionBehavior.AllowOnly, null, true) { - // used by DBDataPermission to convert from DbConnectionOptions to DbConnectionString + // used by DBDataPermission to convert from SqlConnectionOptions to DbConnectionString // since backward compatibility requires Everett level classes } private DbConnectionString( - DbConnectionOptions connectionOptions, + SqlConnectionOptions connectionOptions, string restrictions, KeyRestrictionBehavior behavior, IReadOnlyDictionary synonyms, @@ -505,7 +507,7 @@ private static string[] ParseRestrictions(string restrictions, IReadOnlyDictiona int startPosition = nextStartPosition; string keyname; // since parsing restrictions ignores values, it doesn't matter if we use ODBC rules or OLEDB rules - nextStartPosition = DbConnectionOptions.GetKeyValuePair(restrictions, startPosition, buffer, false, out keyname, out _); + nextStartPosition = SqlConnectionOptions.GetKeyValuePair(restrictions, startPosition, buffer, false, out keyname, out _); if (!string.IsNullOrEmpty(keyname)) { #if DEBUG 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 91b9be9b6a..2bb0c202a7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -8,7 +8,6 @@ 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; @@ -64,7 +63,7 @@ internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) => + SqlConnectionOptions userOptions) => TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); /// @@ -81,7 +80,7 @@ internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) => throw ADP.ConnectionAlreadyOpen(State); } @@ -123,14 +122,14 @@ internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) => + SqlConnectionOptions userOptions) => TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { if (retry == null || !retry.Task.IsCompleted) { @@ -179,7 +178,7 @@ internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) => + SqlConnectionOptions userOptions) => TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); } } 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 67ef4a533a..b6b6d727ed 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,6 @@ 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; using Microsoft.Data.SqlClient.Internal; @@ -802,7 +801,7 @@ internal virtual bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { throw ADP.ConnectionAlreadyOpen(State); } @@ -811,7 +810,7 @@ internal virtual bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { throw ADP.MethodNotImplemented(); } @@ -914,7 +913,7 @@ protected bool TryOpenConnectionInternal( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { // ?->Connecting: prevent set_ConnectionString during Open if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this)) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ServerInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ServerInfo.cs index 45519cbf93..4999e47b1a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ServerInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ServerInfo.cs @@ -14,7 +14,7 @@ internal sealed class ServerInfo /// /// Initialize server info from connection options alone. /// - internal ServerInfo(SqlConnectionString userOptions) + internal ServerInfo(SqlConnectionOptions userOptions) : this(userOptions, userOptions.DataSource, userOptions.ServerSPN) { } @@ -22,7 +22,7 @@ internal ServerInfo(SqlConnectionString userOptions) /// Initialize server info from connection options, but override DataSource and ServerSPN /// with given server name and server SPN. /// - internal ServerInfo(SqlConnectionString userOptions, string serverName, string serverSpn) + internal ServerInfo(SqlConnectionOptions userOptions, string serverName, string serverSpn) : this(userOptions, serverName) { ServerSPN = serverSpn; @@ -33,7 +33,7 @@ internal ServerInfo(SqlConnectionString userOptions, string serverName, string s /// Initialize server info from connection options, but override DataSource with given /// server name. /// - private ServerInfo(SqlConnectionString userOptions, string serverName) + private ServerInfo(SqlConnectionOptions userOptions, string serverName) { Debug.Assert(userOptions != null); Debug.Assert(serverName != null, "server name should never be null"); @@ -56,7 +56,7 @@ private ServerInfo(SqlConnectionString userOptions, string serverName) /// server name. /// internal ServerInfo( - SqlConnectionString userOptions, + SqlConnectionOptions userOptions, RoutingInfo routing, string preRoutingServerName, string serverSpn) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index cf0a296b30..6c5be8930c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -313,18 +313,18 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable /// new Login7 packet will always write out the new password (or a length of zero and no /// bytes if not present). /// - userConnectionOptions may be different to connectionOptions if the connection string - /// has been expanded (see SqlConnectionString.Expand) + /// has been expanded (see SqlConnectionOptions.Expand) /// // @TODO: We really really need simplify what we pass into this. All these optional parameters need to go! internal SqlConnectionInternal( DbConnectionPoolIdentity identity, - SqlConnectionString connectionOptions, + SqlConnectionOptions connectionOptions, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance, - SqlConnectionString userConnectionOptions = null, + SqlConnectionOptions userConnectionOptions = null, SessionData reconnectSessionData = null, bool applyTransientFaultHandling = false, string accessToken = null, @@ -508,7 +508,7 @@ internal Guid ClientConnectionId /// /// The connection options to be used for this connection. /// - internal SqlConnectionString ConnectionOptions { get; } + internal SqlConnectionOptions ConnectionOptions { get; } /// /// The current database for this connection. Null if the connection is not open yet. @@ -941,7 +941,7 @@ internal void CheckEnlistedTransactionBinding() if (enlistedTransaction != null) { - if (ConnectionOptions.TransactionBinding is SqlConnectionString.TransactionBindingEnum.ExplicitUnbind) + if (ConnectionOptions.TransactionBinding is SqlConnectionOptions.TransactionBindingEnum.ExplicitUnbind) { Transaction currentTransaction = Transaction.Current; if (enlistedTransaction.TransactionInformation.Status != TransactionStatus.Active || !enlistedTransaction.Equals(currentTransaction)) @@ -1971,7 +1971,7 @@ internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { return TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); } @@ -2233,7 +2233,7 @@ private void AttemptOneLogin( // @TODO: Rename to meet naming conventions private bool AttemptRetryADAuthWithTimeoutError( SqlException sqlex, - SqlConnectionString connectionOptions, // @TODO: this is not used + SqlConnectionOptions connectionOptions, // @TODO: this is not used TimeoutTimer timeout) { if (!_activeDirectoryAuthTimeoutRetryHelper.CanRetryWithSqlException(sqlex)) @@ -3140,7 +3140,7 @@ private void LoginNoFailover( string newPassword, SecureString newSecurePassword, bool redirectedUserInstance, - SqlConnectionString connectionOptions, + SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) { @@ -3464,7 +3464,7 @@ private void LoginWithFailover( string newPassword, SecureString newSecurePassword, bool redirectedUserInstance, - SqlConnectionString connectionOptions, + SqlConnectionOptions connectionOptions, SqlCredential credential, // @TODO: This isn't used anywhere TimeoutTimer timeout) { @@ -3779,7 +3779,7 @@ private bool IsTransientError(SqlException exc) private void OpenLoginEnlist( TimeoutTimer timeout, - SqlConnectionString connectionOptions, + SqlConnectionOptions connectionOptions, SqlCredential credential, string newPassword, SecureString newSecurePassword, @@ -3905,7 +3905,7 @@ internal override void ResetConnection() } } - private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnectionString options) + private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnectionOptions options) { // @TODO: Invert to save on indentation if (serverInfo.ExtendedServerName == null) @@ -3946,7 +3946,7 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, if (options.EnforceLocalHost) { // Verify LocalHost for |DataDirectory| usage - SqlConnectionString.VerifyLocalHostAndFixup( + SqlConnectionOptions.VerifyLocalHostAndFixup( ref host, enforceLocalHost: true, fixup: true); @@ -3958,7 +3958,7 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, } #if NETFRAMEWORK - private bool ShouldDisableTnir(SqlConnectionString connectionOptions) + private bool ShouldDisableTnir(SqlConnectionOptions connectionOptions) { bool isAzureEndPoint = ADP.IsAzureSqlServerEndpoint(connectionOptions.DataSource); 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 index d6db51e5bb..41d49508be 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; -using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using static Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolState; using Microsoft.Data.SqlClient.Internal; @@ -225,7 +224,7 @@ public void PutObjectFromTransactedPool(DbConnectionInternal connection) /// public DbConnectionInternal ReplaceConnection( DbConnection owningObject, - DbConnectionOptions userOptions, + SqlConnectionOptions userOptions, DbConnectionInternal oldConnection) { throw new NotImplementedException(); @@ -283,7 +282,7 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans public bool TryGetConnection( DbConnection owningObject, TaskCompletionSource? taskCompletionSource, - DbConnectionOptions userOptions, + SqlConnectionOptions userOptions, out DbConnectionInternal? connection) { var timeout = TimeSpan.FromSeconds(owningObject.ConnectionTimeout); @@ -384,7 +383,7 @@ public bool TryGetConnection( /// private DbConnectionInternal? OpenNewInternalConnection( DbConnection? owningConnection, - DbConnectionOptions userOptions, + SqlConnectionOptions userOptions, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -513,7 +512,7 @@ private void RemoveConnection(DbConnectionInternal connection) /// private async Task GetInternalConnection( DbConnection owningConnection, - DbConnectionOptions userOptions, + SqlConnectionOptions userOptions, bool async, TimeSpan timeout) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index f1bf850582..8f62787e30 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.Threading; using Microsoft.Data.Common; -using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.Internal; @@ -33,7 +32,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool sealed internal class DbConnectionPoolGroup { - private readonly DbConnectionOptions _connectionOptions; + private readonly SqlConnectionOptions _connectionOptions; private readonly ConnectionPoolKey _poolKey; private readonly DbConnectionPoolGroupOptions _poolGroupOptions; private ConcurrentDictionary _poolCollection; @@ -50,7 +49,7 @@ sealed internal class DbConnectionPoolGroup private const int PoolGroupStateIdle = 2; // all pools are pruned via Clear private const int PoolGroupStateDisabled = 4; // factory pool entry pruning method - internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, ConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions) + internal DbConnectionPoolGroup(SqlConnectionOptions connectionOptions, ConnectionPoolKey key, DbConnectionPoolGroupOptions poolGroupOptions) { Debug.Assert(connectionOptions != null, "null connection options"); @@ -66,7 +65,7 @@ internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, Connection _state = PoolGroupStateActive; } - internal DbConnectionOptions ConnectionOptions => _connectionOptions; + internal SqlConnectionOptions ConnectionOptions => _connectionOptions; internal ConnectionPoolKey PoolKey => _poolKey; 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 index 8a6ae397ed..904c19f28e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -7,7 +7,6 @@ using System.Data.Common; using System.Threading.Tasks; using System.Transactions; -using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; #nullable enable @@ -123,7 +122,7 @@ internal interface IDbConnectionPool /// 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); + bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, SqlConnectionOptions userOptions, out DbConnectionInternal? connection); /// /// Replaces the internal connection currently associated with owningObject with a new internal connection from the pool. @@ -132,7 +131,7 @@ internal interface IDbConnectionPool /// 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); + DbConnectionInternal ReplaceConnection(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection); /// /// Returns an internal connection to the pool. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs index db2a17bd56..1b6e6a02d3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs @@ -17,7 +17,7 @@ internal sealed class SqlConnectionPoolGroupProviderInfo : DbConnectionPoolGroup private PermissionSet _failoverPermissionSet; #endif - internal SqlConnectionPoolGroupProviderInfo(SqlConnectionString connectionOptions) + internal SqlConnectionPoolGroupProviderInfo(SqlConnectionOptions connectionOptions) { // This is for the case where the user specified the failover partner // in the connection string and we have not yet connected to get the @@ -54,7 +54,7 @@ internal void AliasCheck(string server) } } - internal void FailoverCheck(bool actualUseFailoverPartner, SqlConnectionString userConnectionOptions, string actualFailoverPartner) + internal void FailoverCheck(bool actualUseFailoverPartner, SqlConnectionOptions userConnectionOptions, string actualFailoverPartner) { if (UseFailoverPartner != actualUseFailoverPartner) { @@ -87,7 +87,7 @@ internal void FailoverCheck(bool actualUseFailoverPartner, SqlConnectionString u } #if NETFRAMEWORK - private PermissionSet CreateFailoverPermission(SqlConnectionString userConnectionOptions, string actualFailoverPartner) + private PermissionSet CreateFailoverPermission(SqlConnectionOptions userConnectionOptions, string actualFailoverPartner) { string keywordToReplace; @@ -116,7 +116,7 @@ private PermissionSet CreateFailoverPermission(SqlConnectionString userConnectio } string failoverConnectionString = userConnectionOptions.ExpandKeyword(keywordToReplace, actualFailoverPartner); - return (new SqlConnectionString(failoverConnectionString)).CreatePermissionSet(); + return (new SqlConnectionOptions(failoverConnectionString)).CreatePermissionSet(); } internal void FailoverPermissionDemand() diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index b7fa972e85..96a1a6e5f3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -13,7 +13,6 @@ using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; -using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using static Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolState; using Microsoft.Data.SqlClient.Internal; @@ -62,7 +61,7 @@ internal sealed class WaitHandleDbConnectionPool : IDbConnectionPool private sealed class PendingGetConnection { - public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource completion, DbConnectionOptions userOptions) + public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource completion, SqlConnectionOptions userOptions) { DueTime = dueTime; Owner = owner; @@ -72,7 +71,7 @@ public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSour public long DueTime { get; private set; } public DbConnection Owner { get; private set; } public TaskCompletionSource Completion { get; private set; } - public DbConnectionOptions UserOptions { get; private set; } + public SqlConnectionOptions UserOptions { get; private set; } } private sealed class PoolWaitHandles @@ -492,7 +491,7 @@ private Timer CreateCleanupTimer() => private bool IsBlockingPeriodEnabled() { - var poolGroupConnectionOptions = _connectionPoolGroup.ConnectionOptions as SqlConnectionString; + var poolGroupConnectionOptions = _connectionPoolGroup.ConnectionOptions; if (poolGroupConnectionOptions == null) { return true; @@ -523,7 +522,7 @@ private bool IsBlockingPeriodEnabled() } } - private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + private DbConnectionInternal CreateObject(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection) { DbConnectionInternal newObj = null; @@ -876,7 +875,7 @@ private void WaitForPendingOpen() } while (_pendingOpens.TryPeek(out next)); } - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, SqlConnectionOptions userOptions, out DbConnectionInternal connection) { uint waitForMultipleObjectsTimeout = 0; bool allowCreate = false; @@ -935,7 +934,7 @@ public bool TryGetConnection(DbConnection owningObject, TaskCompletionSourceOptions used to create the new connection /// Inner connection that will be replaced /// A new inner connection that is attached to the - public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", Id); DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection); @@ -1523,7 +1522,7 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans } } - private DbConnectionInternal UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection = null) + private DbConnectionInternal UserCreateRequest(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection = null) { // called by user when they were not able to obtain a free object but // instead obtained creation mutex diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs index 757e57e1c3..c48f9ada8b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs @@ -58,7 +58,7 @@ internal void Initialize( Initialize(); - static SspiAuthenticationParameters CreateAuthParams(SqlConnectionString connString, string serverSpn) => new( + static SspiAuthenticationParameters CreateAuthParams(SqlConnectionOptions connString, string serverSpn) => new( resource: serverSpn, serverName: connString.DataSource, databaseName: connString.InitialCatalog, 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 index b42310af12..0a670ad97f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientPermission.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientPermission.netfx.cs @@ -18,6 +18,10 @@ namespace Microsoft.Data.SqlClient { + // Note: This class implements Code Access Security (CAS) permission for SQL Server connections. CAS is deprecated + // in all modern versions of .NET and .NET Framework, but it will not be removed from .NET Framework. This class + // should be maintained to ensure that existing CAS-based security checks continue to work correctly on .NET Framework. + /// [Serializable] public sealed class SqlClientPermission : DBDataPermission @@ -55,9 +59,9 @@ internal SqlClientPermission(SqlClientPermissionAttribute permissionAttribute) : // Used by SqlClientPermissionAttribute.CreatePermission } - internal SqlClientPermission(SqlConnectionString constr) : base(PermissionState.None) + internal SqlClientPermission(SqlConnectionOptions constr) : base(PermissionState.None) { - // Used by SqlConnectionString.CreatePermissionSet + // Used by SqlConnectionOptions.CreatePermissionSet if (constr != null) { @@ -106,7 +110,7 @@ private bool _IsUnrestricted /// public override void Add(string connectionString, string restrictions, KeyRestrictionBehavior behavior) { - DbConnectionString constr = new DbConnectionString(connectionString, restrictions, behavior, SqlConnectionString.KeywordMap); + DbConnectionString constr = new DbConnectionString(connectionString, restrictions, behavior, SqlConnectionOptions.KeywordMap); AddPermissionEntry(constr); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index fa7a456e1a..b0c9cc4289 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -51,7 +51,7 @@ public sealed partial class SqlConnection : DbConnection, ICloneable private static readonly SqlConnectionFactory s_connectionFactory = SqlConnectionFactory.Instance; private static int _objectTypeCount; // EventSource Counter - private DbConnectionOptions _userConnectionOptions; + private SqlConnectionOptions _userConnectionOptions; private DbConnectionPoolGroup _poolGroup; private DbConnectionInternal _innerConnection; private int _closeCount; @@ -201,41 +201,40 @@ public SqlConnection(string connectionString, SqlCredential credential) : this() // 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)) + if (UsesClearUserIdOrPassword(ConnectionOptions)) { throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); } - if (UsesIntegratedSecurity(connectionOptions)) + if (UsesIntegratedSecurity(ConnectionOptions)) { throw ADP.InvalidMixedArgumentOfSecureCredentialAndIntegratedSecurity(); } - else if (UsesActiveDirectoryIntegrated(connectionOptions)) + else if (UsesActiveDirectoryIntegrated(ConnectionOptions)) { throw SQL.SettingCredentialWithIntegratedArgument(); } - else if (UsesActiveDirectoryInteractive(connectionOptions)) + else if (UsesActiveDirectoryInteractive(ConnectionOptions)) { throw SQL.SettingCredentialWithInteractiveArgument(); } - else if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) + else if (UsesActiveDirectoryDeviceCodeFlow(ConnectionOptions)) { throw SQL.SettingCredentialWithDeviceFlowArgument(); } - else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) + else if (UsesActiveDirectoryManagedIdentity(ConnectionOptions)) { throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); } - else if (UsesActiveDirectoryMSI(connectionOptions)) + else if (UsesActiveDirectoryMSI(ConnectionOptions)) { throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryMSIString); } - else if (UsesActiveDirectoryDefault(connectionOptions)) + else if (UsesActiveDirectoryDefault(ConnectionOptions)) { throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryDefaultString); } - else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) + else if (UsesActiveDirectoryWorkloadIdentity(ConnectionOptions)) { throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); } @@ -337,8 +336,7 @@ internal bool IsColumnEncryptionSettingEnabled { get { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return opt?.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled; + return ConnectionOptions?.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled; } } @@ -433,9 +431,9 @@ private static void ValidateCustomProviders(IDictionary s_connectionFactory; - internal DbConnectionOptions ConnectionOptions => PoolGroup?.ConnectionOptions; + internal SqlConnectionOptions ConnectionOptions => PoolGroup?.ConnectionOptions; - internal DbConnectionOptions UserConnectionOptions => _userConnectionOptions; + internal SqlConnectionOptions UserConnectionOptions => _userConnectionOptions; internal DbConnectionInternal InnerConnection => _innerConnection; @@ -455,7 +453,7 @@ internal DbConnectionPoolGroup PoolGroup /// internal string EnclaveAttestationUrl { - get => ((SqlConnectionString)ConnectionOptions).EnclaveAttestationUrl; + get => ConnectionOptions.EnclaveAttestationUrl; } /// @@ -463,7 +461,7 @@ internal string EnclaveAttestationUrl /// internal SqlConnectionAttestationProtocol AttestationProtocol { - get => ((SqlConnectionString)ConnectionOptions).AttestationProtocol; + get => ConnectionOptions.AttestationProtocol; } /// @@ -471,25 +469,24 @@ internal SqlConnectionAttestationProtocol AttestationProtocol /// internal SqlConnectionIPAddressPreference iPAddressPreference { - get => ((SqlConnectionString)ConnectionOptions).IPAddressPreference; + get => ConnectionOptions.IPAddressPreference; } // This method will be called once connection string is set or changed. private void CacheConnectionStringProperties() { - SqlConnectionString connString = ConnectionOptions as SqlConnectionString; - if (connString != null) + if (ConnectionOptions != null) { - _connectRetryCount = connString.ConnectRetryCount; + _connectRetryCount = ConnectionOptions.ConnectRetryCount; // For Azure Synapse ondemand connections, set _connectRetryCount to 5 instead of 1 to greatly improve recovery // success rate. Note: Synapse should be detected first as it could be detected as a regular Azure SQL DB endpoint. - if (_connectRetryCount == 1 && ADP.IsAzureSynapseOnDemandEndpoint(connString.DataSource)) + if (_connectRetryCount == 1 && ADP.IsAzureSynapseOnDemandEndpoint(ConnectionOptions.DataSource)) { _connectRetryCount = 5; } // For Azure SQL connection, set _connectRetryCount to 2 instead of 1 will greatly improve recovery // success rate - else if (_connectRetryCount == 1 && ADP.IsAzureSqlServerEndpoint(connString.DataSource)) + else if (_connectRetryCount == 1 && ADP.IsAzureSqlServerEndpoint(ConnectionOptions.DataSource)) { _connectRetryCount = 2; } @@ -563,54 +560,54 @@ internal bool AsyncCommandInProgress set => _AsyncCommandInProgress = value; } - private bool UsesActiveDirectoryIntegrated(SqlConnectionString opt) + private bool UsesActiveDirectoryIntegrated(SqlConnectionOptions opt) { return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated; } - private bool UsesActiveDirectoryInteractive(SqlConnectionString opt) + private bool UsesActiveDirectoryInteractive(SqlConnectionOptions opt) { return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive; } - private bool UsesActiveDirectoryDeviceCodeFlow(SqlConnectionString opt) + private bool UsesActiveDirectoryDeviceCodeFlow(SqlConnectionOptions opt) { return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; } - private bool UsesActiveDirectoryManagedIdentity(SqlConnectionString opt) + private bool UsesActiveDirectoryManagedIdentity(SqlConnectionOptions opt) { return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity; } - private bool UsesActiveDirectoryMSI(SqlConnectionString opt) + private bool UsesActiveDirectoryMSI(SqlConnectionOptions opt) { return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI; } - private bool UsesActiveDirectoryDefault(SqlConnectionString opt) + private bool UsesActiveDirectoryDefault(SqlConnectionOptions opt) { return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault; } - private bool UsesActiveDirectoryWorkloadIdentity(SqlConnectionString opt) + private bool UsesActiveDirectoryWorkloadIdentity(SqlConnectionOptions opt) { return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; } - private bool UsesAuthentication(SqlConnectionString opt) + private bool UsesAuthentication(SqlConnectionOptions opt) { return opt != null && opt.Authentication != SqlAuthenticationMethod.NotSpecified; } // Does this connection use Integrated Security? - private bool UsesIntegratedSecurity(SqlConnectionString opt) + private bool UsesIntegratedSecurity(SqlConnectionOptions opt) { return opt != null && opt.IntegratedSecurity; } // Does this connection use old style of clear userID or Password in connection string? - private bool UsesClearUserIdOrPassword(SqlConnectionString opt) + private bool UsesClearUserIdOrPassword(SqlConnectionOptions opt) { bool result = false; if (opt != null) @@ -620,24 +617,24 @@ private bool UsesClearUserIdOrPassword(SqlConnectionString opt) return result; } - internal SqlConnectionString.TransactionBindingEnum TransactionBinding + internal SqlConnectionOptions.TransactionBindingEnum TransactionBinding { - get => ((SqlConnectionString)ConnectionOptions).TransactionBinding; + get => ConnectionOptions.TransactionBinding; } - internal SqlConnectionString.TypeSystem TypeSystem + internal SqlConnectionOptions.TypeSystem TypeSystem { - get => ((SqlConnectionString)ConnectionOptions).TypeSystemVersion; + get => ConnectionOptions.TypeSystemVersion; } internal Version TypeSystemAssemblyVersion { - get => ((SqlConnectionString)ConnectionOptions).TypeSystemAssemblyVersion; + get => ConnectionOptions.TypeSystemAssemblyVersion; } internal int ConnectRetryInterval { - get => ((SqlConnectionString)ConnectionOptions).ConnectRetryInterval; + get => ConnectionOptions.ConnectRetryInterval; } /// @@ -659,7 +656,7 @@ public override string ConnectionString { if (_credential != null || _accessToken != null || _accessTokenCallback != null) { - SqlConnectionString connectionOptions = new SqlConnectionString(value); + SqlConnectionOptions connectionOptions = new SqlConnectionOptions(value); if (_credential != null) { // Check for Credential being used with Authentication=ActiveDirectoryIntegrated | ActiveDirectoryInteractive | @@ -722,8 +719,7 @@ public override int ConnectionTimeout { get { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - return constr != null ? constr.ConnectTimeout : DbConnectionStringDefaults.ConnectTimeout; + return ConnectionOptions != null ? ConnectionOptions.ConnectTimeout : DbConnectionStringDefaults.ConnectTimeout; } } @@ -734,8 +730,7 @@ public int CommandTimeout { get { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - return constr != null ? constr.CommandTimeout : DbConnectionStringDefaults.CommandTimeout; + return ConnectionOptions != null ? ConnectionOptions.CommandTimeout : DbConnectionStringDefaults.CommandTimeout; } } @@ -750,8 +745,7 @@ public string 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; - return InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo ? null : _accessToken; + return InnerConnection.ShouldHidePassword && UserConnectionOptions != null && !UserConnectionOptions.PersistSecurityInfo ? null : _accessToken; } set { @@ -764,7 +758,7 @@ public string AccessToken if (value != null) { // Check if the usage of AccessToken has any conflict with the keys used in connection string and credential - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken((SqlConnectionString)ConnectionOptions); + CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(ConnectionOptions); } // Need to call ConnectionString_Set to do proper pool group check @@ -788,7 +782,7 @@ public Func retry, SqlConnectionOverrides overrides = SqlConnectionOverrides.None) { - SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions; bool result = false; if (LocalAppContextSwitches.GlobalizationInvariantMode) @@ -2186,18 +2173,18 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec throw SQL.GlobalizationInvariantModeNotSupported(); } - _applyTransientFaultHandling = (!overrides.HasFlag(SqlConnectionOverrides.OpenWithoutRetry) && connectionOptions != null && connectionOptions.ConnectRetryCount > 0); + _applyTransientFaultHandling = !overrides.HasFlag(SqlConnectionOverrides.OpenWithoutRetry) && ConnectionOptions != null && ConnectionOptions.ConnectRetryCount > 0; - if (connectionOptions != null && - (connectionOptions.Authentication == SqlAuthenticationMethod.SqlPassword || + if (ConnectionOptions != null && + (ConnectionOptions.Authentication == SqlAuthenticationMethod.SqlPassword || #pragma warning disable 0618 // Use of obsolete member 'SqlAuthenticationMethod.ActiveDirectoryPassword' - connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || + ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || #pragma warning restore 0618 // Use of obsolete member 'SqlAuthenticationMethod.ActiveDirectoryPassword' - connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) && - (!connectionOptions._hasUserIdKeyword || !connectionOptions._hasPasswordKeyword) && + ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) && + (!ConnectionOptions._hasUserIdKeyword || !ConnectionOptions._hasPasswordKeyword) && _credential == null) { - throw SQL.CredentialsNotProvided(connectionOptions.Authentication); + throw SQL.CredentialsNotProvided(ConnectionOptions.Authentication); } #if NETFRAMEWORK @@ -2220,7 +2207,7 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec } else { - if (this.UsesIntegratedSecurity(connectionOptions) || this.UsesActiveDirectoryIntegrated(connectionOptions)) + if (this.UsesIntegratedSecurity(ConnectionOptions) || this.UsesActiveDirectoryIntegrated(ConnectionOptions)) { _lastIdentity = DbConnectionPoolIdentity.GetCurrentWindowsIdentity(); } @@ -2288,13 +2275,13 @@ internal void PermissionDemand() Debug.Assert(DbConnectionClosedConnecting.SingletonInstance == _innerConnection, "not connecting"); DbConnectionPoolGroup poolGroup = PoolGroup; - DbConnectionOptions connectionOptions = poolGroup != null ? poolGroup.ConnectionOptions : null; + SqlConnectionOptions connectionOptions = poolGroup != null ? poolGroup.ConnectionOptions : null; if (connectionOptions == null || connectionOptions.IsEmpty) { throw ADP.NoConnectionString(); } - DbConnectionOptions userConnectionOptions = UserConnectionOptions; + SqlConnectionOptions userConnectionOptions = UserConnectionOptions; Debug.Assert(userConnectionOptions != null, "null UserConnectionOptions"); #if NETFRAMEWORK @@ -2538,13 +2525,13 @@ private string ConnectionString_Get() { SqlClientEventSource.Log.TryTraceEvent(" {0}", ObjectID); bool hidePassword = InnerConnection.ShouldHidePassword; - DbConnectionOptions connectionOptions = UserConnectionOptions; + SqlConnectionOptions connectionOptions = UserConnectionOptions; return connectionOptions != null ? connectionOptions.UsersConnectionString(hidePassword) : ""; } private void ConnectionString_Set(ConnectionPoolKey key) { - DbConnectionOptions connectionOptions = null; + SqlConnectionOptions connectionOptions = null; DbConnectionPoolGroup poolGroup = ConnectionFactory.GetConnectionPoolGroup(key, null, ref connectionOptions); DbConnectionInternal connectionInternal = InnerConnection; bool flag = connectionInternal.AllowSetConnectionString; @@ -2650,7 +2637,7 @@ public static void ChangePassword(string connectionString, string newPassword) ConnectionPoolKey key = new ConnectionPoolKey(connectionString, credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null); - SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key); + SqlConnectionOptions connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key); if (connectionOptions.IntegratedSecurity || connectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { throw SQL.ChangePasswordConflictsWithSSPI(); @@ -2704,7 +2691,7 @@ public static void ChangePassword(string connectionString, SqlCredential credent ConnectionPoolKey key = new ConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null, sspiContextProvider: null); - SqlConnectionString connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key); + SqlConnectionOptions connectionOptions = SqlConnectionFactory.Instance.FindSqlConnectionOptions(key); // Check for connection string values incompatible with SqlCredential if (!string.IsNullOrEmpty(connectionOptions.UserID) || !string.IsNullOrEmpty(connectionOptions.Password)) @@ -2731,7 +2718,7 @@ public static void ChangePassword(string connectionString, SqlCredential credent } } - private static void ChangePassword(string connectionString, SqlConnectionString connectionOptions, SqlCredential credential, string newPassword, SecureString newSecurePassword) + private static void ChangePassword(string connectionString, SqlConnectionOptions connectionOptions, SqlCredential credential, string newPassword, SecureString newSecurePassword) { // 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 diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 9fd1485db4..981ca17641 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -108,15 +108,15 @@ internal void ClearPool(ConnectionPoolKey key) } } - internal DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) => - ((SqlConnectionString)connectionOptions).UserInstance + internal DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(SqlConnectionOptions connectionOptions) => + connectionOptions.UserInstance ? new SqlConnectionPoolProviderInfo() : null; internal DbConnectionInternal CreateNonPooledConnection( DbConnection owningConnection, DbConnectionPoolGroup poolGroup, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { Debug.Assert(owningConnection is not null, "null owningConnection?"); Debug.Assert(poolGroup is not null, "null poolGroup?"); @@ -141,7 +141,7 @@ internal DbConnectionInternal CreateNonPooledConnection( internal DbConnectionInternal CreatePooledConnection( DbConnection owningConnection, IDbConnectionPool pool, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { Debug.Assert(pool != null, "null pool?"); @@ -176,7 +176,7 @@ internal DbConnectionInternal CreatePooledConnection( internal DbConnectionPoolGroup GetConnectionPoolGroup( ConnectionPoolKey key, DbConnectionPoolGroupOptions poolOptions, - ref DbConnectionOptions userConnectionOptions) + ref SqlConnectionOptions userConnectionOptions) { if (string.IsNullOrEmpty(key.ConnectionString)) { @@ -190,7 +190,7 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup( // our collection of pool entries, then we need to create a // new pool entry and add it to our collection. - SqlConnectionString connectionOptions = new SqlConnectionString(key.ConnectionString); + SqlConnectionOptions connectionOptions = new SqlConnectionOptions(key.ConnectionString); if (userConnectionOptions is null) { @@ -316,7 +316,7 @@ internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) internal bool TryGetConnection( DbConnection owningConnection, TaskCompletionSource retry, - DbConnectionOptions userOptions, + SqlConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection) { @@ -466,14 +466,14 @@ internal bool TryGetConnection( #endregion internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo( - DbConnectionOptions connectionOptions) => - new SqlConnectionPoolGroupProviderInfo((SqlConnectionString)connectionOptions); + SqlConnectionOptions connectionOptions) => + new SqlConnectionPoolGroupProviderInfo(connectionOptions); - internal SqlConnectionString FindSqlConnectionOptions(ConnectionPoolKey key) + internal SqlConnectionOptions FindSqlConnectionOptions(ConnectionPoolKey key) { Debug.Assert(key is not null, "Key cannot be null"); - DbConnectionOptions connectionOptions = null; + SqlConnectionOptions connectionOptions = null; if (!string.IsNullOrEmpty(key.ConnectionString) && _connectionPoolGroups.TryGetValue(key, out DbConnectionPoolGroup poolGroup)) @@ -483,7 +483,7 @@ internal SqlConnectionString FindSqlConnectionOptions(ConnectionPoolKey key) if (connectionOptions is null) { - connectionOptions = new SqlConnectionString(key.ConnectionString); + connectionOptions = new SqlConnectionOptions(key.ConnectionString); } if (connectionOptions.IsEmpty) @@ -491,7 +491,7 @@ internal SqlConnectionString FindSqlConnectionOptions(ConnectionPoolKey key) throw ADP.NoConnectionString(); } - return (SqlConnectionString)connectionOptions; + return connectionOptions; } // @TODO: All these methods seem redundant ... shouldn't we always have a SqlConnection? @@ -575,28 +575,28 @@ internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionIntern // @TODO: I think this could be broken down into methods more specific to use cases above protected virtual DbConnectionInternal CreateConnection( - DbConnectionOptions options, + SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { - SqlConnectionString opt = (SqlConnectionString)options; + SqlConnectionOptions opt = options; ConnectionPoolKey key = poolKey; SessionData recoverySessionData = null; SqlConnection sqlOwningConnection = owningConnection as SqlConnection; bool applyTransientFaultHandling = sqlOwningConnection?._applyTransientFaultHandling ?? false; - SqlConnectionString userOpt = null; + SqlConnectionOptions userOpt = null; if (userOptions != null) { - userOpt = (SqlConnectionString)userOptions; + userOpt = userOptions; } else if (sqlOwningConnection != null) { - userOpt = (SqlConnectionString)(sqlOwningConnection.UserConnectionOptions); + userOpt = sqlOwningConnection.UserConnectionOptions; } if (sqlOwningConnection != null) @@ -636,7 +636,7 @@ protected virtual DbConnectionInternal CreateConnection( // 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( + SqlConnectionOptions sseopt = new SqlConnectionOptions( opt, opt.DataSource, userInstance: true, @@ -688,7 +688,7 @@ protected virtual DbConnectionInternal CreateConnection( // 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, userInstance: false, setEnlistValue: null); + opt = new SqlConnectionOptions(opt, instanceName, userInstance: false, setEnlistValue: null); poolGroupProviderInfo = null; // null so we do not pass to constructor below... } @@ -709,9 +709,9 @@ protected virtual DbConnectionInternal CreateConnection( key.SspiContextProvider); } - private static DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(SqlConnectionString connectionOptions) + private static DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(SqlConnectionOptions connectionOptions) { - SqlConnectionString opt = (SqlConnectionString)connectionOptions; + SqlConnectionOptions opt = connectionOptions; DbConnectionPoolGroupOptions poolingOptions = null; @@ -768,7 +768,7 @@ private Task CreateReplaceConnectionContinuation( Task task, DbConnection owningConnection, TaskCompletionSource retry, - DbConnectionOptions userOptions, + SqlConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionPoolGroup poolGroup, CancellationTokenSource cancellationTokenSource) @@ -826,7 +826,7 @@ private IDbConnectionPool GetConnectionPool( DbConnectionPoolGroupOptions poolOptions = connectionPoolGroup.PoolGroupOptions; // get the string to hash on again - DbConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions; + SqlConnectionOptions connectionOptions = connectionPoolGroup.ConnectionOptions; Debug.Assert(connectionOptions != null, "prevent expansion of connectionString"); connectionPoolGroup = GetConnectionPoolGroup(connectionPoolGroup.PoolKey, poolOptions, ref connectionOptions); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.Debug.cs similarity index 94% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.Debug.cs index 2c1a107ba4..30743b3eeb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.Debug.cs @@ -7,12 +7,13 @@ using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; -using Microsoft.Data.SqlClient; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.SqlClient.Internal; -namespace Microsoft.Data.Common.ConnectionString +namespace Microsoft.Data.SqlClient { - internal partial class DbConnectionOptions + internal sealed partial class SqlConnectionOptions { #if DEBUG private const string ConnectionStringPattern = // may not contain embedded null except trailing last value @@ -32,7 +33,7 @@ internal partial class DbConnectionOptions + ")*" // repeat the key-value pair + "[\\s;]*[\u0000\\s]*"; // trailing whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end private static readonly Regex ConnectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled); - + private const string ConnectionStringPatternOdbc = // may not contain embedded null except trailing last value "([\\s;]*" // leading whitespace and extra semicolons + "(?![\\s;])" // key does not start with space or semicolon @@ -49,7 +50,7 @@ internal partial class DbConnectionOptions + "[\\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, IReadOnlyDictionary synonyms) { @@ -64,16 +65,16 @@ private static void DebugTraceKeyValuePair(string keyname, string keyvalue, IRea // don't trace passwords ever! if (keyvalue != null) { - SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}', KeyValue='{1}'", keyname, keyvalue); + SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}', KeyValue='{1}'", keyname, keyvalue); } else { - SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}'", keyname); + SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}'", keyname); } } } } - + #if DEBUG private static void ParseComparison( Dictionary parseTable, @@ -88,8 +89,8 @@ private static void ParseComparison( foreach (var parsedValue in parsedValues) { string key = parsedValue.Key; - string value1 = parsedValue.Value; - + 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}>"); @@ -107,28 +108,28 @@ private static void ParseComparison( bool isEquivalent = msg1 == msg2; if (!isEquivalent) { - // We also accept cases were Regex parser (debug only) reports "wrong format" and + // 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) || + 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"); @@ -156,7 +157,7 @@ private static Dictionary SplitConnectionString( { throw ADP.ConnectionStringSyntax(match.Length); } - + int indexValue = 0; CaptureCollection keyValues = match.Groups[ValueIndex].Captures; foreach (Capture keypair in match.Groups[KeyIndex].Captures) @@ -184,7 +185,7 @@ private static Dictionary SplitConnectionString( { keyValue = null; } - + DebugTraceKeyValuePair(keyName, keyValue, synonyms); string realKeyName = synonyms != null ? synonyms.TryGetValue(keyName, out string synonym) ? synonym : null @@ -194,14 +195,14 @@ private static Dictionary SplitConnectionString( { 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/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs similarity index 59% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs index ad05f67830..6eafb195c8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs @@ -6,8 +6,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Net; using System.Net.NetworkInformation; +using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Security; using System.Security.Permissions; @@ -17,12 +20,37 @@ namespace Microsoft.Data.SqlClient { - internal sealed class SqlConnectionString : DbConnectionOptions + internal sealed partial class SqlConnectionOptions { // 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 + #region DbConnectionOptions fields + + private const string ConnectionStringValidKeyPattern = "^(?![;\\s])[^\\p{Cc}]+(? _parsetable; + + internal Dictionary Parsetable => _parsetable; + public bool IsEmpty => _keyChain == null; + + #endregion + internal enum TypeSystem { Latest = 2008, @@ -115,7 +143,7 @@ internal static class TRANSACTIONBINDING /// /// Static constructor to do things that we can't do in a single line initialization. /// - static SqlConnectionString() + static SqlConnectionOptions() { // Add keywords and synonyms to the keyword map. // @TODO: About half of these synonyms are just the same as the keyword but with spaces @@ -209,8 +237,19 @@ static SqlConnectionString() #endif } - internal SqlConnectionString(string connectionString): base(connectionString, s_keywordMap) + internal SqlConnectionOptions(string connectionString) { + // Initialize parse table (inlined from DbConnectionOptions base constructor) + _parsetable = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + _usersConnectionString = connectionString ?? ""; + if (_usersConnectionString.Length > 0) + { + _keyChain = ParseInternal(_parsetable, _usersConnectionString, true, s_keywordMap, false); + _hasPasswordKeyword = _parsetable.ContainsKey(DbConnectionStringKeywords.Password) || + _parsetable.ContainsKey(DbConnectionStringSynonyms.Pwd); + _hasUserIdKeyword = _parsetable.ContainsKey(DbConnectionStringKeywords.UserId) || + _parsetable.ContainsKey(DbConnectionStringSynonyms.Uid); + } #if !NETFRAMEWORK ThrowUnsupportedIfKeywordSet(DbConnectionStringKeywords.ConnectionReset); @@ -512,8 +551,14 @@ internal SqlConnectionString(string connectionString): base(connectionString, s_ // This c-tor is used to create SSE and user instance connection strings when user instance is set to true // BUG (VSTFDevDiv) 479687: Using TransactionScope with Linq2SQL against user instances fails with "connection has been broken" message - internal SqlConnectionString(SqlConnectionString connectionOptions, string dataSource, bool userInstance, bool? setEnlistValue) : base(connectionOptions) + internal SqlConnectionOptions(SqlConnectionOptions connectionOptions, string dataSource, bool userInstance, bool? setEnlistValue) { + // Copy parse state (inlined from DbConnectionOptions copy constructor) + _usersConnectionString = connectionOptions._usersConnectionString; + _parsetable = connectionOptions._parsetable; + _keyChain = connectionOptions._keyChain; + _hasPasswordKeyword = connectionOptions._hasPasswordKeyword; + _hasUserIdKeyword = connectionOptions._hasUserIdKeyword; _integratedSecurity = connectionOptions._integratedSecurity; _encrypt = connectionOptions._encrypt; @@ -641,7 +686,7 @@ internal bool EnforceLocalHost } } - protected internal override string Expand() + internal string Expand() { if (_expandedAttachDBFilename != null) { @@ -653,7 +698,7 @@ protected internal override string Expand() } else { - return base.Expand(); + return _usersConnectionString; } } @@ -973,7 +1018,7 @@ internal static class NETLIB private static Dictionary s_netlibMapping; #if NETFRAMEWORK - protected internal override PermissionSet CreatePermissionSet() + internal PermissionSet CreatePermissionSet() { PermissionSet permissionSet = new(PermissionState.None); permissionSet.AddPermission(new SqlClientPermission(this)); @@ -1004,5 +1049,716 @@ private static void AddKeywordToMap(string keyword, params string[] synonyms) } #endregion + + #region DbConnectionOptions methods + + internal bool TryGetParsetableValue(string key, out string value) => _parsetable.TryGetValue(key, out value); + + // same as Boolean, but with SSPI thrown in as valid yes + public bool ConvertValueToIntegratedSecurity() + { + 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; + } + + if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no")) + { + return false; + } + + string tmp = stringValue.Trim(); // Remove leading & trailing whitespace. + if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes")) + { + return true; + } + + if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no")) + { + return false; + } + + throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.IntegratedSecurity); + } + + public int ConvertValueToInt32(string keyName, int defaultValue) + { + return _parsetable.TryGetValue(keyName, out string value) && value != null ? + ConvertToInt32Internal(keyName, value) : + defaultValue; + } + + internal static int ConvertToInt32Internal(string keyname, string stringValue) + { + try + { + return int.Parse(stringValue, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture); + } + catch (FormatException e) + { + throw ADP.InvalidConnectionOptionValue(keyname, e); + } + catch (OverflowException e) + { + throw ADP.InvalidConnectionOptionValue(keyname, e); + } + } + + public string ConvertValueToString(string keyName, string defaultValue) + => _parsetable.TryGetValue(keyName, out string value) && value != null ? value : defaultValue; + + public bool ContainsKey(string keyword) => _parsetable.ContainsKey(keyword); + + public string UsersConnectionString(bool hidePassword) => UsersConnectionString(hidePassword, false); + + internal string UsersConnectionStringForTrace() => UsersConnectionString(true, true); + + private string UsersConnectionString(bool hidePassword, bool forceHidePassword) + { + string connectionString = _usersConnectionString; + if (_hasPasswordKeyword && (forceHidePassword || (hidePassword && !HasPersistablePassword))) + { + ReplacePasswordPwd(out connectionString, false); + } + return connectionString ?? string.Empty; + } + + 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) + { + string value; + return _parsetable.TryGetValue(keyName, out value) ? + ConvertValueToBooleanInternal(keyName, value) : + defaultValue; + } + + internal static bool ConvertValueToBooleanInternal(string keyName, string stringValue) + { + if (CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes")) + { + return true; + } + else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no")) + { + return false; + } + else + { + string tmp = stringValue.Trim(); // Remove leading & trailing whitespace. + if (CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes")) + { + return true; + } + else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no")) + { + return false; + } + else + { + throw ADP.InvalidConnectionOptionValue(keyName); + } + } + } + + private static bool CompareInsensitiveInvariant(string strvalue, string strconst) + => (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst)); + + private static string GetKeyName(StringBuilder buffer) + { + int count = buffer.Length; + while ((0 < count) && char.IsWhiteSpace(buffer[count - 1])) + { + count--; // trailing whitespace + } + return buffer.ToString(0, count).ToLower(CultureInfo.InvariantCulture); + } + + private static string GetKeyValue(StringBuilder buffer, bool trimWhitespace) + { + int count = buffer.Length; + int index = 0; + if (trimWhitespace) + { + while ((index < count) && char.IsWhiteSpace(buffer[index])) + { + index++; // leading whitespace + } + while ((0 < count) && char.IsWhiteSpace(buffer[count - 1])) + { + count--; // trailing whitespace + } + } + return buffer.ToString(index, count - index); + } + + // transition states used for parsing + private enum ParserState + { + NothingYet = 1, //start point + Key, + KeyEqual, + KeyEnd, + UnquotedValue, + DoubleQuoteValue, + DoubleQuoteValueQuote, + SingleQuoteValue, + SingleQuoteValueQuote, + BraceQuoteValue, + BraceQuoteValueQuote, + QuotedValueEnd, + 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; + + buffer.Length = 0; + keyname = null; + keyvalue = null; + + char currentChar = '\0'; + + ParserState parserState = ParserState.NothingYet; + int length = connectionString.Length; + for (; currentPosition < length; ++currentPosition) + { + currentChar = connectionString[currentPosition]; + + switch (parserState) + { + case ParserState.NothingYet: // [\\s;]* + if ((';' == currentChar) || char.IsWhiteSpace(currentChar)) + { + continue; + } + if ('\0' == currentChar) + { parserState = ParserState.NullTermination; continue; } + if (char.IsControl(currentChar)) + { throw ADP.ConnectionStringSyntax(startposition); } + startposition = currentPosition; + if ('=' != currentChar) + { + parserState = ParserState.Key; + break; + } + else + { + parserState = ParserState.KeyEqual; + continue; + } + + case ParserState.Key: // (?([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+) + if ('=' == currentChar) + { parserState = ParserState.KeyEqual; continue; } + if (char.IsWhiteSpace(currentChar)) + { break; } + if (char.IsControl(currentChar)) + { throw ADP.ConnectionStringSyntax(startposition); } + break; + + case ParserState.KeyEqual: // \\s*=(?!=)\\s* + if (!useOdbcRules && '=' == currentChar) + { parserState = ParserState.Key; break; } + keyname = GetKeyName(buffer); + if (string.IsNullOrEmpty(keyname)) + { throw ADP.ConnectionStringSyntax(startposition); } + buffer.Length = 0; + parserState = ParserState.KeyEnd; + goto case ParserState.KeyEnd; + + case ParserState.KeyEnd: + if (char.IsWhiteSpace(currentChar)) + { continue; } + if (useOdbcRules) + { + if ('{' == currentChar) + { parserState = ParserState.BraceQuoteValue; break; } + } + else + { + if ('\'' == currentChar) + { parserState = ParserState.SingleQuoteValue; continue; } + if ('"' == currentChar) + { parserState = ParserState.DoubleQuoteValue; continue; } + } + if (';' == currentChar) + { goto ParserExit; } + if ('\0' == currentChar) + { goto ParserExit; } + if (char.IsControl(currentChar)) + { throw ADP.ConnectionStringSyntax(startposition); } + parserState = ParserState.UnquotedValue; + break; + + case ParserState.UnquotedValue: // "((?![\"'\\s])" + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" + "(? parsetable, + string connectionString, + bool buildChain, + IReadOnlyDictionary synonyms, + bool firstKey) + { + Debug.Assert(connectionString != null, "null connectionstring"); + StringBuilder buffer = new StringBuilder(); + NameValuePair localKeychain = null, keychain = null; + + #if DEBUG + try + { + #endif + int nextStartPosition = 0; + int endPosition = connectionString.Length; + while (nextStartPosition < endPosition) + { + int startPosition = nextStartPosition; + + string keyname, keyvalue; + nextStartPosition = GetKeyValuePair(connectionString, startPosition, buffer, firstKey, out keyname, out keyvalue); + if (string.IsNullOrEmpty(keyname)) + { + // if (nextStartPosition != endPosition) { throw; } + break; + } + + DebugTraceKeyValuePair(keyname, keyvalue, synonyms); + Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname"); + Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue"); + + string realkeyname = (synonyms is not 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) + } + + if (localKeychain != null) + { + localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition); + } + else if (buildChain) + { // first time only - don't contain modified chain from UDL file + keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition); + } + } + #if DEBUG + } + catch (ArgumentException e) + { + ParseComparison(parsetable, connectionString, synonyms, firstKey, e); + throw; + } + ParseComparison(parsetable, connectionString, synonyms, firstKey, null); + #endif + + return keychain; + } + + internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) + { + bool expanded = false; + int copyPosition = 0; + NameValuePair head = null, tail = null, next = null; + StringBuilder builder = new StringBuilder(_usersConnectionString.Length); + for (NameValuePair current = _keyChain; current != null; current = current.Next) + { + if (!CompareInsensitiveInvariant(DbConnectionStringKeywords.Password, current.Name) && + !CompareInsensitiveInvariant(DbConnectionStringSynonyms.Pwd, current.Name)) + { + builder.Append(_usersConnectionString, copyPosition, current.Length); + if (fakePassword) + { + next = new NameValuePair(current.Name, current.Value, current.Length); + } + } + else if (fakePassword) + { + // replace user password/pwd value with * + const string equalstar = "=*;"; + builder.Append(current.Name).Append(equalstar); + next = new NameValuePair(current.Name, "*", current.Name.Length + equalstar.Length); + expanded = true; + } + else + { + // drop the password/pwd completely in returning for user + expanded = true; + } + + if (fakePassword) + { + if (tail != null) + { + tail = tail.Next = next; + } + else + { + tail = head = next; + } + } + copyPosition += current.Length; + } + Debug.Assert(expanded, "password/pwd was not removed"); + 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. + 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 Net 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; + + 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 + + #endregion } } 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 33d78c57c7..4e54a32c75 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -193,7 +193,7 @@ private static Dictionary CreateKeywordsDictionary() // @TODO: Ok, I think we should consider centralizing all these keywords into a single // place. We have DbConnectionString*, Keywords, etc. // Can we consider something DbConnectionOptions as backing store for the values - // and both SqlConnectionStringBuilder and SqlConnectionString use it to store? + // and both SqlConnectionStringBuilder and SqlConnectionOptions use it to store? Dictionary pairs = new(StringComparer.OrdinalIgnoreCase) { { DbConnectionStringKeywords.ApplicationIntent, Keywords.ApplicationIntent }, @@ -828,19 +828,19 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c svalue = svalue.Trim(); if (StringComparer.OrdinalIgnoreCase.Equals(svalue, NamedPipes)) { - return SqlConnectionString.NETLIB.NamedPipes; + return SqlConnectionOptions.NETLIB.NamedPipes; } else if (StringComparer.OrdinalIgnoreCase.Equals(svalue, SharedMemory)) { - return SqlConnectionString.NETLIB.SharedMemory; + return SqlConnectionOptions.NETLIB.SharedMemory; } else if (StringComparer.OrdinalIgnoreCase.Equals(svalue, TCPIP)) { - return SqlConnectionString.NETLIB.TCPIP; + return SqlConnectionOptions.NETLIB.TCPIP; } else if (StringComparer.OrdinalIgnoreCase.Equals(svalue, VIA)) { - return SqlConnectionString.NETLIB.VIA; + return SqlConnectionOptions.NETLIB.VIA; } else { @@ -859,10 +859,10 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul { return svalue.Trim().ToLower(CultureInfo.InvariantCulture) switch { - SqlConnectionString.NETLIB.NamedPipes => NamedPipes, - SqlConnectionString.NETLIB.SharedMemory => SharedMemory, - SqlConnectionString.NETLIB.TCPIP => TCPIP, - SqlConnectionString.NETLIB.VIA => VIA, + SqlConnectionOptions.NETLIB.NamedPipes => NamedPipes, + SqlConnectionOptions.NETLIB.SharedMemory => SharedMemory, + SqlConnectionOptions.NETLIB.TCPIP => TCPIP, + SqlConnectionOptions.NETLIB.VIA => VIA, _ => svalue, }; } @@ -1877,14 +1877,14 @@ public string NetworkLibrary { value = value.Trim().ToLower(CultureInfo.InvariantCulture) switch { - SqlConnectionString.NETLIB.AppleTalk => SqlConnectionString.NETLIB.AppleTalk, - SqlConnectionString.NETLIB.BanyanVines => SqlConnectionString.NETLIB.BanyanVines, - SqlConnectionString.NETLIB.IPXSPX => SqlConnectionString.NETLIB.IPXSPX, - SqlConnectionString.NETLIB.Multiprotocol => SqlConnectionString.NETLIB.Multiprotocol, - SqlConnectionString.NETLIB.NamedPipes => SqlConnectionString.NETLIB.NamedPipes, - SqlConnectionString.NETLIB.SharedMemory => SqlConnectionString.NETLIB.SharedMemory, - SqlConnectionString.NETLIB.TCPIP => SqlConnectionString.NETLIB.TCPIP, - SqlConnectionString.NETLIB.VIA => SqlConnectionString.NETLIB.VIA, + SqlConnectionOptions.NETLIB.AppleTalk => SqlConnectionOptions.NETLIB.AppleTalk, + SqlConnectionOptions.NETLIB.BanyanVines => SqlConnectionOptions.NETLIB.BanyanVines, + SqlConnectionOptions.NETLIB.IPXSPX => SqlConnectionOptions.NETLIB.IPXSPX, + SqlConnectionOptions.NETLIB.Multiprotocol => SqlConnectionOptions.NETLIB.Multiprotocol, + SqlConnectionOptions.NETLIB.NamedPipes => SqlConnectionOptions.NETLIB.NamedPipes, + SqlConnectionOptions.NETLIB.SharedMemory => SqlConnectionOptions.NETLIB.SharedMemory, + SqlConnectionOptions.NETLIB.TCPIP => SqlConnectionOptions.NETLIB.TCPIP, + SqlConnectionOptions.NETLIB.VIA => SqlConnectionOptions.NETLIB.VIA, _ => throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.NetworkLibrary), }; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index f01fe4506d..649c73147c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -66,7 +66,7 @@ internal class SharedState private ALTROWSTATUS _altRowStatus; private int _recordsAffected = -1; private long _defaultTimeoutMilliseconds; - private SqlConnectionString.TypeSystem _typeSystem; + private SqlConnectionOptions.TypeSystem _typeSystem; // SQLStatistics support private SqlStatistics _statistics; @@ -572,7 +572,7 @@ internal DataTable BuildSchemaTable() 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) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && col.Is2008DateTimeType) { schemaRow[providerType] = SqlDbType.NVarChar; switch (col.type) @@ -594,9 +594,9 @@ internal DataTable BuildSchemaTable() break; } } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && col.IsLargeUdt) + else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && col.IsLargeUdt) { - if (_typeSystem == SqlConnectionString.TypeSystem.SQLServer2005) + if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005) { schemaRow[providerType] = SqlDbType.VarBinary; } @@ -606,7 +606,7 @@ internal DataTable BuildSchemaTable() schemaRow[providerType] = SqlDbType.Image; } } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) + else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) { // TypeSystem.SQLServer2005 and above @@ -654,7 +654,7 @@ internal DataTable BuildSchemaTable() schemaRow[precision] = col.metaType.Precision; } - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && col.Is2008DateTimeType) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && col.Is2008DateTimeType) { schemaRow[scale] = MetaType.MetaNVarChar.Scale; } @@ -1194,13 +1194,13 @@ private string GetDataTypeNameInternal(_SqlMetaData metaData) { string dataTypeName = null; - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) { dataTypeName = MetaType.MetaNVarChar.TypeName; } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) + else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) { - if (_typeSystem == SqlConnectionString.TypeSystem.SQLServer2005) + if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005) { dataTypeName = MetaType.MetaMaxVarBinary.TypeName; } @@ -1210,7 +1210,7 @@ private string GetDataTypeNameInternal(_SqlMetaData metaData) dataTypeName = MetaType.MetaImage.TypeName; } } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) + else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) { // TypeSystem.SQLServer2005 and above @@ -1282,14 +1282,14 @@ private Type GetFieldTypeInternal(_SqlMetaData metaData) { Type fieldType = null; - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) { // Return 2008 types as string fieldType = MetaType.MetaNVarChar.ClassType; } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) + else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) { - if (_typeSystem == SqlConnectionString.TypeSystem.SQLServer2005) + if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005) { fieldType = MetaType.MetaMaxVarBinary.ClassType; } @@ -1299,7 +1299,7 @@ private Type GetFieldTypeInternal(_SqlMetaData metaData) fieldType = MetaType.MetaImage.ClassType; } } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) + else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) { // TypeSystem.SQLServer2005 and above if (metaData.type == SqlDbType.Udt) @@ -1415,13 +1415,13 @@ private Type GetProviderSpecificFieldTypeInternal(_SqlMetaData metaData) { Type providerSpecificFieldType = null; - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) { providerSpecificFieldType = MetaType.MetaNVarChar.SqlType; } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) + else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) { - if (_typeSystem == SqlConnectionString.TypeSystem.SQLServer2005) + if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005) { providerSpecificFieldType = MetaType.MetaMaxVarBinary.SqlType; } @@ -1431,7 +1431,7 @@ private Type GetProviderSpecificFieldTypeInternal(_SqlMetaData metaData) providerSpecificFieldType = MetaType.MetaImage.SqlType; } } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) + else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) { // TypeSystem.SQLServer2005 and above if (metaData.type == SqlDbType.Udt) @@ -2402,7 +2402,7 @@ override public DateTime GetDateTime(int 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) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) { // TypeSystem.SQLServer2005 or less @@ -2501,7 +2501,7 @@ virtual public SqlChars GetSqlChars(int i) ReadColumn(i); SqlString data; // Convert 2008 types to string - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) { data = _data[i].Sql2008DateTimeSqlString; } @@ -2580,7 +2580,7 @@ virtual public SqlString GetSqlString(int i) { ReadColumn(i); - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) { return _data[i].Sql2008DateTimeSqlString; } @@ -2594,7 +2594,7 @@ virtual public SqlXml GetSqlXml(int i) ReadColumn(i); SqlXml sx = null; - if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) + if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) { // TypeSystem.SQLServer2005 @@ -2680,15 +2680,15 @@ private object GetSqlValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met 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) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) { return data.Sql2008DateTimeSqlString; } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) + else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) { return data.SqlValue; } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) + else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) { // TypeSystem.SQLServer2005 and above @@ -2759,7 +2759,7 @@ 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) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) { return _data[i].Sql2008DateTimeString; } @@ -2808,7 +2808,7 @@ virtual public TimeSpan GetTimeSpan(int i) TimeSpan t = _data[i].Time; - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005) { // TypeSystem.SQLServer2005 or less @@ -2830,7 +2830,7 @@ virtual public DateTimeOffset GetDateTimeOffset(int i) DateTimeOffset dto = _data[i].DateTimeOffset; - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005) { // TypeSystem.SQLServer2005 or less @@ -2868,7 +2868,7 @@ private object GetValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaDa { 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 (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) { if (data.IsNull) { @@ -2879,11 +2879,11 @@ private object GetValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaDa return data.Sql2008DateTimeString; } } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) + else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) { return data.Value; } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) + else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) { // TypeSystem.SQLServer2005 and above @@ -2990,20 +2990,20 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met { return (T)(object)data.Decimal; } - else if (typeof(T) == typeof(DateTimeOffset) && dataType == typeof(DateTimeOffset) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) + else if (typeof(T) == typeof(DateTimeOffset) && dataType == typeof(DateTimeOffset) && _typeSystem > SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) { return (T)(object)data.DateTimeOffset; } - else if (typeof(T) == typeof(DateTime) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) + else if (typeof(T) == typeof(DateTime) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) { return (T)(object)data.DateTime; } #if !NETFRAMEWORK - else if (typeof(T) == typeof(DateOnly) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005) + else if (typeof(T) == typeof(DateOnly) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionOptions.TypeSystem.SQLServer2005) { return (T)(object)data.DateOnly; } - else if (typeof(T) == typeof(TimeOnly) && dataType == typeof(TimeOnly) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005) + else if (typeof(T) == typeof(TimeOnly) && dataType == typeof(TimeOnly) && _typeSystem > SqlConnectionOptions.TypeSystem.SQLServer2005) { return (T)(object)data.TimeOnly; } @@ -3244,7 +3244,7 @@ override public int GetValues(object[] values) private MetaType GetVersionedMetaType(MetaType actualMetaType) { - Debug.Assert(_typeSystem == SqlConnectionString.TypeSystem.SQLServer2000, "Should not be in this function under anything else but SQLServer2000"); + Debug.Assert(_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2000, "Should not be in this function under anything else but SQLServer2000"); MetaType metaType = null; @@ -5996,7 +5996,7 @@ private ReadOnlyCollection BuildColumnSchema() _SqlMetaData col = md[i]; SqlDbColumn dbColumn = new SqlDbColumn(md[i]); - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && col.Is2008DateTimeType) + if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && col.Is2008DateTimeType) { dbColumn.SqlNumericScale = MetaType.MetaNVarChar.Scale; } 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 94d42a3393..9304f3051a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -576,7 +576,7 @@ internal static bool Start(string connectionString, string queue, bool useDefaul #if NETFRAMEWORK // Create new connection options for demand on their connection string. We modify the connection string // and assert on our modified string when we create the container. - SqlConnectionString connectionStringObject = new(connectionString); + SqlConnectionOptions connectionStringObject = new(connectionString); connectionStringObject.DemandPermission(); if (connectionStringObject.LocalDBInstance != null) { @@ -719,7 +719,7 @@ internal static bool Stop(string connectionString, string queue, bool useDefault #if NETFRAMEWORK // Create new connection options for demand on their connection string. We modify the connection string // and assert on our modified string when we create the container. - SqlConnectionString connectionStringObject = new(connectionString); + SqlConnectionOptions connectionStringObject = new(connectionString); connectionStringObject.DemandPermission(); if (connectionStringObject.LocalDBInstance != null) { 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 b3d5191e1a..004ae0ed08 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs @@ -85,7 +85,7 @@ internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, str _queue = _hashHelper.Queue; } #if DEBUG - SqlConnectionString connectionStringOptions = new(_hashHelper.ConnectionStringBuilder.ConnectionString); + SqlConnectionOptions connectionStringOptions = new(_hashHelper.ConnectionStringBuilder.ConnectionString); SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace()); #endif @@ -93,10 +93,10 @@ internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, str // connection string used in the hashHelper. _con = new SqlConnection(_hashHelper.ConnectionStringBuilder.ConnectionString); // Create connection and open. +#if NETFRAMEWORK // Assert permission for this particular connection string since it differs from the user passed string // which we have already demanded upon. - SqlConnectionString connStringObj = (SqlConnectionString)_con.ConnectionOptions; -#if NETFRAMEWORK + SqlConnectionOptions connStringObj = _con.ConnectionOptions; connStringObj.CreatePermissionSet().Assert(); if (connStringObj.LocalDBInstance != null) { @@ -1627,7 +1627,7 @@ private bool Start( out user, queueService); #if DEBUG - SqlConnectionString connectionStringOptions = new(connectionStringBuilder.ConnectionString); + SqlConnectionOptions connectionStringOptions = new(connectionStringBuilder.ConnectionString); SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace()); #endif @@ -1703,7 +1703,7 @@ internal bool Stop( out user, queueService); #if DEBUG - SqlConnectionString connectionStringOptions = new(connectionStringBuilder.ConnectionString); + SqlConnectionOptions connectionStringOptions = new(connectionStringBuilder.ConnectionString); SqlClientEventSource.Log.TryNotificationTraceEvent(" Modified connection string: '{0}'", connectionStringOptions.UsersConnectionStringForTrace()); #endif 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 29352c5460..53b89fbc90 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -376,7 +376,7 @@ internal void Connect( ServerInfo serverInfo, SqlConnectionInternal connHandler, TimeoutTimer timeout, - SqlConnectionString connectionOptions, + SqlConnectionOptions connectionOptions, #if NETFRAMEWORK bool withFailover, 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 56ead6a4a2..a384095254 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -63,7 +63,7 @@ static internal void AliasRegistryLookup(ref string host, ref string protocol) if (-1 != index && index + 1 < parsedAliasName.Length) { parsedProtocol = parsedAliasName.Substring(0, index); - if (SqlConnectionString.ValidProtocol(parsedProtocol)) + if (SqlConnectionOptions.ValidProtocol(parsedProtocol)) { protocol = parsedProtocol; host = parsedAliasName.Substring(index + 1); @@ -72,7 +72,7 @@ static internal void AliasRegistryLookup(ref string host, ref string protocol) } else { - protocol = (string)SqlConnectionString.NetlibMapping()[parsedProtocol]; + protocol = (string)SqlConnectionOptions.NetlibMapping()[parsedProtocol]; if (protocol != null) { host = parsedAliasName; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs index 5106a287c5..2fe9e63aed 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs @@ -43,7 +43,7 @@ public void JSONSerializationTest() + @"""$type"":""System.Collections.ListDictionaryInternal, System.Private.CoreLib""}," + @"""InnerException"":null," + @"""HelpURL"":null," - + @"""StackTraceString"":"" at Microsoft.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken)\\n at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)\\n at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)\\n at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)\\n at System.Data.ProviderBase.DbConnectionPool.WaitForPendingOpen()\\n""," + + @"""StackTraceString"":"" at Microsoft.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken)\\n at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, SqlConnectionOptions userOptions)\\n at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, SqlConnectionOptions options, DbConnectionPoolKey poolKey, SqlConnectionOptions userOptions)\\n at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, SqlConnectionOptions userOptions, DbConnectionInternal& connection)\\n at System.Data.ProviderBase.DbConnectionPool.WaitForPendingOpen()\\n""," + @"""RemoteStackTraceString"":null," + @"""RemoteStackIndex"":0," + @"""ExceptionMethod"":null," diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index 48bcc87ad2..2b1d437f98 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -36,7 +36,7 @@ private ChannelDbConnectionPool ConstructPool(SqlConnectionFactory connectionFac hasTransactionAffinity: true ); dbConnectionPoolGroup ??= new DbConnectionPoolGroup( - new DbConnectionOptions("DataSource=localhost;", null), + new SqlConnectionOptions("Data Source=localhost;"), new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions ); @@ -63,7 +63,7 @@ public void GetConnectionEmptyPool_ShouldCreateNewConnection(int numConnections) var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -93,7 +93,7 @@ public async Task GetConnectionAsyncEmptyPool_ShouldCreateNewConnection(int numC var completed = pool.TryGetConnection( new SqlConnection(), tcs, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -119,7 +119,7 @@ public void GetConnectionMaxPoolSize_ShouldTimeoutAfterPeriod() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -133,7 +133,7 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection("Timeout=1"), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? extraConnection ); } @@ -159,7 +159,7 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldTimeoutAfterPeriod() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -174,7 +174,7 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection("Timeout=1"), taskCompletionSource, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? extraConnection ); await taskCompletionSource.Task; @@ -200,7 +200,7 @@ public async Task GetConnectionMaxPoolSize_ShouldReuseAfterConnectionReleased() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? firstConnection ); @@ -209,7 +209,7 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -225,7 +225,7 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? extraConnection ); return extraConnection; @@ -247,7 +247,7 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldReuseAfterConnectionReleas pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? firstConnection ); @@ -256,7 +256,7 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -270,7 +270,7 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), taskCompletionSource, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? recycledConnection ); pool.ReturnInternalConnection(firstConnection!, firstOwningConnection); @@ -291,7 +291,7 @@ public async Task GetConnectionMaxPoolSize_ShouldRespectOrderOfRequest() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? firstConnection ); @@ -300,7 +300,7 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -321,7 +321,7 @@ out DbConnectionInternal? internalConnection pool.TryGetConnection( new SqlConnection(""), null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? recycledConnection ); return recycledConnection; @@ -334,7 +334,7 @@ out DbConnectionInternal? recycledConnection pool.TryGetConnection( new SqlConnection("Timeout=1"), null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? failedConnection ); return failedConnection; @@ -360,7 +360,7 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldRespectOrderOfRequest() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? firstConnection ); @@ -369,7 +369,7 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -384,7 +384,7 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), recycledTaskCompletionSource, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? recycledConnection ); @@ -394,7 +394,7 @@ out DbConnectionInternal? recycledConnection var exceeded2 = pool.TryGetConnection( new SqlConnection("Timeout=1"), failedCompletionSource, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? failedConnection ); @@ -417,7 +417,7 @@ public void ConnectionsAreReused() var completed1 = pool.TryGetConnection( owningConnection, null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection1 ); @@ -432,7 +432,7 @@ out DbConnectionInternal? internalConnection1 var completed2 = pool.TryGetConnection( owningConnection, null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection2 ); @@ -454,7 +454,7 @@ public void GetConnectionTimeout_ShouldThrowTimeoutException() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); }); @@ -475,7 +475,7 @@ public async Task GetConnectionAsyncTimeout_ShouldThrowTimeoutException() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -500,7 +500,7 @@ public void StressTest() var completed = pool.TryGetConnection( owningObject, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); if (completed) @@ -534,7 +534,7 @@ public void StressTestAsync() var completed = pool.TryGetConnection( owningObject, taskCompletionSource, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); internalConnection = await taskCompletionSource.Task; @@ -614,7 +614,7 @@ public void TestLoadBalanceTimeout() public void TestPoolGroup() { var dbConnectionPoolGroup = new DbConnectionPoolGroup( - new DbConnectionOptions("DataSource=localhost;", null), + new SqlConnectionOptions("Data Source=localhost;"), new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), new DbConnectionPoolGroupOptions( poolByIdentity: false, @@ -731,7 +731,7 @@ public void Clear_MultipleIdleConnections_AllAreDestroyed() pool.TryGetConnection( owningConnections[i], taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out internalConnections[i] ); Assert.Equal(0, internalConnections[i]!.ClearGeneration); @@ -760,7 +760,7 @@ public void Clear_BusyConnection_NotDestroyedImmediately() pool.TryGetConnection( owningConnection, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? busyConnection ); Assert.NotNull(busyConnection); @@ -784,7 +784,7 @@ public void Clear_BusyConnectionReturned_IsDestroyed() pool.TryGetConnection( owningConnection, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? busyConnection ); Assert.NotNull(busyConnection); @@ -814,13 +814,13 @@ public void Clear_MixedBusyAndIdle_OnlyIdleDestroyedImmediately() pool.TryGetConnection( busyOwner, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? busyConnection ); pool.TryGetConnection( idleOwner, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? idleConnection ); Assert.NotNull(busyConnection); @@ -853,7 +853,7 @@ public void Clear_NewConnectionsAfterClear_ArePooledNormally() pool.TryGetConnection( owningConnection, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? oldConnection ); Assert.Equal(0, oldConnection!.ClearGeneration); @@ -867,7 +867,7 @@ out DbConnectionInternal? oldConnection pool.TryGetConnection( newOwner, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? newConnection ); Assert.NotNull(newConnection); @@ -885,7 +885,7 @@ out DbConnectionInternal? newConnection pool.TryGetConnection( reuseOwner, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? reusedConnection ); Assert.Same(newConnection, reusedConnection); @@ -902,7 +902,7 @@ public void Clear_MultipleClearCalls_DoNotCorruptState() pool.TryGetConnection( owningConnection, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? connection ); Assert.Equal(0, connection!.ClearGeneration); @@ -921,7 +921,7 @@ out DbConnectionInternal? connection pool.TryGetConnection( newOwner, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? newConnection ); Assert.NotNull(newConnection); @@ -935,12 +935,12 @@ out DbConnectionInternal? newConnection internal class SuccessfulSqlConnectionFactory : SqlConnectionFactory { protected override DbConnectionInternal CreateConnection( - DbConnectionOptions options, + SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { return new StubDbConnectionInternal(); } @@ -949,12 +949,12 @@ protected override DbConnectionInternal CreateConnection( internal class TimeoutSqlConnectionFactory : SqlConnectionFactory { protected override DbConnectionInternal CreateConnection( - DbConnectionOptions options, + SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { throw ADP.PooledOpenTimeout(); } @@ -1006,7 +1006,7 @@ public void Constructor_WithZeroMaxPoolSize_ThrowsArgumentOutOfRangeException() hasTransactionAffinity: true ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( - new DbConnectionOptions("DataSource=localhost;", null), + new SqlConnectionOptions("Data Source=localhost;"), new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions ); @@ -1037,7 +1037,7 @@ public void Constructor_WithLargeMaxPoolSize() hasTransactionAffinity: true ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( - new DbConnectionOptions("DataSource=localhost;", null), + new SqlConnectionOptions("Data Source=localhost;"), new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions ); @@ -1078,7 +1078,7 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() hasTransactionAffinity: true ); var dbConnectionPoolGroup1 = new DbConnectionPoolGroup( - new DbConnectionOptions("DataSource=localhost;", null), + new SqlConnectionOptions("Data Source=localhost;"), new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions1 ); @@ -1104,7 +1104,7 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() hasTransactionAffinity: true ); var dbConnectionPoolGroup2 = new DbConnectionPoolGroup( - new DbConnectionOptions("DataSource=localhost;", null), + new SqlConnectionOptions("Data Source=localhost;"), new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions2 ); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs index 9f284be22a..c03c7cc550 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs @@ -676,12 +676,12 @@ internal class MockDbConnectionPool : IDbConnectionPool public void Clear() => throw new NotImplementedException(); - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal? connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, SqlConnectionOptions userOptions, out DbConnectionInternal? connection) { throw new NotImplementedException(); } - public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection) { throw new NotImplementedException(); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs index bc637087a1..4b4a30ce19 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs @@ -59,7 +59,7 @@ private WaitHandleDbConnectionPool CreatePool( ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( - new DbConnectionOptions("DataSource=localhost;", null), + new SqlConnectionOptions("Data Source=localhost;"), new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), poolGroupOptions ); @@ -82,7 +82,7 @@ private DbConnectionInternal GetConnection(SqlConnection owner) _pool.TryGetConnection( owner, taskCompletionSource: null, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? connection); return connection!; } @@ -95,7 +95,7 @@ private async Task GetConnectionAsync( _pool.TryGetConnection( owner, taskCompletionSource: tcs, - new DbConnectionOptions("", null), + new SqlConnectionOptions(""), out DbConnectionInternal? connection); return connection ?? await tcs.Task; } @@ -900,12 +900,12 @@ public void SequentialTransactions_CanReuseConnections() internal class MockSqlConnectionFactory : SqlConnectionFactory { protected override DbConnectionInternal CreateConnection( - DbConnectionOptions options, + SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, - DbConnectionOptions userOptions) + SqlConnectionOptions userOptions) { return new MockDbConnectionInternal(); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionOptionsTest.cs similarity index 93% rename from src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs rename to src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionOptionsTest.cs index 3c8689ef09..fb2e969dec 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionOptionsTest.cs @@ -8,7 +8,7 @@ namespace Microsoft.Data.SqlClient.UnitTests.Microsoft.Data.SqlClient { - public class SqlConnectionStringTest : IDisposable + public class SqlConnectionOptionsTest : IDisposable { // Ensure we restore the original app context switch values after each // test. @@ -45,7 +45,7 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, bo // Note: TNIR is disabled by default for Azure SQL Database servers (i.e. *.database.windows.net) // and when using federated auth unless explicitly set in the connection string. // However, this evaluation only happens at login time so TNIR behavior may not match - // the value of TransparentNetworkIPResolution property in SqlConnectionString. + // the value of TransparentNetworkIPResolution property in SqlConnectionOptions. // Arrange _appContextSwitchHelper.DisableTnirByDefault = tnirDisabledAppContext; @@ -57,7 +57,7 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, bo { builder.TransparentNetworkIPResolution = tnirEnabledInConnString.Value; } - SqlConnectionString connectionString = new(builder.ConnectionString); + SqlConnectionOptions connectionString = new(builder.ConnectionString); // Assert Assert.Equal(expectedValue, connectionString.TransparentNetworkIPResolution); @@ -83,7 +83,7 @@ public void TestDefaultMultiSubnetFailover(bool? msfInConnString, bool? msfEnabl { builder.MultiSubnetFailover = msfInConnString.Value; } - SqlConnectionString connectionString = new(builder.ConnectionString); + SqlConnectionOptions connectionString = new(builder.ConnectionString); Assert.Equal(expectedValue, connectionString.MultiSubnetFailover); } @@ -103,7 +103,7 @@ public void TestMultiSubnetFailoverWithFailoverPartnerThrows() InitialCatalog = "database" }; - Assert.Throws(() => new SqlConnectionString(builder.ConnectionString)); + Assert.Throws(() => new SqlConnectionOptions(builder.ConnectionString)); } } } From 8266a5ac604212dad13803ee3efce1301a0804e8 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 7 May 2026 08:13:50 -0300 Subject: [PATCH 086/298] Finish sqlclient-stress pipeline (#4198) --- eng/pipelines/stress/stress-tests-job.yml | 46 +++++++++++++++++-- .../stress/stress-tests-pipeline.yml | 18 ++++++-- eng/pipelines/stress/stress-tests-stage.yml | 12 ++++- .../Microsoft.Data.SqlClient.csproj | 4 +- .../ref/Microsoft.Data.SqlClient.csproj | 4 +- .../src/Microsoft.Data.SqlClient.csproj | 5 +- ...soft.Data.SqlClient.FunctionalTests.csproj | 20 ++------ ...icrosoft.Data.SqlClient.ManualTests.csproj | 20 ++------ .../tests/StressTests/Directory.Build.props | 11 ++--- .../Microsoft.Data.SqlClient.UnitTests.csproj | 18 ++------ 10 files changed, 83 insertions(+), 75 deletions(-) diff --git a/eng/pipelines/stress/stress-tests-job.yml b/eng/pipelines/stress/stress-tests-job.yml index 40cecf6e93..6eda90890b 100644 --- a/eng/pipelines/stress/stress-tests-job.yml +++ b/eng/pipelines/stress/stress-tests-job.yml @@ -48,6 +48,11 @@ parameters: - name: jobNameSuffix type: string + # True to fail the job when stress tests fail. When false, test failures produce warnings + # (SucceededWithIssues) but do not fail the job. + - name: failOnTestFailure + type: boolean + # The list of .NET Framework runtimes to test against. - name: netFrameworkTestRuntimes type: object @@ -88,10 +93,13 @@ jobs: variables: - # The directory where dotnet artifacts will be staged. Not to be - # confused with pipeline artifacts. - - name: dotnetArtifactsDir - value: $(Build.StagingDirectory)/dotnetArtifacts + # Import the variable group that provides SQL Server build properties used by the + # shared configure-sql-server-*-step.yml templates (e.g. x64AliasRegistryPath, + # x86AliasRegistryPath, SQLAliasName, SQLAliasPort). + - group: ADO Build Properties + + # Import the variable group that provides SQL Server test configuration variables. + - group: ADO Test Configuration Properties # The top-level project file to build and run. - name: project @@ -101,7 +109,6 @@ jobs: - name: dotnetArguments value: >- --verbosity ${{ parameters.dotnetVerbosity }} - --artifacts-path $(dotnetArtifactsDir) --configuration ${{ parameters.buildConfiguration }} # The contents of the config file to use for all tests. We will write this to a JSON file and @@ -153,6 +160,12 @@ jobs: # Write the JSON content to the config file. $content | Out-File -FilePath "config.json" + # Authenticate with NuGet feeds so that upstream packages (e.g. runtime host packs) can be + # fetched through the ADO Artifacts feed. This is required on hosted pool agents (macOS) + # that do not have pre-configured feed credentials. + - task: NuGetAuthenticate@1 + displayName: Authenticate NuGet feeds + # Build the project. - task: DotNetCoreCLI@2 displayName: Build Project @@ -161,10 +174,31 @@ jobs: projects: $(project) arguments: $(dotnetArguments) + # Set a flag so test steps can distinguish a build failure from a test failure. + - pwsh: Write-Host "##vso[task.setvariable variable=buildSucceeded]true" + displayName: Set build success flag + # Run the stress tests for each .NET runtime. + # + # The condition and continueOnError work together to achieve the following behavior: + # + # condition: and(succeededOrFailed(), eq(variables['buildSucceeded'], 'true')) + # - succeededOrFailed() allows the step to run even if a *previous test* step failed, + # ensuring all runtimes are exercised regardless of earlier failures. + # - eq(variables['buildSucceeded'], 'true') gates on the flag set above, so tests are + # skipped entirely if the build or any setup step failed (since there's nothing to run). + # + # continueOnError: ${{ not(parameters.failOnTestFailure) }} + # - When failOnTestFailure is false, continueOnError is true: a test failure marks the + # step and job as SucceededWithIssues (orange warning) rather than Failed. + # - When failOnTestFailure is true, continueOnError is false: a test failure fails the + # job (red), though subsequent runtimes still run due to the condition above. + # - ${{ each runtime in parameters.netTestRuntimes }}: - task: DotNetCoreCLI@2 displayName: Test [${{ runtime }}] + condition: and(succeededOrFailed(), eq(variables['buildSucceeded'], 'true')) + continueOnError: ${{ not(parameters.failOnTestFailure) }} inputs: command: run projects: $(project) @@ -174,6 +208,8 @@ jobs: - ${{ each runtime in parameters.netFrameworkTestRuntimes }}: - task: DotNetCoreCLI@2 displayName: Test [${{ runtime }}] + condition: and(succeededOrFailed(), eq(variables['buildSucceeded'], 'true')) + continueOnError: ${{ not(parameters.failOnTestFailure) }} inputs: command: run projects: $(project) diff --git a/eng/pipelines/stress/stress-tests-pipeline.yml b/eng/pipelines/stress/stress-tests-pipeline.yml index cec68912fb..1f2e356461 100644 --- a/eng/pipelines/stress/stress-tests-pipeline.yml +++ b/eng/pipelines/stress/stress-tests-pipeline.yml @@ -8,13 +8,13 @@ # # Public project: # Triggering pipelines: PR-SqlClient-Project, CI-SqlClient (branch main only) -# Pipeline name: Stress-SqlClient -# Pipeline URL: TODO: Create the Azure DevOps pipeline. +# Pipeline name: sqlclient-stress +# Pipeline URL: https://sqlclientdrivers.visualstudio.com/public/_build?definitionId=2250 # # ADO.Net project: # Triggering pipeline: MDS Main CI (branch internal/main only) -# Pipeline name: Stress-SqlClient -# Pipeline URL: TODO: Create the Azure DevOps pipeline. +# Pipeline name: sqlclient-stress +# Pipeline URL: TODO: add URL when pipeline is created # Set the pipeline run name to the day-of-year and the daily run counter. name: $(DayOfYear)$(Rev:rr) @@ -49,7 +49,7 @@ resources: # The MDS Main CI pipeline in the ADO.Net project. - pipeline: ADO-Net-Internal-CI-MDS-Main-CI project: ADO.Net - source: /Internal CI/MDS Main CI + source: /CI/MDS Main CI trigger: branches: include: @@ -73,6 +73,13 @@ parameters: type: boolean default: false + # True to fail the pipeline when stress tests fail. When false (default), test failures produce + # warnings but do not fail the overall pipeline run. + - name: failOnTestFailure + displayName: Fail pipeline on test failure + type: boolean + default: false + # Dotnet CLI verbosity level. - name: dotnetVerbosity displayName: dotnet CLI Verbosity @@ -98,4 +105,5 @@ stages: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} + failOnTestFailure: ${{ parameters.failOnTestFailure }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} diff --git a/eng/pipelines/stress/stress-tests-stage.yml b/eng/pipelines/stress/stress-tests-stage.yml index 0f0000f99a..f641ea0e7e 100644 --- a/eng/pipelines/stress/stress-tests-stage.yml +++ b/eng/pipelines/stress/stress-tests-stage.yml @@ -32,6 +32,10 @@ parameters: type: boolean default: false + # True to fail the job when stress tests fail. When false, test failures produce warnings. + - name: failOnTestFailure + type: boolean + # The verbosity level for the dotnet CLI commands. - name: dotnetVerbosity type: string @@ -48,10 +52,11 @@ parameters: type: object default: [net462] - # The list of .NET runtimes to test against. + # The list of .NET runtimes to test against. These must align with the TargetFrameworks defined + # in the stress test Directory.Build.props and the TFMs that SqlClient ships. - name: netTestRuntimes type: object - default: [net8.0, net9.0, net10.0] + default: [net8.0, net9.0] stages: - stage: stress_tests_stage @@ -75,6 +80,7 @@ stages: debug: ${{ parameters.debug }} displayNamePrefix: Linux dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + failOnTestFailure: ${{ parameters.failOnTestFailure }} jobNameSuffix: linux netTestRuntimes: ${{ parameters.netTestRuntimes }} poolName: ADO-CI-1ES-Pool @@ -94,6 +100,7 @@ stages: debug: ${{ parameters.debug }} displayNamePrefix: Win dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + failOnTestFailure: ${{ parameters.failOnTestFailure }} jobNameSuffix: windows # Note that we include the .NET Framework runtimes for test runs on Windows. netFrameworkTestRuntimes: ${{ parameters.netFrameworkTestRuntimes }} @@ -117,6 +124,7 @@ stages: debug: ${{ parameters.debug }} displayNamePrefix: macOS dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + failOnTestFailure: ${{ parameters.failOnTestFailure }} jobNameSuffix: macos netTestRuntimes: ${{ parameters.netTestRuntimes }} # We don't have any 1ES Hosted Pool images for macOS, so we use a generic one from Azure diff --git a/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj index ca4c73a6ec..9d3860476c 100644 --- a/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/notsupported/Microsoft.Data.SqlClient.csproj @@ -137,9 +137,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index 2895ce5a4a..0b6282965d 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -93,9 +93,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index cffc9fe96f..559099062a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -144,10 +144,7 @@ - - All - runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 3bc6ea9fa7..8fe4c2be23 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -63,8 +63,6 @@ - @@ -75,12 +73,8 @@ - - + + @@ -94,8 +88,6 @@ - @@ -105,12 +97,8 @@ - - + + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index cf7bbf236a..8ea8a7cecc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -357,8 +357,6 @@ - @@ -367,12 +365,8 @@ - - + + @@ -391,8 +385,6 @@ - @@ -400,12 +392,8 @@ - - + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props index 49b7a47634..910f8d53a0 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props @@ -10,18 +10,15 @@ $(MSBuildThisFileDirectory) - + net8.0;net9.0;net10.0 - + $(TargetFrameworks);net462 latest diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index 96c3c052b1..8c59d27572 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -46,19 +46,14 @@ - - - + + @@ -68,18 +63,13 @@ - - - + + From a3648e409a30debdc7161ad5554338fb2390b727 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 7 May 2026 08:15:26 -0300 Subject: [PATCH 087/298] Create YAML Kerberos Test Pipeline (#4252) Co-authored-by: Priyanka Tiwari --- .../kerberos/build-and-test-steps.yml | 144 +++++++++ eng/pipelines/kerberos/linux-cleanup-step.yml | 45 +++ eng/pipelines/kerberos/linux-init-step.yml | 117 +++++++ eng/pipelines/kerberos/sqlclient-kerberos.yml | 292 ++++++++++++++++++ .../sqlclient-pr-package-ref-pipeline.yml | 1 + .../sqlclient-pr-project-ref-pipeline.yml | 1 + 6 files changed, 600 insertions(+) create mode 100644 eng/pipelines/kerberos/build-and-test-steps.yml create mode 100644 eng/pipelines/kerberos/linux-cleanup-step.yml create mode 100644 eng/pipelines/kerberos/linux-init-step.yml create mode 100644 eng/pipelines/kerberos/sqlclient-kerberos.yml diff --git a/eng/pipelines/kerberos/build-and-test-steps.yml b/eng/pipelines/kerberos/build-and-test-steps.yml new file mode 100644 index 0000000000..307c93f978 --- /dev/null +++ b/eng/pipelines/kerberos/build-and-test-steps.yml @@ -0,0 +1,144 @@ +################################################################################# +# 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. # +################################################################################# + +# Shared build-and-test steps used by both the Windows and Linux Kerberos jobs. +# +# Parameters: +# buildTarget — The build.proj target that builds SqlClient for the current +# OS (e.g. BuildSqlClientWindows or BuildSqlClientUnix). +# testFramework — The TFM to test against (e.g. net9.0, net462). +# testRunTitle — Title for the published test results (displayed in the ADO +# Tests tab). +# artifactName — Name of the published pipeline artifact that carries the +# test results and coverage files. + +parameters: + + # build.proj target to build SqlClient for the current OS. + - name: buildTarget + type: string + + # TFM to pass to the test targets (-p:TestFramework). + - name: testFramework + type: string + + # Title shown in the ADO Tests tab for this run. + - name: testRunTitle + type: string + + # Pipeline artifact name for test results and coverage. + - name: artifactName + type: string + +steps: + + # --------------------------------------------------------------------------- + # Build + # --------------------------------------------------------------------------- + + # Build the given target. + # + # The test stages build as part of their targets, but this separate step isolates build failures + # so we can fail fast before running tests. Retries are enabled intentionally (1 attempt for + # build, 2 attempts for test steps) to reduce transient infrastructure-related failures. + # + - task: DotNetCoreCLI@2 + displayName: Build SqlClient + retryCountOnTaskFailure: 1 + inputs: + command: build + projects: build.proj + arguments: >- + -t:${{ parameters.buildTarget }} + -p:Configuration=Release + + # --------------------------------------------------------------------------- + # Run tests in separate steps to permit focused retries. + # --------------------------------------------------------------------------- + + # Run the Unit Test suite. + - task: DotNetCoreCLI@2 + displayName: Run Unit Tests (${{ parameters.testFramework }}) + retryCountOnTaskFailure: 2 + inputs: + command: build + projects: build.proj + arguments: >- + -t:TestSqlClientUnit + -p:TestFramework=${{ parameters.testFramework }} + -p:Configuration=Release + + # Run the Functional Test suite. + - task: DotNetCoreCLI@2 + displayName: Run Functional Tests (${{ parameters.testFramework }}) + retryCountOnTaskFailure: 2 + inputs: + command: build + projects: build.proj + arguments: >- + -t:TestSqlClientFunctional + -p:TestFramework=${{ parameters.testFramework }} + -p:Configuration=Release + + # Run the Manual Test suite. + - task: DotNetCoreCLI@2 + displayName: Run Manual Tests (${{ parameters.testFramework }}) + retryCountOnTaskFailure: 2 + inputs: + command: build + projects: build.proj + arguments: >- + -t:TestSqlClientManual + -p:TestFramework=${{ parameters.testFramework }} + -p:Configuration=Release + + # --------------------------------------------------------------------------- + # Publish results & coverage + # --------------------------------------------------------------------------- + + # Publish the TRX test results to the pipeline run. + - task: PublishTestResults@2 + displayName: Publish Test Results + condition: succeededOrFailed() + inputs: + testResultsFormat: VSTest + # build.proj defines TestResultsFolderPath which defaults to + # $(Build.SourcesDirectory)/test_results, so we look there for the results and coverage files. + testResultsFiles: $(Build.SourcesDirectory)/test_results/**/*.trx + mergeTestResults: true + testRunTitle: ${{ parameters.testRunTitle }} + buildConfiguration: Release + + # Azure Pipelines task conditions do not support path existence checks directly, + # so compute this once and gate later steps on the variable. + - pwsh: | + $resultsDir = "$(Build.SourcesDirectory)/test_results" + if (Test-Path -LiteralPath $resultsDir) { + Write-Host "##vso[task.setvariable variable=HasTestResultsDir]true" + } + else { + Write-Host "##vso[task.setvariable variable=HasTestResultsDir]false" + } + displayName: Detect test_results directory + condition: succeededOrFailed() + + # Give our coverage files a unique name to make it clear where they originated when we download + # the artifacts from all jobs in the merge stage. + - pwsh: | + cd $(Build.SourcesDirectory)/test_results + Get-ChildItem -Filter "*.coverage" -Recurse | + Rename-Item -NewName { "${{ parameters.testFramework }}" + $_.Name } + displayName: Rename coverage files + condition: and(succeededOrFailed(), eq(variables['HasTestResultsDir'], 'true')) + + # Publish TRX test results and coverage files as pipeline artifacts. The merge stage needs the + # coverage files from all of the jobs. + - task: PublishPipelineArtifact@1 + displayName: Publish Test Artifacts + condition: and(succeededOrFailed(), eq(variables['HasTestResultsDir'], 'true')) + inputs: + targetPath: $(Build.SourcesDirectory)/test_results + artifact: ${{ parameters.artifactName }} diff --git a/eng/pipelines/kerberos/linux-cleanup-step.yml b/eng/pipelines/kerberos/linux-cleanup-step.yml new file mode 100644 index 0000000000..d3491bb337 --- /dev/null +++ b/eng/pipelines/kerberos/linux-cleanup-step.yml @@ -0,0 +1,45 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This template leaves the Active Directory domain and destroys Kerberos +# credentials. It should be referenced at the end of any job that called +# linux-init-step.yml. +# +# All steps use condition: always() so that cleanup runs even when previous +# steps fail. + +parameters: + + # The Active Directory domain to leave (e.g. mydomain.contoso.com). + - name: kerberosDomain + type: string + + # The domain user account used during the join. + - name: kerberosDomainUser + type: string + + # The password for the domain user account. + - name: kerberosDomainPassword + type: string + +steps: + + - bash: | + set -uo pipefail + + DOMAIN="${{ parameters.kerberosDomain }}" + DOMAIN_USER="${{ parameters.kerberosDomainUser }}" + DOMAIN_PASSWORD="${{ parameters.kerberosDomainPassword }}" + DOMAIN_UPPER=$(echo "$DOMAIN" | tr '[:lower:]' '[:upper:]') + + # Leave the domain + echo "$DOMAIN_PASSWORD" | sudo realm leave "$DOMAIN_UPPER" --verbose \ + -U "$DOMAIN_USER@$DOMAIN_UPPER" || true + + # Destroy the TGT and credential cache + kdestroy || true + displayName: Clean up Kerberos (domain leave + kdestroy) + condition: always() diff --git a/eng/pipelines/kerberos/linux-init-step.yml b/eng/pipelines/kerberos/linux-init-step.yml new file mode 100644 index 0000000000..c0c4603232 --- /dev/null +++ b/eng/pipelines/kerberos/linux-init-step.yml @@ -0,0 +1,117 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This template joins a Linux agent to an Active Directory domain using Kerberos +# and acquires a TGT (Ticket-Granting Ticket) for the specified domain user. +# +# Prerequisites: +# - The agent must be running on Ubuntu/Debian (uses apt-get). +# - The domain controller must be reachable from the agent network. +# +# After this step completes successfully, the agent will have: +# - Kerberos packages installed (krb5-user, realmd, sssd, adcli, etc.) +# - Hostname set to FQDN within the domain +# - NTP synchronized with the domain controller +# - Machine joined to the AD domain +# - A valid Kerberos TGT for the specified user + +parameters: + + # The Active Directory domain to join (e.g. mydomain.contoso.com). + - name: kerberosDomain + type: string + + # The Organizational Unit in which to place the computer account. + - name: kerberosDomainOU + type: string + + # The domain user account to authenticate with (sAMAccountName, without @realm). + - name: kerberosDomainUser + type: string + + # The password for the domain user account. + - name: kerberosDomainPassword + type: string + +steps: + + - bash: | + set -euo pipefail + + DOMAIN="${{ parameters.kerberosDomain }}" + DOMAIN_OU="${{ parameters.kerberosDomainOU }}" + DOMAIN_USER="${{ parameters.kerberosDomainUser }}" + DOMAIN_PASSWORD="${{ parameters.kerberosDomainPassword }}" + DOMAIN_UPPER=$(echo "$DOMAIN" | tr '[:lower:]' '[:upper:]') + + echo "Domain: $DOMAIN" + echo "Realm: $DOMAIN_UPPER" + echo "User: $DOMAIN_USER" + echo "OU: $DOMAIN_OU" + + if [ -z "$DOMAIN_PASSWORD" ]; then + echo "##vso[task.logissue type=error]KerberosDomainPassword is empty" + exit 1 + fi + + # ----------------------------------------------------------------------- + # Install Kerberos and AD integration packages + # ----------------------------------------------------------------------- + echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections + + sudo apt-get -y update + sudo apt-get install -y dialog apt-utils + sudo apt-get install -y \ + krb5-user samba sssd sssd-tools libnss-sss libpam-sss \ + ntp ntpdate realmd adcli + + # ----------------------------------------------------------------------- + # Set the hostname to FQDN within the domain + # ----------------------------------------------------------------------- + CURRENT_HOSTNAME="$(hostname)" + if [ "$CURRENT_HOSTNAME" = "$DOMAIN" ] || [[ "$CURRENT_HOSTNAME" == *".$DOMAIN" ]]; then + echo "Hostname already uses domain suffix '.$DOMAIN': $CURRENT_HOSTNAME" + else + sudo hostnamectl set-hostname "$CURRENT_HOSTNAME.$DOMAIN" + fi + + # ----------------------------------------------------------------------- + # Synchronize time with the domain controller (required for Kerberos) + # ----------------------------------------------------------------------- + if ! sudo grep -Fqx "server $DOMAIN" /etc/ntp.conf; then + echo "server $DOMAIN" | sudo tee -a /etc/ntp.conf + fi + sudo systemctl stop ntp + sudo ntpdate "$DOMAIN" + sudo systemctl start ntp + + # ----------------------------------------------------------------------- + # Configure Kerberos realm + # ----------------------------------------------------------------------- + echo "[libdefaults] + default_realm = $DOMAIN_UPPER + rdns = false" | sudo tee /etc/krb5.conf + + # ----------------------------------------------------------------------- + # Discover and join the domain + # ----------------------------------------------------------------------- + sudo realm discover "$DOMAIN_UPPER" + + echo "$DOMAIN_PASSWORD" | sudo realm join --verbose "$DOMAIN_UPPER" \ + -U "$DOMAIN_USER@$DOMAIN_UPPER" \ + --computer-ou "OU=$DOMAIN_OU" + + realm list + + # ----------------------------------------------------------------------- + # Acquire a Kerberos TGT + # ----------------------------------------------------------------------- + echo "$DOMAIN_PASSWORD" | kinit "$DOMAIN_USER@$DOMAIN_UPPER" + + klist + sudo ip addr + sudo ip route + displayName: Initialize Kerberos (domain join + kinit) diff --git a/eng/pipelines/kerberos/sqlclient-kerberos.yml b/eng/pipelines/kerberos/sqlclient-kerberos.yml new file mode 100644 index 0000000000..1cd19961e7 --- /dev/null +++ b/eng/pipelines/kerberos/sqlclient-kerberos.yml @@ -0,0 +1,292 @@ +################################################################################# +# 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. # +################################################################################# + +# ============================================================================= +# sqlclient-kerberos +# ============================================================================= +# Daily Kerberos authentication test pipeline for Microsoft.Data.SqlClient. +# Replaces the Classic "Test-SqlClient-Kerberos-Azure" pipeline. +# +# Schedule: daily at 07:00 UTC on main, release/6.1, and release/7.0. +# +# Job breakdown (10 total): +# Stage: windows → 7 jobs (net462 + net8/9/10 × NativeSNI/ManagedSNI) +# Stage: linux → 3 jobs (net8, net9, net10 — ManagedSNI only) +# Stage: Merge-Code-Coverage → 1 job +# +# Shared steps: +# Build and test steps are defined in build-and-test-steps.yml and reused +# by both stages. Each stage passes its OS-specific build target and the +# per-job testFramework matrix variable. +# +# Required ADO variable groups (link in pipeline UI): +# - kv-sqldrivers-shared (provides the agent-at-sqldrv-ad secret) +# +# Variables defined in this file (no pipeline UI configuration needed): +# - KerberosDomain sqldrv.ad +# - KerberosDomainOU agents +# - KerberosDomainUser agent +# - KerberosDomainPassword $(agent-at-sqldrv-ad) from kv-sqldrivers-shared +# - REMOTE_TCP_CONN_STRING TCP connection string to sqldrv-sql22 +# - REMOTE_NP_CONN_STRING Named Pipe connection string to sqldrv-sql22 +# ============================================================================= + +trigger: none # Scheduled runs only — no CI trigger +pr: none # Not triggered by PRs + +schedules: + - cron: '0 7 * * *' + displayName: Daily run (07:00 UTC) + branches: + include: + - main + - release/6.1 + - release/7.0 + always: true + +name: $(date:yyyyMMdd)$(rev:.r) + +variables: + # Our Kerberos environment doesn't change often, so we can define these variables here rather than + # in the Pipeline UI or in Libraries. + - name: KerberosDomain + value: sqldrv.ad + + - name: KerberosDomainOU + value: agents + + - name: KerberosDomainUser + value: agent + + - name: KerberosDomainPassword + value: $(agent-at-sqldrv-ad) # Secret from kv-sqldrivers-shared variable group + + - name: REMOTE_TCP_CONN_STRING + value: Data Source=tcp:sqldrv-sql22.sqldrv.ad\sql2022;Initial Catalog=Northwind;Integrated Security=true;Encrypt=false;TrustServerCertificate=true + + - name: REMOTE_NP_CONN_STRING + value: Data Source=np:sqldrv-sql22.sqldrv.ad\sql2022;Initial Catalog=Northwind;Integrated Security=true;Encrypt=false;TrustServerCertificate=true + +# ============================================================================= +# STAGES +# ============================================================================= +stages: + + # =========================================================================== + # Stage 1 - Windows (7 jobs = net462 + 3 TFs × 2 ManagedSNI) + # =========================================================================== + - stage: windows + displayName: Windows + dependsOn: [] + variables: + # KerberosDomainPassword expands at runtime in this stage from the agent-at-sqldrv-ad secret + # exposed by this variable group. + - group: kv-sqldrivers-shared + jobs: + - job: windows + displayName: Windows + timeoutInMinutes: 90 + workspace: + clean: all # Purge obj/artifacts from prior runs on self-hosted agents + strategy: + matrix: + # Azure Pipelines exposes matrix variables as environment variables for each step. + # Do not use the name targetFramework here: MSBuild imports environment variables as + # properties, and TargetFramework would leak into dotnet build build.proj, forcing + # transitive project references onto an invalid TFM (for example SqlServer.Server -> net9.0). + net462_NativeSNI: + testFramework: net462 + managedSNI: 'false' + net8_NativeSNI: + testFramework: net8.0 + managedSNI: 'false' + net8_ManagedSNI: + testFramework: net8.0 + managedSNI: 'true' + net9_NativeSNI: + testFramework: net9.0 + managedSNI: 'false' + net9_ManagedSNI: + testFramework: net9.0 + managedSNI: 'true' + net10_NativeSNI: + testFramework: net10.0 + managedSNI: 'false' + net10_ManagedSNI: + testFramework: net10.0 + managedSNI: 'true' + pool: + name: ADO-Trusted-Domain-Win-WestUS2 + demands: + - ImageOverride -equals ADO-MMS22-SQL19 + steps: + + - checkout: self + clean: true + fetchDepth: 1 + fetchTags: false + + - template: /eng/pipelines/steps/install-dotnet.yml@self + parameters: + runtimes: [8.x, 9.x] + + # --- Update test configuration --- + # Uses runtime variables from the matrix ($(managedSNI)) so we use + # inline PowerShell instead of the shared config template which + # requires compile-time parameters. + - pwsh: | + $managedSni = [System.Convert]::ToBoolean($env:MANAGED_SNI) + $jdata = Get-Content -Raw "config.default.json" | ConvertFrom-Json + foreach ($p in $jdata) { + $p.TCPConnectionString = $env:REMOTE_TCP_CONN_STRING + $p.NPConnectionString = $env:REMOTE_NP_CONN_STRING + $p.SupportsIntegratedSecurity = $true + $p.UseManagedSNIOnWindows = $managedSni + } + $jdata | ConvertTo-Json | Set-Content "config.json" + workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities + displayName: Update test config.json + env: + REMOTE_TCP_CONN_STRING: $(REMOTE_TCP_CONN_STRING) + REMOTE_NP_CONN_STRING: $(REMOTE_NP_CONN_STRING) + MANAGED_SNI: $(managedSNI) + + # --- Prepare Windows services --- + - powershell: | + $svc = Get-Service -Name SQLBrowser -ErrorAction SilentlyContinue + if ($null -ne $svc) { + Set-Service -StartupType Automatic SQLBrowser + if ($svc.Status -ne 'Running') { Start-Service SQLBrowser } + Get-Service SQLBrowser | Select-Object Name, StartType, Status + } + displayName: Start SQL Server Browser + + - powershell: | + Set-DtcNetworkSetting -DtcName "Local" ` + -InboundTransactionsEnabled $true ` + -OutboundTransactionsEnabled $true ` + -RemoteClientAccessEnabled $true ` + -Confirm:$false + + Get-NetFirewallRule -DisplayName "Distributed Transaction Coordinator (RPC)" | Set-NetFirewallRule -Profile Domain -Action Allow -Enabled True + Get-NetFirewallRule -DisplayName "Distributed Transaction Coordinator (RPC-EPMAP)" | Set-NetFirewallRule -Profile Domain -Action Allow -Enabled True + Get-NetFirewallRule -DisplayName "Distributed Transaction Coordinator (TCP-Out)" | Set-NetFirewallRule -Profile Domain -Action Allow -Enabled True + Get-NetFirewallRule -DisplayName "Distributed Transaction Coordinator (TCP-In)" | Set-NetFirewallRule -Profile Domain -Action Allow -Enabled True + displayName: Enable Network DTC Access + + # --- Build and test --- + - template: /eng/pipelines/kerberos/build-and-test-steps.yml@self + parameters: + buildTarget: BuildSqlClientWindows + testFramework: $(testFramework) + testRunTitle: Windows-$(testFramework)-ManagedSNI_$(managedSNI) + artifactName: $(testFramework)-ManagedSNI_$(managedSNI)-$(System.JobId) + + # =========================================================================== + # Stage 2 - Linux - .NET Core + Kerberos (3 jobs = 3 TFs) + # =========================================================================== + - stage: linux + displayName: Linux + dependsOn: [] + variables: + # KerberosDomainPassword expands at runtime in this stage from the agent-at-sqldrv-ad secret + # exposed by this variable group. + - group: kv-sqldrivers-shared + jobs: + - job: linux + displayName: Linux + timeoutInMinutes: 90 + workspace: + clean: all # Purge leftovers on self-hosted agents to reduce cross-run flakiness + strategy: + matrix: + net8: + testFramework: net8.0 + net9: + testFramework: net9.0 + net10: + testFramework: net10.0 + pool: + name: ADO-Trusted-Linux-WestUS2 + demands: + - ImageOverride -equals ADO-UB20-SQL22 + + steps: + + - checkout: self + clean: true + fetchDepth: 1 + fetchTags: false + + # --- Install .NET SDK and runtimes --- + - template: /eng/pipelines/steps/install-dotnet.yml@self + parameters: + runtimes: [8.x, 9.x] + + # --- Update test configuration (with Kerberos credentials) --- + - pwsh: | + $jdata = Get-Content -Raw "config.default.json" | ConvertFrom-Json + foreach ($p in $jdata) { + $p.TCPConnectionString = $env:REMOTE_TCP_CONN_STRING + $p.NPConnectionString = $env:REMOTE_NP_CONN_STRING + $p.SupportsIntegratedSecurity = $true + } + $jdata | Add-Member -NotePropertyName "KerberosDomainUser" -NotePropertyValue $env:KERBEROS_DOMAIN_USER -Force + $jdata | Add-Member -NotePropertyName "KerberosDomainPassword" -NotePropertyValue $env:KERBEROS_DOMAIN_PASSWORD -Force + $jdata | ConvertTo-Json | Set-Content "config.json" + workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities + displayName: Update test config.json (Kerberos) + env: + REMOTE_TCP_CONN_STRING: $(REMOTE_TCP_CONN_STRING) + REMOTE_NP_CONN_STRING: $(REMOTE_NP_CONN_STRING) + KERBEROS_DOMAIN_USER: $(KerberosDomainUser) + KERBEROS_DOMAIN_PASSWORD: $(KerberosDomainPassword) + + # --- Kerberos domain join --- + - template: /eng/pipelines/kerberos/linux-init-step.yml@self + parameters: + kerberosDomain: $(KerberosDomain) + kerberosDomainOU: $(KerberosDomainOU) + kerberosDomainUser: $(KerberosDomainUser) + kerberosDomainPassword: $(KerberosDomainPassword) + + # --- Verify SQL connectivity --- + - pwsh: | + Install-Module -Name SqlServer -Force -Confirm:$false + Import-Module SqlServer + Invoke-Sqlcmd -Query "SELECT @@VERSION, @@SERVERNAME" -ConnectionString $env:REMOTE_TCP_CONN_STRING + displayName: Verify SQL connectivity + env: + REMOTE_TCP_CONN_STRING: $(REMOTE_TCP_CONN_STRING) + + # --- Build and test --- + - template: /eng/pipelines/kerberos/build-and-test-steps.yml@self + parameters: + buildTarget: BuildSqlClientUnix + testFramework: $(testFramework) + testRunTitle: Linux-$(testFramework) + artifactName: $(testFramework)-linux-$(System.JobId) + + # --- Kerberos cleanup (always runs) --- + - template: /eng/pipelines/kerberos/linux-cleanup-step.yml@self + parameters: + kerberosDomain: $(KerberosDomain) + kerberosDomainUser: $(KerberosDomainUser) + kerberosDomainPassword: $(KerberosDomainPassword) + + # =========================================================================== + # Stage 3 — Merge Code Coverage (1 job) + # =========================================================================== + - stage: merge + displayName: Merge Code Coverage + dependsOn: + - windows + - linux + condition: succeededOrFailed() + jobs: + - template: /eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml@self + parameters: + upload: false diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index ba2ff262bf..be7e2e9e3b 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -51,6 +51,7 @@ pr: - global.json - NuGet.config exclude: + - eng/pipelines/kerberos/* - eng/pipelines/onebranch/* - eng/pipelines/stress/* diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index a928175f17..30950ec21c 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -51,6 +51,7 @@ pr: - global.json - NuGet.config exclude: + - eng/pipelines/kerberos/* - eng/pipelines/onebranch/* - eng/pipelines/stress/* From 95d212a54b574b3f89302ea5ecce30263f502702 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 7 May 2026 08:19:56 -0300 Subject: [PATCH 088/298] Tweaks for hotfix cherry-pick workflow (#4166) --- .github/scripts/cherry-pick-to-release.sh | 21 +++++++- .../scripts/tests/cherry-pick-to-release.bats | 49 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/.github/scripts/cherry-pick-to-release.sh b/.github/scripts/cherry-pick-to-release.sh index a8612e7453..69a655d110 100755 --- a/.github/scripts/cherry-pick-to-release.sh +++ b/.github/scripts/cherry-pick-to-release.sh @@ -193,10 +193,29 @@ else lookup_milestone "${VERSION}" + # Build the PR body using printf to avoid quoting pitfalls with embedded + # newlines (mixed $'...' and '...' quoting can leave literal \n in output). + CONFLICT_BODY="$(printf '%s' \ + "Cherry-pick of #${PR_NUMBER} (${MERGE_COMMIT_SHA}) into " \ + "\`${TARGET_BRANCH}\` **failed due to merge conflicts**." \ + "${MILESTONE_NOTE}" \ + $'\n\nPlease resolve manually:\n```bash\n' \ + "git fetch origin" \ + $'\n' \ + "git checkout ${CHERRY_PICK_BRANCH}" \ + $'\n' \ + "${CHERRY_PICK_CMD}" \ + $'\n' \ + "# resolve conflicts" \ + $'\n' \ + "git push origin ${CHERRY_PICK_BRANCH} --force" \ + $'\n```')" + gh pr create \ + --draft \ --base "${TARGET_BRANCH}" \ --head "${CHERRY_PICK_BRANCH}" \ --title "[${VERSION} Cherry-pick - CONFLICTS] ${PR_TITLE}" \ ${MILESTONE_ARG} \ - --body $'Cherry-pick of #'"${PR_NUMBER}"' ('"${MERGE_COMMIT_SHA}"') into `'"${TARGET_BRANCH}"'` **failed due to merge conflicts**.'"${MILESTONE_NOTE}"$'\n\nPlease resolve manually:\n```bash\ngit fetch origin\ngit checkout '"${CHERRY_PICK_BRANCH}"'\n'"${CHERRY_PICK_CMD}"$'\n# resolve conflicts\ngit push origin '"${CHERRY_PICK_BRANCH}"' --force\n```' + --body "${CONFLICT_BODY}" fi diff --git a/.github/scripts/tests/cherry-pick-to-release.bats b/.github/scripts/tests/cherry-pick-to-release.bats index 68ef3d7f8c..3484b48ea9 100644 --- a/.github/scripts/tests/cherry-pick-to-release.bats +++ b/.github/scripts/tests/cherry-pick-to-release.bats @@ -296,3 +296,52 @@ STUB # Should have created an empty commit. grep -q "GIT: commit --allow-empty" "${STUB_DIR}/git.log" } + +@test "conflict PR body contains real newlines, not literal backslash-n" { + write_git_mock ' + if [[ "$1" == "fetch" ]]; then exit 0; fi + if [[ "$1" == "cherry" ]]; then echo "+ abc123"; exit 0; fi + if [[ "$1" == "checkout" ]]; then exit 0; fi + if [[ "$1" == "rev-list" ]]; then echo "abc123def456 parent1"; exit 0; fi + if [[ "$1" == "cherry-pick" ]]; then + if [[ "$2" == "--abort" ]]; then exit 0; fi + exit 1 + fi + if [[ "$1" == "commit" ]]; then exit 0; fi + if [[ "$1" == "push" ]]; then exit 0; fi + exit 0 + ' + # Capture the full --body argument to a file for inspection. + write_gh_mock ' + if [[ "$1" == "api" ]]; then echo "7.0.1"; exit 0; fi + if [[ "$1" == "pr" && "$2" == "create" ]]; then + while [[ $# -gt 0 ]]; do + if [[ "$1" == "--body" ]]; then + printf "%s" "$2" > "'"${STUB_DIR}"'/pr-body.txt" + break + fi + shift + done + exit 0 + fi + exit 0 + ' + + run bash "${SCRIPT}" + [ "$status" -eq 0 ] + + # The body file must exist (gh pr create was called with --body). + [ -f "${STUB_DIR}/pr-body.txt" ] + + local body + body="$(cat "${STUB_DIR}/pr-body.txt")" + + # Must NOT contain literal two-character sequence '\n'. + [[ "$body" != *'\\n'* ]] + # Each command in the code block must be on its own line. + [[ "$body" == *$'\ngit fetch origin\n'* ]] + [[ "$body" == *$'\ngit checkout dev/automation/pr-42-to-7.0.1\n'* ]] + [[ "$body" == *$'\ngit cherry-pick abc123def456\n'* ]] + [[ "$body" == *$'\n# resolve conflicts\n'* ]] + [[ "$body" == *$'\ngit push origin dev/automation/pr-42-to-7.0.1 --force\n'* ]] +} From 4a3639970f35bc4c5e67432fbedd9f193727d1f0 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 7 May 2026 06:43:26 -0700 Subject: [PATCH 089/298] Remove extra connection options parameters (#4261) --- .../Data/ProviderBase/DbConnectionClosed.cs | 21 ++- .../Data/ProviderBase/DbConnectionInternal.cs | 13 +- .../Connection/SqlConnectionInternal.cs | 31 +--- .../ConnectionPool/ChannelDbConnectionPool.cs | 12 +- .../ConnectionPool/IDbConnectionPool.cs | 8 +- .../WaitHandleDbConnectionPool.cs | 35 ++--- .../Microsoft/Data/SqlClient/SqlConnection.cs | 4 +- .../Data/SqlClient/SqlConnectionFactory.cs | 37 ++--- .../ChannelDbConnectionPoolTest.cs | 46 +----- .../TransactedConnectionPoolTest.cs | 4 +- ...itHandleDbConnectionPoolTransactionTest.cs | 5 +- .../Microsoft/Data/SqlClient/CasTest.cs | 135 ++++++++++++++++++ 12 files changed, 186 insertions(+), 165 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/CasTest.cs 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 2bb0c202a7..96704bacce 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -62,9 +62,8 @@ protected internal override Task GetSchemaAsync( internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) => - TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); + TaskCompletionSource retry) => + TryOpenConnectionInternal(outerConnection, connectionFactory, retry); /// internal override void ResetConnection() => throw ADP.ClosedConnectionError(); @@ -79,8 +78,7 @@ protected DbConnectionBusy(ConnectionState state) : base(state, true, false) internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) + TaskCompletionSource retry) => throw ADP.ConnectionAlreadyOpen(State); } @@ -121,15 +119,13 @@ internal override void CloseConnection(DbConnection owningObject, SqlConnectionF internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) => - TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); + TaskCompletionSource retry) => + TryOpenConnection(outerConnection, connectionFactory, retry); internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) + TaskCompletionSource retry) { if (retry == null || !retry.Task.IsCompleted) { @@ -177,8 +173,7 @@ private DbConnectionClosedPreviouslyOpened() : base(ConnectionState.Closed, true internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) => - TryOpenConnection(outerConnection, connectionFactory, retry, userOptions); + TaskCompletionSource retry) => + TryOpenConnection(outerConnection, connectionFactory, retry); } } 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 b6b6d727ed..ac3949c726 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -688,7 +688,7 @@ internal void MakePooledConnection(IDbConnectionPool connectionPool) internal virtual void OpenConnection(DbConnection outerConnection, SqlConnectionFactory connectionFactory) { - if (!TryOpenConnection(outerConnection, connectionFactory, null, null)) + if (!TryOpenConnection(outerConnection, connectionFactory, null)) { throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending); } @@ -800,8 +800,7 @@ internal void SetInStasis() internal virtual bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) + TaskCompletionSource retry) { throw ADP.ConnectionAlreadyOpen(State); } @@ -809,8 +808,7 @@ internal virtual bool TryOpenConnection( internal virtual bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) + TaskCompletionSource retry) { throw ADP.MethodNotImplemented(); } @@ -912,8 +910,7 @@ protected virtual void ReleaseAdditionalLocksForClose(bool lockToken) protected bool TryOpenConnectionInternal( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) + TaskCompletionSource retry) { // ?->Connecting: prevent set_ConnectionString during Open if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this)) @@ -922,7 +919,7 @@ protected bool TryOpenConnectionInternal( try { connectionFactory.PermissionDemand(outerConnection); - if (!connectionFactory.TryGetConnection(outerConnection, retry, userOptions, this, out openConnection)) + if (!connectionFactory.TryGetConnection(outerConnection, retry, this, out openConnection)) { return false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 6c5be8930c..e62541137c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -312,8 +312,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable /// - 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). - /// - userConnectionOptions may be different to connectionOptions if the connection string - /// has been expanded (see SqlConnectionOptions.Expand) /// // @TODO: We really really need simplify what we pass into this. All these optional parameters need to go! internal SqlConnectionInternal( @@ -324,7 +322,6 @@ internal SqlConnectionInternal( string newPassword, SecureString newSecurePassword, bool redirectedUserInstance, - SqlConnectionOptions userConnectionOptions = null, SessionData reconnectSessionData = null, bool applyTransientFaultHandling = false, string accessToken = null, @@ -341,29 +338,6 @@ internal SqlConnectionInternal( { 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 (SecurityException) - { - Debug.Assert(false, "unexpected SecurityException for current codepath"); - throw; - } - #endif #endif Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, @@ -1970,10 +1944,9 @@ internal void OnLoginAck(SqlLoginAck rec) internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry, - SqlConnectionOptions userOptions) + TaskCompletionSource retry) { - return TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions); + return TryOpenConnectionInternal(outerConnection, connectionFactory, retry); } internal void ValidateConnectionForExecute(SqlCommand command) 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 index 41d49508be..b83434cac6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -224,7 +224,6 @@ public void PutObjectFromTransactedPool(DbConnectionInternal connection) /// public DbConnectionInternal ReplaceConnection( DbConnection owningObject, - SqlConnectionOptions userOptions, DbConnectionInternal oldConnection) { throw new NotImplementedException(); @@ -282,7 +281,6 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans public bool TryGetConnection( DbConnection owningObject, TaskCompletionSource? taskCompletionSource, - SqlConnectionOptions userOptions, out DbConnectionInternal? connection) { var timeout = TimeSpan.FromSeconds(owningObject.ConnectionTimeout); @@ -292,7 +290,6 @@ public bool TryGetConnection( { var task = GetInternalConnection( owningObject, - userOptions, async: false, timeout); @@ -340,7 +337,6 @@ public bool TryGetConnection( { connection = await GetInternalConnection( owningObject, - userOptions, async: true, timeout ).ConfigureAwait(false); @@ -375,7 +371,6 @@ public bool TryGetConnection( /// Opens a new internal connection to the database. /// /// The owning connection. - /// The options for the connection. /// The cancellation token to cancel the operation. /// A task representing the asynchronous operation, with a result of the new internal connection. /// @@ -383,7 +378,6 @@ public bool TryGetConnection( /// private DbConnectionInternal? OpenNewInternalConnection( DbConnection? owningConnection, - SqlConnectionOptions userOptions, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -405,8 +399,7 @@ public bool TryGetConnection( // when this support is added to DbConnectionInternal. var connection = ConnectionFactory.CreatePooledConnection( owningConnection, - this, - userOptions); + this); if (connection is not null) { @@ -498,7 +491,6 @@ private void RemoveConnection(DbConnectionInternal connection) /// Gets an internal connection from the pool, either by retrieving an idle connection or opening a new one. /// /// The DbConnection that will own this internal connection - /// The user options to set on the internal connection /// A boolean indicating whether the operation should be asynchronous. /// The timeout for the operation. /// Returns a DbConnectionInternal that is retrieved from the pool. @@ -512,7 +504,6 @@ private void RemoveConnection(DbConnectionInternal connection) /// private async Task GetInternalConnection( DbConnection owningConnection, - SqlConnectionOptions userOptions, bool async, TimeSpan timeout) { @@ -533,7 +524,6 @@ private async Task GetInternalConnection( // If we didn't find an idle connection, try to open a new one. connection ??= OpenNewInternalConnection( owningConnection, - userOptions, cancellationToken); // If we're at max capacity and couldn't open a connection. Block on the idle channel with a 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 index 904c19f28e..43dae3fa0f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -119,19 +119,17 @@ internal interface IDbConnectionPool /// 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, SqlConnectionOptions userOptions, out DbConnectionInternal? connection); + bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, 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 connection whose internal connection should be replaced. /// The internal connection currently associated with the owning object. /// A reference to the new DbConnectionInternal. - DbConnectionInternal ReplaceConnection(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection); + DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection); /// /// Returns an internal connection to the pool. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 96a1a6e5f3..4243e777ba 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -61,17 +61,15 @@ internal sealed class WaitHandleDbConnectionPool : IDbConnectionPool private sealed class PendingGetConnection { - public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource completion, SqlConnectionOptions userOptions) + public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource completion) { DueTime = dueTime; Owner = owner; Completion = completion; - UserOptions = userOptions; } public long DueTime { get; private set; } public DbConnection Owner { get; private set; } public TaskCompletionSource Completion { get; private set; } - public SqlConnectionOptions UserOptions { get; private set; } } private sealed class PoolWaitHandles @@ -522,7 +520,7 @@ private bool IsBlockingPeriodEnabled() } } - private DbConnectionInternal CreateObject(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection) + private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionInternal oldConnection) { DbConnectionInternal newObj = null; @@ -530,8 +528,7 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, SqlConnecti { newObj = _connectionFactory.CreatePooledConnection( owningObject, - this, - userOptions); + this); lock (_objectList) { @@ -837,7 +834,6 @@ private void WaitForPendingOpen() delay, allowCreate: true, onlyOneCheckConnection: false, - next.UserOptions, out connection); } // @TODO: CER Exception Handling was removed here (see GH#3581) @@ -875,7 +871,7 @@ private void WaitForPendingOpen() } while (_pendingOpens.TryPeek(out next)); } - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, SqlConnectionOptions userOptions, out DbConnectionInternal connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, out DbConnectionInternal connection) { uint waitForMultipleObjectsTimeout = 0; bool allowCreate = false; @@ -901,7 +897,7 @@ public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource {0}, Creating new connection.", Id); try { - obj = UserCreateRequest(owningObject, userOptions); + obj = UserCreateRequest(owningObject); } catch { @@ -1045,7 +1040,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj if (semaphoreHolder.Obtained) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", Id); - obj = UserCreateRequest(owningObject, userOptions); + obj = UserCreateRequest(owningObject); } else { @@ -1131,13 +1126,12 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o /// Creates a new connection to replace an existing connection /// /// Outer connection that currently owns - /// Options used to create the new connection /// Inner connection that will be replaced /// A new inner connection that is attached to the - public DbConnectionInternal ReplaceConnection(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", Id); - DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection); + DbConnectionInternal newConnection = UserCreateRequest(owningObject, oldConnection); if (newConnection != null) { @@ -1277,8 +1271,7 @@ private void PoolCreateRequest(object state) { try { - // Don't specify any user options because there is no outer connection associated with the new connection - newObj = CreateObject(owningObject: null, userOptions: null, oldConnection: null); + newObj = CreateObject(owningObject: null, oldConnection: null); } catch { @@ -1522,7 +1515,7 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans } } - private DbConnectionInternal UserCreateRequest(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection = null) + private DbConnectionInternal UserCreateRequest(DbConnection owningObject, DbConnectionInternal oldConnection = null) { // called by user when they were not able to obtain a free object but // instead obtained creation mutex @@ -1542,7 +1535,7 @@ private DbConnectionInternal UserCreateRequest(DbConnection owningObject, SqlCon // TODO: Consider implement a control knob here; why do we only check for dead objects ever other time? why not every 10th time or every time? if ((oldConnection != null) || (Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects()) { - obj = CreateObject(owningObject, userOptions, oldConnection); + obj = CreateObject(owningObject, oldConnection); } } return obj; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index b0c9cc4289..aa9d8edc06 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -2228,14 +2228,14 @@ private bool TryOpenInner(TaskCompletionSource retry) { if (ForceNewConnection) { - if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry, UserConnectionOptions)) + if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry)) { return false; } } else { - if (!InnerConnection.TryOpenConnection(this, ConnectionFactory, retry, UserConnectionOptions)) + if (!InnerConnection.TryOpenConnection(this, ConnectionFactory, retry)) { return false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 981ca17641..30010d0d4b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -115,8 +115,7 @@ internal DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(SqlConnec internal DbConnectionInternal CreateNonPooledConnection( DbConnection owningConnection, - DbConnectionPoolGroup poolGroup, - SqlConnectionOptions userOptions) + DbConnectionPoolGroup poolGroup) { Debug.Assert(owningConnection is not null, "null owningConnection?"); Debug.Assert(poolGroup is not null, "null poolGroup?"); @@ -126,8 +125,7 @@ internal DbConnectionInternal CreateNonPooledConnection( poolGroup.PoolKey, poolGroup.ProviderInfo, pool: null, - owningConnection, - userOptions); + owningConnection); if (newConnection is not null) { SqlClientDiagnostics.Metrics.HardConnectRequest(); @@ -140,8 +138,7 @@ internal DbConnectionInternal CreateNonPooledConnection( internal DbConnectionInternal CreatePooledConnection( DbConnection owningConnection, - IDbConnectionPool pool, - SqlConnectionOptions userOptions) + IDbConnectionPool pool) { Debug.Assert(pool != null, "null pool?"); @@ -150,8 +147,7 @@ internal DbConnectionInternal CreatePooledConnection( pool.PoolGroup.PoolKey, pool.PoolGroup.ProviderInfo, pool, - owningConnection, - userOptions); + owningConnection); if (newConnection is null) { @@ -316,7 +312,6 @@ internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) internal bool TryGetConnection( DbConnection owningConnection, TaskCompletionSource retry, - SqlConnectionOptions userOptions, DbConnectionInternal oldConnection, out DbConnectionInternal connection) { @@ -387,7 +382,6 @@ internal bool TryGetConnection( s_pendingOpenNonPooled[idx], owningConnection, retry, - userOptions, oldConnection, poolGroup, cancellationTokenSource); @@ -413,7 +407,7 @@ internal bool TryGetConnection( return false; } - connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions); + connection = CreateNonPooledConnection(owningConnection, poolGroup); SqlClientDiagnostics.Metrics.EnterNonPooledConnection(); } @@ -423,11 +417,11 @@ internal bool TryGetConnection( { Debug.Assert(oldConnection is not DbConnectionClosed, "Force new connection, but there is no old connection"); - connection = connectionPool.ReplaceConnection(owningConnection, userOptions, oldConnection); + connection = connectionPool.ReplaceConnection(owningConnection, oldConnection); } else { - if (!connectionPool.TryGetConnection(owningConnection, retry, userOptions, out connection)) + if (!connectionPool.TryGetConnection(owningConnection, retry, out connection)) { return false; } @@ -579,8 +573,7 @@ protected virtual DbConnectionInternal CreateConnection( ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, - DbConnection owningConnection, - SqlConnectionOptions userOptions) + DbConnection owningConnection) { SqlConnectionOptions opt = options; ConnectionPoolKey key = poolKey; @@ -589,16 +582,6 @@ protected virtual DbConnectionInternal CreateConnection( SqlConnection sqlOwningConnection = owningConnection as SqlConnection; bool applyTransientFaultHandling = sqlOwningConnection?._applyTransientFaultHandling ?? false; - SqlConnectionOptions userOpt = null; - if (userOptions != null) - { - userOpt = userOptions; - } - else if (sqlOwningConnection != null) - { - userOpt = sqlOwningConnection.UserConnectionOptions; - } - if (sqlOwningConnection != null) { recoverySessionData = sqlOwningConnection._recoverySessionData; @@ -700,7 +683,6 @@ protected virtual DbConnectionInternal CreateConnection( newPassword: string.Empty, newSecurePassword: null, redirectedUserInstance, - userOpt, recoverySessionData, applyTransientFaultHandling, key.AccessToken, @@ -768,7 +750,6 @@ private Task CreateReplaceConnectionContinuation( Task task, DbConnection owningConnection, TaskCompletionSource retry, - SqlConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionPoolGroup poolGroup, CancellationTokenSource cancellationTokenSource) @@ -781,7 +762,7 @@ private Task CreateReplaceConnectionContinuation( { ADP.SetCurrentTransaction(retry.Task.AsyncState as System.Transactions.Transaction); - DbConnectionInternal newConnection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions); + DbConnectionInternal newConnection = CreateNonPooledConnection(owningConnection, poolGroup); if (oldConnection?.State == ConnectionState.Open) { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index 2b1d437f98..7c1593af14 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -63,7 +63,6 @@ public void GetConnectionEmptyPool_ShouldCreateNewConnection(int numConnections) var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -93,7 +92,6 @@ public async Task GetConnectionAsyncEmptyPool_ShouldCreateNewConnection(int numC var completed = pool.TryGetConnection( new SqlConnection(), tcs, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -119,7 +117,6 @@ public void GetConnectionMaxPoolSize_ShouldTimeoutAfterPeriod() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -133,7 +130,6 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection("Timeout=1"), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? extraConnection ); } @@ -159,7 +155,6 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldTimeoutAfterPeriod() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -174,7 +169,6 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection("Timeout=1"), taskCompletionSource, - new SqlConnectionOptions(""), out DbConnectionInternal? extraConnection ); await taskCompletionSource.Task; @@ -200,7 +194,6 @@ public async Task GetConnectionMaxPoolSize_ShouldReuseAfterConnectionReleased() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? firstConnection ); @@ -209,7 +202,6 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -225,7 +217,6 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? extraConnection ); return extraConnection; @@ -247,7 +238,6 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldReuseAfterConnectionReleas pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? firstConnection ); @@ -256,7 +246,6 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -270,7 +259,6 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), taskCompletionSource, - new SqlConnectionOptions(""), out DbConnectionInternal? recycledConnection ); pool.ReturnInternalConnection(firstConnection!, firstOwningConnection); @@ -291,7 +279,6 @@ public async Task GetConnectionMaxPoolSize_ShouldRespectOrderOfRequest() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? firstConnection ); @@ -300,7 +287,6 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -321,7 +307,6 @@ out DbConnectionInternal? internalConnection pool.TryGetConnection( new SqlConnection(""), null, - new SqlConnectionOptions(""), out DbConnectionInternal? recycledConnection ); return recycledConnection; @@ -334,7 +319,6 @@ out DbConnectionInternal? recycledConnection pool.TryGetConnection( new SqlConnection("Timeout=1"), null, - new SqlConnectionOptions(""), out DbConnectionInternal? failedConnection ); return failedConnection; @@ -360,7 +344,6 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldRespectOrderOfRequest() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? firstConnection ); @@ -369,7 +352,6 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -384,7 +366,6 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), recycledTaskCompletionSource, - new SqlConnectionOptions(""), out DbConnectionInternal? recycledConnection ); @@ -394,7 +375,6 @@ out DbConnectionInternal? recycledConnection var exceeded2 = pool.TryGetConnection( new SqlConnection("Timeout=1"), failedCompletionSource, - new SqlConnectionOptions(""), out DbConnectionInternal? failedConnection ); @@ -417,7 +397,6 @@ public void ConnectionsAreReused() var completed1 = pool.TryGetConnection( owningConnection, null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection1 ); @@ -432,7 +411,6 @@ out DbConnectionInternal? internalConnection1 var completed2 = pool.TryGetConnection( owningConnection, null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection2 ); @@ -454,7 +432,6 @@ public void GetConnectionTimeout_ShouldThrowTimeoutException() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); }); @@ -475,7 +452,6 @@ public async Task GetConnectionAsyncTimeout_ShouldThrowTimeoutException() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); @@ -500,7 +476,6 @@ public void StressTest() var completed = pool.TryGetConnection( owningObject, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); if (completed) @@ -534,7 +509,6 @@ public void StressTestAsync() var completed = pool.TryGetConnection( owningObject, taskCompletionSource, - new SqlConnectionOptions(""), out DbConnectionInternal? internalConnection ); internalConnection = await taskCompletionSource.Task; @@ -692,7 +666,7 @@ public void TestPutObjectFromTransactedPool() public void TestReplaceConnection() { var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => pool.ReplaceConnection(null!, null!, null!)); + Assert.Throws(() => pool.ReplaceConnection(null!, null!)); } [Fact] @@ -731,7 +705,6 @@ public void Clear_MultipleIdleConnections_AllAreDestroyed() pool.TryGetConnection( owningConnections[i], taskCompletionSource: null, - new SqlConnectionOptions(""), out internalConnections[i] ); Assert.Equal(0, internalConnections[i]!.ClearGeneration); @@ -760,7 +733,6 @@ public void Clear_BusyConnection_NotDestroyedImmediately() pool.TryGetConnection( owningConnection, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? busyConnection ); Assert.NotNull(busyConnection); @@ -784,7 +756,6 @@ public void Clear_BusyConnectionReturned_IsDestroyed() pool.TryGetConnection( owningConnection, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? busyConnection ); Assert.NotNull(busyConnection); @@ -814,13 +785,11 @@ public void Clear_MixedBusyAndIdle_OnlyIdleDestroyedImmediately() pool.TryGetConnection( busyOwner, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? busyConnection ); pool.TryGetConnection( idleOwner, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? idleConnection ); Assert.NotNull(busyConnection); @@ -853,7 +822,6 @@ public void Clear_NewConnectionsAfterClear_ArePooledNormally() pool.TryGetConnection( owningConnection, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? oldConnection ); Assert.Equal(0, oldConnection!.ClearGeneration); @@ -867,7 +835,6 @@ out DbConnectionInternal? oldConnection pool.TryGetConnection( newOwner, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? newConnection ); Assert.NotNull(newConnection); @@ -885,7 +852,6 @@ out DbConnectionInternal? newConnection pool.TryGetConnection( reuseOwner, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? reusedConnection ); Assert.Same(newConnection, reusedConnection); @@ -902,7 +868,6 @@ public void Clear_MultipleClearCalls_DoNotCorruptState() pool.TryGetConnection( owningConnection, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? connection ); Assert.Equal(0, connection!.ClearGeneration); @@ -921,7 +886,6 @@ out DbConnectionInternal? connection pool.TryGetConnection( newOwner, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? newConnection ); Assert.NotNull(newConnection); @@ -939,8 +903,7 @@ protected override DbConnectionInternal CreateConnection( ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, - DbConnection owningConnection, - SqlConnectionOptions userOptions) + DbConnection owningConnection) { return new StubDbConnectionInternal(); } @@ -953,8 +916,7 @@ protected override DbConnectionInternal CreateConnection( ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, - DbConnection owningConnection, - SqlConnectionOptions userOptions) + DbConnection owningConnection) { throw ADP.PooledOpenTimeout(); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs index c03c7cc550..521ded14b8 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs @@ -676,12 +676,12 @@ internal class MockDbConnectionPool : IDbConnectionPool public void Clear() => throw new NotImplementedException(); - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, SqlConnectionOptions userOptions, out DbConnectionInternal? connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, out DbConnectionInternal? connection) { throw new NotImplementedException(); } - public DbConnectionInternal ReplaceConnection(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection) { throw new NotImplementedException(); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs index 4b4a30ce19..a5e5d0339f 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs @@ -82,7 +82,6 @@ private DbConnectionInternal GetConnection(SqlConnection owner) _pool.TryGetConnection( owner, taskCompletionSource: null, - new SqlConnectionOptions(""), out DbConnectionInternal? connection); return connection!; } @@ -95,7 +94,6 @@ private async Task GetConnectionAsync( _pool.TryGetConnection( owner, taskCompletionSource: tcs, - new SqlConnectionOptions(""), out DbConnectionInternal? connection); return connection ?? await tcs.Task; } @@ -904,8 +902,7 @@ protected override DbConnectionInternal CreateConnection( ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, - DbConnection owningConnection, - SqlConnectionOptions userOptions) + DbConnection owningConnection) { return new MockDbConnectionInternal(); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/CasTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/CasTest.cs new file mode 100644 index 0000000000..d69ca287d5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/CasTest.cs @@ -0,0 +1,135 @@ +// 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; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests +{ + /// + /// Tests for Code Access Security (CAS) permission demands in SqlConnection. + /// + /// CAS is a .NET Framework security mechanism that restricts what code can do based on + /// its origin and identity. SqlConnection.Open() calls DemandPermission(), which performs + /// a stack walk to verify that all callers have been granted SqlClientPermission. If any + /// caller lacks the permission, a SecurityException is thrown before any network I/O occurs. + /// + /// These tests use AppDomain sandboxing to create partial-trust environments with controlled + /// permission sets, proving that the permission demand is enforced during Open(). + /// + public class CasTest + { + /// + /// Verify that calling Open() in a partial-trust sandbox without SqlClientPermission + /// causes a SecurityException. Paired with the next test, this experimentally proves + /// that SqlClientPermission is the specific permission being demanded: the only + /// difference between the two sandboxes is SqlClientPermission being granted. + /// + [Fact] + public void Open_WithoutSqlClientPermission_ThrowsSecurityException() + { + var permissions = new System.Security.PermissionSet(System.Security.Permissions.PermissionState.None); + permissions.AddPermission(new System.Security.Permissions.SecurityPermission( + System.Security.Permissions.SecurityPermissionFlag.Execution)); + + var setup = new AppDomainSetup + { + ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + }; + + AppDomain sandbox = AppDomain.CreateDomain("PermissionTest_NoPerm", null, setup, permissions); + try + { + Assert.Throws(() => + sandbox.DoCallBack(OpenConnectionInSandbox)); + } + finally + { + AppDomain.Unload(sandbox); + } + } + + /// + /// Same sandbox as above, but with a narrow SqlClientPermission granted for the exact + /// connection string used in OpenConnectionInSandbox. The SecurityException goes away, + /// proving that the CAS demand is specifically for SqlClientPermission scoped to that + /// connection string. (Open still fails with a non-security error because we can't connect.) + /// + [Fact] + public void Open_WithSqlClientPermission_DoesNotThrowSecurityException() + { + var permissions = new System.Security.PermissionSet(System.Security.Permissions.PermissionState.None); + permissions.AddPermission(new System.Security.Permissions.SecurityPermission( + System.Security.Permissions.SecurityPermissionFlag.Execution)); + var sqlPerm = new SqlClientPermission(System.Security.Permissions.PermissionState.None); + sqlPerm.Add("Server=will_not_resolve_12345;Connect Timeout=1;", "", KeyRestrictionBehavior.AllowOnly); + permissions.AddPermission(sqlPerm); + + var setup = new AppDomainSetup + { + ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + }; + + AppDomain sandbox = AppDomain.CreateDomain("PermissionTest_WithPerm", null, setup, permissions); + try + { + var ex = Record.Exception(() => sandbox.DoCallBack(OpenConnectionInSandbox)); + Assert.NotNull(ex); + Assert.IsNotType(ex); + } + finally + { + AppDomain.Unload(sandbox); + } + } + + /// + /// Grant a narrow SqlClientPermission for a different server than the one used in Open(). + /// This proves that the permission demand checks the connection string content, not just + /// whether any SqlClientPermission is present. + /// + [Fact] + public void Open_WithMismatchedSqlClientPermission_ThrowsSecurityException() + { + var permissions = new System.Security.PermissionSet(System.Security.Permissions.PermissionState.None); + permissions.AddPermission(new System.Security.Permissions.SecurityPermission( + System.Security.Permissions.SecurityPermissionFlag.Execution)); + var sqlPerm = new SqlClientPermission(System.Security.Permissions.PermissionState.None); + sqlPerm.Add("Server=some_other_server;", "", KeyRestrictionBehavior.AllowOnly); + permissions.AddPermission(sqlPerm); + + var setup = new AppDomainSetup + { + ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + }; + + AppDomain sandbox = AppDomain.CreateDomain("PermissionTest_Mismatch", null, setup, permissions); + try + { + var ex = Record.Exception(() => sandbox.DoCallBack(OpenConnectionInSandbox)); + Assert.NotNull(ex); + // The demand fails because the granted permission ("some_other_server") doesn't + // cover the demanded permission ("will_not_resolve_12345"). This surfaces as + // SecurityException or FileLoadException (the CLR encounters a circular assembly + // load when constructing SecurityException with the custom permission type). + Assert.True( + ex is System.Security.SecurityException || ex is System.IO.FileLoadException, + $"Expected security failure, got {ex.GetType().FullName}: {ex.Message}"); + } + finally + { + AppDomain.Unload(sandbox); + } + } + + private static void OpenConnectionInSandbox() + { + using var connection = new SqlConnection("Server=will_not_resolve_12345;Connect Timeout=1;"); + connection.Open(); + } + } +} +#endif From 959d2fc46826f54f58a82de68fdd01d57925d3b6 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 7 May 2026 12:52:38 -0300 Subject: [PATCH 090/298] Restrict kerberos pipeline to main branch (#4265) --- eng/pipelines/kerberos/sqlclient-kerberos.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eng/pipelines/kerberos/sqlclient-kerberos.yml b/eng/pipelines/kerberos/sqlclient-kerberos.yml index 1cd19961e7..a480ee8fb7 100644 --- a/eng/pipelines/kerberos/sqlclient-kerberos.yml +++ b/eng/pipelines/kerberos/sqlclient-kerberos.yml @@ -10,7 +10,7 @@ # Daily Kerberos authentication test pipeline for Microsoft.Data.SqlClient. # Replaces the Classic "Test-SqlClient-Kerberos-Azure" pipeline. # -# Schedule: daily at 07:00 UTC on main, release/6.1, and release/7.0. +# Schedule: daily at 07:00 UTC on main. # # Job breakdown (10 total): # Stage: windows → 7 jobs (net462 + net8/9/10 × NativeSNI/ManagedSNI) @@ -43,8 +43,6 @@ schedules: branches: include: - main - - release/6.1 - - release/7.0 always: true name: $(date:yyyyMMdd)$(rev:.r) From 822dca423b79699d7f612f0a49ac05d7ef37bb18 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 8 May 2026 16:10:26 -0300 Subject: [PATCH 091/298] Migrate SqlClient packaging to dotnet pack and centralize pack fallback (#4257) --- .github/copilot-instructions.md | 8 + AGENTS.md | 11 + BUILDGUIDE.md | 67 ++--- TESTGUIDE.md | 44 ++-- build.proj | 138 +++------- .../templates/jobs/ci-build-nugets-job.yml | 33 ++- .../steps/generate-nuget-package-step.yml | 80 ------ .../template/release-notes-template.md | 2 +- .../src/Versions.props | 4 +- .../Abstractions/src/Versions.props | 4 +- .../Azure/src/Versions.props | 4 +- .../Logging/src/Versions.props | 4 +- src/Microsoft.Data.SqlClient/Versions.props | 4 +- .../src/Microsoft.Data.SqlClient.csproj | 124 ++++++++- .../src/Microsoft.Data.SqlClient.nuspec | 238 ++++++++++++++++++ src/Microsoft.SqlServer.Server/Versions.props | 4 +- tools/scripts/downloadLatestNuget.ps1 | 27 -- tools/specs/Microsoft.Data.SqlClient.nuspec | 234 ----------------- 18 files changed, 493 insertions(+), 537 deletions(-) delete mode 100644 eng/pipelines/common/templates/steps/generate-nuget-package-step.yml create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.nuspec delete mode 100644 tools/scripts/downloadLatestNuget.ps1 delete mode 100644 tools/specs/Microsoft.Data.SqlClient.nuspec diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 0c78f461cb..51ce83bb10 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -150,6 +150,14 @@ When a new issue is created, follow these steps: - Do not modify `CHANGELOG.md` unless executing a release workflow (see `release-notes` prompt). - Do not close issues without a fix or without providing a clear reason. +## Terminal Execution Safety +- Treat any non-zero shell exit code as a failed step that requires correction before proceeding. +- If a bash process exits, do not wait for more output from that process; rerun the command in a fresh terminal session. +- Validate that expected command output was produced before using it as evidence for conclusions. +- When terminal execution fails, surface the failure immediately and retry with a corrected command. +- Avoid `set -e` in this automation workflow; use focused commands and verify each result explicitly so shell exits are observable and attributable. +- Prefer short, single-purpose terminal commands over long chained scripts when debugging or gathering state. + ## 📝 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. diff --git a/AGENTS.md b/AGENTS.md index 690e9c9c8d..42f3a39399 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,6 +54,17 @@ This repository provides reusable prompts in `.github/prompts/` for common maint 6. **Performance Optimization**: Use pooling, async, efficient allocations 7. **Observability**: EventSource tracing, meaningful errors +## Terminal Reliability Rules + +When using shell/terminal tools, follow these rules strictly: + +1. Treat non-zero terminal exit codes as immediate failures to investigate; do not continue as if the command succeeded. +2. If a bash session exits, assume it is dead and start a new command/session; do not wait for additional output from that session. +3. After any command expected to gather data, verify output was actually returned before proceeding. +4. If command execution failed, report the failure clearly and retry with a corrected command instead of waiting. +5. Avoid `set -e` in this automation context; prefer single-purpose commands with explicit follow-up checks so failures are visible without killing the shell unexpectedly. +6. Prefer shorter command batches over long chained scripts when collecting evidence; this makes bash exits easier to detect and recover from. + ## Branch Naming All branches created by AI agents **must** live under the `dev/automation/` prefix. Use a descriptive suffix, for example: diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 715923d1b3..16aa709fe5 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -20,9 +20,8 @@ on operating systems that do not support .NET Framework. As such, it is not nece no action is required. On Linux and macOS systems, the `pwsh` command is required to be in the `$PATH` environment variable. For specific instructions see: [Install PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell) -The **NuGet** binary is required to package the Microsoft.Data.SqlClient project. For convenience, this can be done -via the PowerShell script [tools/scripts/downloadLatestNuget.ps1](tools/scripts/downloadLatestNuget.ps1), however, any -`nuget.exe` binary can be used. +The **NuGet** binary is optional for inspection and feed-management workflows, but build and packaging flows in this +repository are run through `dotnet build` against `build.proj`. ## Developer Workflow @@ -34,21 +33,16 @@ package the project. The `build.proj` file provides convenient targets to accomp > may be noticed, possibly severe. All official build and test infrastructure uses the `build.proj` entrypoint, and it > is recommended that `build.proj` is used for local development, as well. -> [!TIP] -> `build.proj` was written with the intention of being called from `msbuild`. As such, the examples below -> use `msbuild`. On systems where `msbuild` is not available, simply replace `msbuild` with `dotnet msbuild` to get the -> same behavior. - > [!TIP] > This section is not exhaustive of all targets or parameters to `build.proj`. Complete documentation is available in > [`build.proj`](build.proj). ### Building Projects -From the root of your repository, run `msbuild` against `build.proj` with a build target, following this pattern: +From the root of your repository, run `dotnet build` against `build.proj` with a build target, following this pattern: ```bash -msbuild build.proj -t: [optional_parameters] +dotnet build build.proj -t: [optional_parameters] ``` The following build targets can be used to build the following projects. All targets will implicitly build any other @@ -86,18 +80,21 @@ placed in `artifacts/Microsoft.Data.SqlClient.ref/Project-/` #### Examples Build all projects: + ```bash -msbuild build.proj -t:Build +dotnet build build.proj -t:Build ``` Build Microsoft.Data.SqlClient in Release configuration: + ```bash -msbuild build.proj -t:BuildSqlClient -p:Configuration=Release +dotnet build build.proj -t:BuildSqlClient -p:Configuration=Release ``` Build v1.2.3 of Microsoft.Data.SqlClient.Extensions.Abstractions: + ```bash -msbuild build.proj -t:BuildAbstractions -p:PackageVersionAbstractions=1.2.3 +dotnet build build.proj -t:BuildAbstractions -p:PackageVersionAbstractions=1.2.3 ``` ### Testing Projects @@ -105,10 +102,10 @@ msbuild build.proj -t:BuildAbstractions -p:PackageVersionAbstractions=1.2.3 This section provides a summary and brief example of how to execute tests for projects in this repository. **For more information about test procedures, including config file setup, see [TESTGUIDE.md](TESTGUIDE.md).** -From the root of your repository, run `msbuild` against `build.proj` with a test target, following this pattern: +From the root of your repository, run `dotnet build` against `build.proj` with a test target, following this pattern: ```bash -msbuild build.proj -t: [optional_parameters] +dotnet build build.proj -t: [optional_parameters] ``` | `` | Description | @@ -140,37 +137,37 @@ A selection of parameters for test targets in `build.proj` relevant to common de Run Microsoft.Data.SqlClient unit tests: ```bash -msbuild build.proj -t:TestSqlClientUnit +dotnet build build.proj -t:TestSqlClientUnit ``` Run Microsoft.Data.SqlClient manual test set 2: ```bash -msbuild build.proj -t:TestSqlClientManual -p:TestSet=2 +dotnet build build.proj -t:TestSqlClientManual -p:TestSet=2 ``` Run Microsoft.Data.SqlClient functional tests against x86 dotnet: ```bash -msbuild build.proj -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' +dotnet build build.proj -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' ``` Run all Microsoft.Data.SqlClient.Extensions.Azure unit tests, including interactive, but excluding failing tests: ```bash -msbuild build.proj -t:TestAzure -p:TestFilters=category!=failing +dotnet build build.proj -t:TestAzure -p:TestFilters=category!=failing ``` Run Microsoft.Data.SqlClient functional tests against net8.0 runtime: ```bash -msbuild build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 +dotnet build build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 ``` ### Packaging Projects Just like building and testing the various projects in this repository, packaging the projects into NuGet packages is -also handled by `build.proj`. From the root of your repository, run `msbuild` against `build.proj` with a pack target, +also handled by `build.proj`. From the root of your repository, run `dotnet build` against `build.proj` with a pack target, following this pattern: ```bash -msbuild build.proj -t: [optional_parameters] +dotnet build build.proj -t: [optional_parameters] ``` | `` | Description | @@ -191,30 +188,36 @@ A selection of parameters for pack targets in `build.proj` relevant to common de | `[optional_parameter]` | Default Value | Allowed Values | Description | |------------------------------------|---------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| | `-p:Configuration=` | `Debug` | `Debug`, `Release` | Build configuration. Only applies if project and dependencies are being built. | -| `-p:NugetPath=` | `[blank]` | eg. `C:\my\nuget.exe` | _Only applies to `PackSqlClient`._ Path to `nuget.exe` to use. If not provided, defaults to `nuget.exe` in the PATH. | | `-p:PackBuild=` | `true` | `true`, `false` | Whether or not to build the project before packing. If `false`, project must be built using the same parameters. | | `-p:PackageVersion=` | `[blank]` | eg. `1.2.3-dev123` | Version to assign to the package, where `` can be one of: `['Abstractions', 'Azure', 'AkvProvider', 'Logging', 'SqlClient', 'SqlServer']`. If `PackBuild` is `true`, the assembly and file versions will be derived from this version. See Versioning for more details. | +For `PackSqlClient`, these additional parameters are optional overrides for dependency versions injected into the SqlClient nuspec during pack: + +- `-p:PackageVersionAbstractions=` +- `-p:PackageVersionLogging=` + +If omitted, `PackSqlClient` computes `AbstractionsPackageVersion` and `LoggingPackageVersion` from sibling projects using the current `BuildNumber` and `BuildSuffix` context. + #### Examples Package Microsoft.Data.SqlClient.Internal.Logging into a NuGet package: ```bash -msbuild build.proj -t:PackLogging +dotnet build build.proj -t:PackLogging ``` -Package Microsoft.Data.SqlClient if `nuget.exe` is not in the `$PATH` environment variable: +Package Microsoft.Data.SqlClient: ```bash -msbuild build.proj -t:PackSqlClient -p:NugetPath="C:\my\nuget.exe" +dotnet build build.proj -t:PackSqlClient ``` Package version 1.2.3 of Microsoft.Data.SqlClient.Extensions.Abstractions: ```bash -msbuild build.proj -t:PackAbstractions -p:PackageVersionAbstractions=1.2.3 +dotnet build build.proj -t:PackAbstractions -p:PackageVersionAbstractions=1.2.3 ``` Package Microsoft.Data.SqlClient.Extensions.Azure without building it beforehand: ```bash -msbuild build.proj -t:PackAzure -p:PackBuild=false +dotnet build build.proj -t:PackAzure -p:PackBuild=false ``` ## Versioning @@ -276,20 +279,20 @@ and Microsoft.Data.SqlClient.Internal.Logging v2.2.2. ```bash # Build v2.2.2 of Logging and copy to packages -msbuild build.proj -t:PackLogging \ +dotnet build build.proj -t:PackLogging \ -p:ReferenceType=Package \ -p:PackageVersionLogging=2.2.2 cp artifacts/Microsoft.Data.SqlClient.Internal.Logging/Debug/*.*pkg packages/ # Build v1.0.1 of Abstractions that depends on v2.2.2 of Logging -msbuild build.proj -t:PackAbstractions \ +dotnet build build.proj -t:PackAbstractions \ -p:ReferenceType=Package \ -p:PackageVersionAbstractions=1.0.1 \ -p:PackageVersionLogging=2.2.2 \ cp artifacts/Microsoft.Data.SqlClient.Extensions.Abstractions/Package-Debug/*.*pkg packages/ # Build SqlClient -msbuild -t:PackSqlClient \ +dotnet build build.proj -t:PackSqlClient \ -p:ReferenceType=Package \ -p:PackageVersionSqlClient=7.1.1 \ -p:PackageVersionAbstractions=1.0.1 \ @@ -299,7 +302,7 @@ cp artifacts/Microsoft.Data.SqlClient/Package-Debug/*.*pkg packages/ Run Microsoft.Data.SqlClient functional tests against the versions built above: ```bash -msbuild build.proj -t:TestSqlClientFunctional \ +dotnet build build.proj -t:TestSqlClientFunctional \ -p:ReferenceType=Package \ -p:PackageVersionSqlClient=7.1.1 \ -p:PackageVersionAbstractions=1.0.1 \ diff --git a/TESTGUIDE.md b/TESTGUIDE.md index fdcb8749ec..349674deee 100644 --- a/TESTGUIDE.md +++ b/TESTGUIDE.md @@ -23,13 +23,7 @@ These projects target `net8.0`, `net9.0`, and `net10.0` on all platforms. On Win Use [build.proj](build.proj) from the repository root: ```bash -msbuild build.proj -t: [optional_parameters] -``` - -If `msbuild` is not available, use `dotnet msbuild`: - -```bash -dotnet msbuild build.proj -t: [optional_parameters] +dotnet build build.proj -t: [optional_parameters] ``` Test targets build the projects they depend on, so a separate build step is not required for normal test runs. @@ -51,55 +45,55 @@ Test targets build the projects they depend on, so a separate build step is not Run the SqlClient unit tests: ```bash -msbuild build.proj -t:TestSqlClientUnit +dotnet build build.proj -t:TestSqlClientUnit ``` Run the SqlClient functional tests: ```bash -msbuild build.proj -t:TestSqlClientFunctional +dotnet build build.proj -t:TestSqlClientFunctional ``` Run the SqlClient manual tests: ```bash -msbuild build.proj -t:TestSqlClientManual +dotnet build build.proj -t:TestSqlClientManual ``` Run only manual test set 2: ```bash -msbuild build.proj -t:TestSqlClientManual -p:TestSet=2 +dotnet build build.proj -t:TestSqlClientManual -p:TestSet=2 ``` Run manual test sets 1 and 3: ```bash -msbuild build.proj -t:TestSqlClientManual -p:TestSet=13 +dotnet build build.proj -t:TestSqlClientManual -p:TestSet=13 ``` Run Always Encrypted manual tests: ```bash -msbuild build.proj -t:TestSqlClientManual -p:TestSet=AE +dotnet build build.proj -t:TestSqlClientManual -p:TestSet=AE ``` Run a specific target framework: ```bash -msbuild build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 +dotnet build build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 ``` Run functional tests against an x86 `dotnet` installation: ```bash -msbuild build.proj -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' +dotnet build build.proj -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' ``` Run all Azure extension tests, including `interactive` tests, while still excluding tests marked `failing` or `flaky`: ```bash -msbuild build.proj -t:TestAzure -p:TestFilters=category!=failing +dotnet build build.proj -t:TestAzure -p:TestFilters=category!=failing ``` ## Test Parameters @@ -132,13 +126,13 @@ Examples: ```bash # Run a single test by fully-qualified name. -msbuild build.proj -t:TestSqlClientUnit -p:TestFilters=FullyQualifiedName=Namespace.ClassName.MethodName +dotnet build build.proj -t:TestSqlClientUnit -p:TestFilters=FullyQualifiedName=Namespace.ClassName.MethodName # Run only flaky tests while investigating quarantine failures. -msbuild build.proj -t:TestSqlClientManual -p:TestFilters=category=flaky +dotnet build build.proj -t:TestSqlClientManual -p:TestFilters=category=flaky # Disable the default filter. -msbuild build.proj -t:TestSqlClientFunctional -p:TestFilters=none +dotnet build build.proj -t:TestSqlClientFunctional -p:TestFilters=none ``` When passing filter expressions that contain shell-sensitive characters such as `&`, quote or escape the value as @@ -222,14 +216,14 @@ For SQL Server in a Linux container, WSL, or another host where SQL authenticati You can override the config file path with the `MDS_TEST_CONFIG` environment variable: ```bash -MDS_TEST_CONFIG=/path/to/config.json msbuild build.proj -t:TestSqlClientManual -p:TestSet=2 +MDS_TEST_CONFIG=/path/to/config.json dotnet build build.proj -t:TestSqlClientManual -p:TestSet=2 ``` On PowerShell: ```powershell $env:MDS_TEST_CONFIG = "C:\path\to\config.json" -msbuild build.proj -t:TestSqlClientManual -p:TestSet=2 +dotnet build build.proj -t:TestSqlClientManual -p:TestSet=2 ``` ## Configuration Properties @@ -289,23 +283,23 @@ If `TestSet` is omitted, all sets are compiled and run. You can combine sets by Test results are written to `test_results` by default. Override the location with `TestResultsFolderPath`: ```bash -msbuild build.proj -t:TestSqlClientUnit -p:TestResultsFolderPath=/tmp/sqlclient-test-results +dotnet build build.proj -t:TestSqlClientUnit -p:TestResultsFolderPath=/tmp/sqlclient-test-results ``` Hang blame collection is enabled by default with a `10m` timeout. To increase the timeout: ```bash -msbuild build.proj -t:TestSqlClientManual -p:TestBlameTimeout=30m +dotnet build build.proj -t:TestSqlClientManual -p:TestBlameTimeout=30m ``` To disable hang blame collection: ```bash -msbuild build.proj -t:TestSqlClientManual -p:TestBlameTimeout=0 +dotnet build build.proj -t:TestSqlClientManual -p:TestBlameTimeout=0 ``` Code coverage is enabled by default. To disable it for a faster local run: ```bash -msbuild build.proj -t:TestSqlClientUnit -p:TestCodeCoverage=false +dotnet build build.proj -t:TestSqlClientUnit -p:TestCodeCoverage=false ``` diff --git a/build.proj b/build.proj index a0ef562934..14db6015ba 100644 --- a/build.proj +++ b/build.proj @@ -69,16 +69,6 @@ --> - - nuget - - BuildSqlClient - $(RepoRoot)tools/specs/Microsoft.Data.SqlClient.nuspec $(RepoRoot)tools/GenAPI/Microsoft.DotNet.GenAPI/ $(GenApiPath)Microsoft.DotNet.GenAPI.csproj @@ -531,107 +513,47 @@ - - - - git rev-parse HEAD - - - $([System.Text.RegularExpressions.Regex]::Replace($(GitCommand), "\s+", " ")) - - - - - + + BuildSqlClient + - + + - - "$(DotnetPath)dotnet" msbuild "$(SqlClientProjectPath)" - -nologo - -verbosity:quiet - -getProperty:SqlClientPackageVersion + + "$(DotnetPath)dotnet" pack "$(SqlClientProjectPath)" + -p:Configuration=$(Configuration) + $(PackBuildArgument) + $(SigningKeyPathArgument) $(BuildNumberArgument) $(BuildSuffixArgument) $(PackageVersionSqlClientArgument) - - - $([System.Text.RegularExpressions.Regex]::Replace($(GetSqlClientPackageVersionCommand), "\s+", " ")) - - - - - - - - - - - "$(DotnetPath)dotnet" msbuild "$(AbstractionsProjectPath)" - -nologo - -verbosity:quiet - -getProperty:AbstractionsPackageVersion - $(BuildNumberArgument) - $(BuildSuffixArgument) - - $([System.Text.RegularExpressions.Regex]::Replace($(GetAbstractionsPackageVersionCommand), "\s+", " ")) - - - - - - - - - - - "$(DotnetPath)dotnet" msbuild "$(LoggingProjectPath)" - -nologo - -verbosity:quiet - -getProperty:LoggingPackageVersion - $(BuildNumberArgument) - $(BuildSuffixArgument) - - $([System.Text.RegularExpressions.Regex]::Replace($(GetLoggingPackageVersionCommand), "\s+", " ")) - - - - - - - - <_EvaluatedSqlClientPackageVersion>$([System.Text.RegularExpressions.Regex]::Replace($(_EvaluatedSqlClientPackageVersion), "\s", "")) - $([System.Text.RegularExpressions.Regex]::Replace($(PackageVersionAbstractions), "\s", "")) - $([System.Text.RegularExpressions.Regex]::Replace($(PackageVersionLogging), "\s", "")) - $([System.Text.RegularExpressions.Regex]::Replace($(CommitId), "\s", "")) - - "$(NugetPath)" pack "$(SqlClientNuspecPath)" - -Symbols - -SymbolPackageFormat snupkg - -Version "$(_EvaluatedSqlClientPackageVersion)" - -OutputDirectory "$(SqlClientArtifactRoot)/$(ReferenceType)-$(Configuration)" - -properties "COMMITID=$(CommitId);Configuration=$(Configuration);ReferenceType=$(ReferenceType);AbstractionsPackageVersion=$(PackageVersionAbstractions);LoggingPackageVersion=$(PackageVersionLogging)" - + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + + + -p:PackageOutputPath="$(SqlClientArtifactRoot)/$(ReferenceType)-$(Configuration)" + - $([System.Text.RegularExpressions.Regex]::Replace($(NuGetCommand), "\s+", " ")) + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - - + + diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index afb20ac80e..31bf8be63b 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -143,16 +143,29 @@ jobs: abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} loggingPackageVersion: ${{ parameters.loggingPackageVersion }} - - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self - parameters: - buildConfiguration: ${{ parameters.buildConfiguration }} - displayName: 'Create MDS NuGet Package' - generateSymbolsPackage: true - nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec' - outputDirectory: $(packagePath) - packageVersion: ${{ parameters.mdsPackageVersion }} - properties: 'AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }};LoggingPackageVersion=${{ parameters.loggingPackageVersion }}' - referenceType: ${{ parameters.referenceType }} + - task: DotNetCoreCLI@2 + displayName: 'Create MDS NuGet Package' + inputs: + command: build + projects: build.proj + arguments: >- + -t:PackSqlClient + -p:PackBuild=false + -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} + -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + + # PackSqlClient outputs to artifacts/Microsoft.Data.SqlClient/-/. + # Downstream steps (local feed copy, AKV pack, artifact publish) all expect packages at + # $(packagePath), so copy them there. + - task: CopyFiles@2 + displayName: Copy MDS Package to Output Directory + inputs: + sourceFolder: $(Build.SourcesDirectory)/artifacts/Microsoft.Data.SqlClient/${{ parameters.referenceType }}-${{ parameters.buildConfiguration }} + contents: 'Microsoft.Data.SqlClient.*' + targetFolder: $(packagePath) # When building in Package mode, the AKV Provider restore needs to find the MDS package # we just built. Copy it to the local NuGet feed so NuGet.config can resolve it. diff --git a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml deleted file mode 100644 index 46c5c61492..0000000000 --- a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml +++ /dev/null @@ -1,80 +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: nuspecPath - type: string - - - name: packageVersion - type: string - - - name: outputDirectory - type: string - default: '$(Build.SourcesDirectory)/output' - - # The C# build configuration (e.g. Debug or Release) to use when building the NuGet package. - - name: buildConfiguration - type: string - default: Debug - values: - - Debug - - Release - - - name: generateSymbolsPackage - type: boolean - - - name: displayName - type: string - - - name: installNuget - type: boolean - default: true - - # The C# project reference type to use when building and packing the packages. - - name: referenceType - type: string - values: - # Reference sibling packages as NuGet packages. - - Package - # Reference sibling packages as C# projects. - - Project - - # Semi-colon separated properties to pass to nuget pack via the -properties argument. - - name: properties - type: string - default: '' - -steps: -- ${{ if parameters.installNuget }}: - - task: NuGetToolInstaller@1 - displayName: 'Install Latest Nuget' - inputs: - checkLatest: true - -- powershell: | - $Commit=git rev-parse HEAD - Write-Host "##vso[task.setvariable variable=CommitHead;]$Commit" - displayName: CommitHead - -- task: NuGetCommand@2 - displayName: ${{parameters.displayName }} - inputs: - command: custom - ${{ if parameters.generateSymbolsPackage }}: - arguments: >- - pack - -Symbols - -SymbolPackageFormat snupkg - ${{parameters.nuspecPath}} - -Version ${{parameters.packageVersion}} - -OutputDirectory ${{parameters.outputDirectory}} - -properties "COMMITID=$(CommitHead);Configuration=${{parameters.buildConfiguration}};ReferenceType=${{parameters.referenceType}};${{parameters.properties}}" - ${{else }}: - arguments: >- - pack - ${{parameters.nuspecPath}} - -Version ${{parameters.packageVersion}} - -OutputDirectory ${{parameters.outputDirectory}} - -properties "COMMITID=$(CommitHead);Configuration=${{parameters.buildConfiguration}};ReferenceType=${{parameters.referenceType}};${{parameters.properties}}" diff --git a/release-notes/template/release-notes-template.md b/release-notes/template/release-notes-template.md index 4e3ebcb0c0..9df5a11e7f 100644 --- a/release-notes/template/release-notes-template.md +++ b/release-notes/template/release-notes-template.md @@ -87,7 +87,7 @@ We thank the following public contributors. Their efforts toward this project ar ### Dependencies -(Use the dependencies listed in the package's NuGet spec under tools/specs/ or the project file.) +(Use the dependencies listed in the package's NuGet spec beside the SqlClient project file, or in the project file.) #### [Target Framework Version] diff --git a/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props index 25e3ff90ff..31f85b7da4 100644 --- a/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props @@ -66,9 +66,7 @@ want a quick build without supplying any parameters. --> - dev - - $(AkvProviderVersionDefault)-$(BuildSuffix) + $(AkvProviderVersionDefault)-dev $(AkvProviderVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props index 32330ad3a6..14daeda570 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props @@ -67,9 +67,7 @@ want a quick build without supplying any parameters. --> - dev - - $(AbstractionsVersionDefault)-$(BuildSuffix) + $(AbstractionsVersionDefault)-dev $(AbstractionsVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props index 1876c965eb..3c4bb9dfac 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props @@ -66,9 +66,7 @@ want a quick build without supplying any parameters. --> - dev - - $(AzureVersionDefault)-$(BuildSuffix) + $(AzureVersionDefault)-dev $(AzureVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) diff --git a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props index 3278bdc2e9..eeb25b93ca 100644 --- a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props @@ -67,9 +67,7 @@ want a quick build without supplying any parameters. --> - dev - - $(LoggingVersionDefault)-$(BuildSuffix) + $(LoggingVersionDefault)-dev $(LoggingVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) diff --git a/src/Microsoft.Data.SqlClient/Versions.props b/src/Microsoft.Data.SqlClient/Versions.props index 5ec3b8b9ac..2a915285b0 100644 --- a/src/Microsoft.Data.SqlClient/Versions.props +++ b/src/Microsoft.Data.SqlClient/Versions.props @@ -66,9 +66,7 @@ want a quick build without supplying any parameters. --> - dev - - $(SqlClientVersionDefault)-$(BuildSuffix) + $(SqlClientVersionDefault)-dev $(SqlClientVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 559099062a..cd7fdb4b79 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -76,6 +76,126 @@ $(ArtifactPath)$(AssemblyName)/$(ReferenceType)-$(Configuration)/$(NormalizedTargetOs)/ + + + + + $(MSBuildProjectDirectory)/Microsoft.Data.SqlClient.nuspec + $(BaseIntermediateOutputPath)Microsoft.Data.SqlClient.pack.nuspec + + $(RepoRoot)/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj + $(RepoRoot)/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj + $(RepoRoot)/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props + $(RepoRoot)/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props + + + + + $(SqlClientPackNuspecTemplatePath) + + + $(RepoRoot)artifacts + + + Version=$(Version);Configuration=$(Configuration);ReferenceType=$(ReferenceType) + + + true + + + snupkg + + + true + + + + + + + + + + + + + $(SourceRevisionId) + $(NuspecProperties);COMMITID=$(CommitId) + <_AbstractionsPackageVersionTrimmed>$([System.String]::Copy('$(AbstractionsPackageVersion)').Trim()) + <_LoggingPackageVersionTrimmed>$([System.String]::Copy('$(LoggingPackageVersion)').Trim()) + + + + + + + + <_SqlClientPackNuspecExpandedText>$([System.IO.File]::ReadAllText('$(SqlClientPackNuspecTemplatePath)').Replace('$AbstractionsPackageVersion$','$(AbstractionsPackageVersion)').Replace('$LoggingPackageVersion$','$(LoggingPackageVersion)')) + + + + + + $(SqlClientPackNuspecGeneratedPath) + + + @@ -115,7 +235,7 @@ - @@ -123,7 +243,7 @@ - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.nuspec b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.nuspec new file mode 100644 index 0000000000..8f04553f94 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.nuspec @@ -0,0 +1,238 @@ + + + + Microsoft.Data.SqlClient + $Version$ + Microsoft.Data.SqlClient + Microsoft + true + MIT + 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. + https://go.microsoft.com/fwlink/?linkid=2090501 + © Microsoft Corporation. All rights reserved. + sqlclient microsoft.data.sqlclient + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.SqlServer.Server/Versions.props b/src/Microsoft.SqlServer.Server/Versions.props index ec959cafbf..0a342f9426 100644 --- a/src/Microsoft.SqlServer.Server/Versions.props +++ b/src/Microsoft.SqlServer.Server/Versions.props @@ -67,9 +67,7 @@ want a quick build without supplying any parameters. --> - dev - - $(SqlServerVersionDefault)-$(BuildSuffix) + $(SqlServerVersionDefault)-dev $(SqlServerVersionDefault.Split('-')[0]).$(FileVersionBuildNumber) diff --git a/tools/scripts/downloadLatestNuget.ps1 b/tools/scripts/downloadLatestNuget.ps1 deleted file mode 100644 index faddf3bb09..0000000000 --- a/tools/scripts/downloadLatestNuget.ps1 +++ /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. -# Script: downloadLatestNuget.ps1 -# Author: Keerat Singh -# Date: 07-Dec-2018 -# Comments: This script downloads the latest NuGet Binary. -# -param( - [string]$nugetDestPath, - [string]$nugetSrcPath="https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" -) -Function DownloadLatestNuget() -{ - if(!$nugetDestPath) - { - $nugetDestPath = (Get-location).ToString() +'\.nuget\' - } - if (!(Test-Path $nugetDestPath)) - { - New-Item -ItemType Directory -Path $nugetDestPath - } - Write-Output "Source: $nugetSrcPath" - Write-Output "Destination: $nugetDestPath" - Start-BitsTransfer -Source $nugetSrcPath -Destination $nugetDestPath\nuget.exe -} -DownloadLatestNuget \ No newline at end of file diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec deleted file mode 100644 index 75aa9e786d..0000000000 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ /dev/null @@ -1,234 +0,0 @@ - - - - Microsoft.Data.SqlClient - 1.0.0 - Microsoft.Data.SqlClient - Microsoft - true - MIT - 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. - https://go.microsoft.com/fwlink/?linkid=2090501 - © Microsoft Corporation. All rights reserved. - sqlclient microsoft.data.sqlclient - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 3a3181aa44d53c460a767fb27cf6f9e1b0e12f7c Mon Sep 17 00:00:00 2001 From: Shreya Laxminarayana Rao <147193825+ShreyaLaxminarayan@users.noreply.github.com> Date: Sat, 9 May 2026 03:50:02 +0530 Subject: [PATCH 092/298] Fix flaky SequentialAccess test with dynamic expected value (#4224) --- .../SQL/DataStreamTest/DataStreamTest.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) 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 01c539043c..a49e14de6e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -961,6 +961,7 @@ private static void SequentialAccess(string connectionString) long cb = 0; long di = 0; long cbTotal = 0; + long expectedTotal = 0; object o; int i; SqlBinary sqlbin; @@ -968,6 +969,15 @@ private static void SequentialAccess(string connectionString) using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); + // Compute the expected total bytes independently of the C# APIs under test in order to compare. + //The inner SELECT generates the XML, CAST converts it to a Unicode string, LEN() * 2 matches s.Length * 2 in C# since UTF-16 uses 2 bytes per character. + // SUM() totals across all rows, matching the loop below. + using (SqlCommand cmdExpected = new SqlCommand("SELECT SUM(LEN(CAST(xml_data AS NVARCHAR(MAX))) * 2) FROM " + + "(SELECT CAST((SELECT * FROM orders FOR XML AUTO) AS NVARCHAR(MAX)) AS xml_data) t", conn)) + { + expectedTotal = (long)cmdExpected.ExecuteScalar(); + } + using (SqlCommand cmd = new SqlCommand("select * from orders for xml auto", conn)) { // Simple reads @@ -982,7 +992,7 @@ private static void SequentialAccess(string connectionString) } } while (reader.NextResult()); } - DataTestUtility.AssertEqualsWithDescription((long)536198, cbTotal, "FAILED: cbTotal result did not have expected value"); + DataTestUtility.AssertEqualsWithDescription(expectedTotal, cbTotal, "FAILED: cbTotal result did not have expected value"); // Simple GetFieldValue cbTotal = 0; @@ -997,7 +1007,7 @@ private static void SequentialAccess(string connectionString) } } while (reader.NextResult()); } - DataTestUtility.AssertEqualsWithDescription((long)536198, cbTotal, "FAILED: cbTotal result did not have expected value"); + DataTestUtility.AssertEqualsWithDescription(expectedTotal, cbTotal, "FAILED: cbTotal result did not have expected value"); // Simple GetFieldValueAsync cbTotal = 0; @@ -1012,7 +1022,7 @@ private static void SequentialAccess(string connectionString) } } while (reader.NextResult()); } - DataTestUtility.AssertEqualsWithDescription((long)536198, cbTotal, "FAILED: cbTotal result did not have expected value"); + DataTestUtility.AssertEqualsWithDescription(expectedTotal, cbTotal, "FAILED: cbTotal result did not have expected value"); // test sequential access reading everything cbTotal = 0; @@ -1036,7 +1046,7 @@ private static void SequentialAccess(string connectionString) } } while (reader.NextResult()); } - DataTestUtility.AssertEqualsWithDescription((long)536198, cbTotal, "FAILED: cbTotal result did not have expected value"); + DataTestUtility.AssertEqualsWithDescription(expectedTotal, cbTotal, "FAILED: cbTotal result did not have expected value"); } // Test IsDBNull From dc19dcda53f669f944cb0695775ba6b7138c714d Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Wed, 13 May 2026 00:54:45 +1000 Subject: [PATCH 093/298] remove redundant Attribute( suffixes (#4158) Co-authored-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> --- .../DbConnectionString.netfx.cs | 8 ++-- .../Data/SqlClient/SqlCommandBuilder.cs | 14 +++---- .../Microsoft/Data/SqlClient/SqlConnection.cs | 22 +++++----- .../Data/SqlClient/SqlDataAdapter.cs | 24 +++++------ .../Microsoft/Data/SqlClient/SqlDataReader.cs | 4 +- .../Microsoft/Data/SqlClient/SqlDependency.cs | 40 +++++++++---------- .../Microsoft/Data/SqlClient/SqlException.cs | 6 +-- .../Data/SqlClient/SqlParameterCollection.cs | 6 +-- 8 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs index 841369423b..ff6659745a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs @@ -326,7 +326,7 @@ private void ValidateCombinedSet(DbConnectionString componentSet, DbConnectionSt HashSet component = new HashSet(componentSet._restrictionValues); combined.ExceptWith(component); Debug.Assert(combined.Count == 0, "Combined set allows values not allowed by component set"); -#endif +#endif } else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) { @@ -350,7 +350,7 @@ private void ValidateCombinedSet(DbConnectionString componentSet, DbConnectionSt HashSet component = new HashSet(componentSet._restrictionValues); combined.IntersectWith(component); Debug.Assert(combined.Count == 0, "Combined values allows values prevented by component set"); -#endif +#endif } else if (combinedSet._behavior == KeyRestrictionBehavior.PreventUsage) { @@ -362,7 +362,7 @@ private void ValidateCombinedSet(DbConnectionString componentSet, DbConnectionSt HashSet component = new HashSet(componentSet._restrictionValues); component.IntersectWith(combined); Debug.Assert(component.Count == 0, "Combined values does not prevent all of the values prevented by the component set"); -#endif +#endif } else { @@ -564,7 +564,7 @@ static internal string[] RemoveDuplicates(string[] restrictions) return restrictions; } - [ConditionalAttribute("DEBUG")] + [Conditional("DEBUG")] private static void Verify(string[] restrictionValues) { if (restrictionValues != null) 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 4ba8f27d79..e2296b1635 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs @@ -37,7 +37,7 @@ public SqlCommandBuilder(SqlDataAdapter adapter) : this() /// SqlServer only supports CatalogLocation.Start [ Browsable(false), - EditorBrowsableAttribute(EditorBrowsableState.Never), + EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ] public override CatalogLocation CatalogLocation @@ -59,7 +59,7 @@ public override CatalogLocation CatalogLocation /// SqlServer only supports '.' [ Browsable(false), - EditorBrowsableAttribute(EditorBrowsableState.Never), + EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ] public override string CatalogSeparator @@ -80,8 +80,8 @@ public override string CatalogSeparator /// [ DefaultValue(null), - ResCategoryAttribute(nameof(Strings.DataCategory_Update)), - ResDescriptionAttribute(nameof(Strings.SqlCommandBuilder_DataAdapter)), + ResCategory(nameof(Strings.DataCategory_Update)), + ResDescription(nameof(Strings.SqlCommandBuilder_DataAdapter)), ] public new SqlDataAdapter DataAdapter { @@ -99,7 +99,7 @@ public override string CatalogSeparator /// SqlServer only supports '.' [ Browsable(false), - EditorBrowsableAttribute(EditorBrowsableState.Never), + EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ] public override string QuotePrefix @@ -121,7 +121,7 @@ public override string QuotePrefix /// [ Browsable(false), - EditorBrowsableAttribute(EditorBrowsableState.Never), + EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ] public override string QuoteSuffix @@ -143,7 +143,7 @@ public override string QuoteSuffix /// [ Browsable(false), - EditorBrowsableAttribute(EditorBrowsableState.Never), + EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ] public override string SchemaSeparator diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index aa9d8edc06..8b06a4f90f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -163,19 +163,19 @@ public SqlRetryLogicBaseProvider RetryLogicProvider /// [DefaultValue(null)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Data))] + [ResCategory(nameof(Strings.DataCategory_Data))] [ResDescription(nameof(Strings.TCE_SqlConnection_ColumnEncryptionKeyCacheTtl))] public static TimeSpan ColumnEncryptionKeyCacheTtl { get; set; } = TimeSpan.FromHours(2); /// [DefaultValue(null)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Data))] + [ResCategory(nameof(Strings.DataCategory_Data))] [ResDescription(nameof(Strings.TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled))] public static bool ColumnEncryptionQueryMetadataCacheEnabled { get; set; } = true; /// [DefaultValue(null)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Data))] + [ResCategory(nameof(Strings.DataCategory_Data))] [ResDescription(nameof(Strings.TCE_SqlConnection_TrustedColumnMasterKeyPaths))] public static IDictionary> ColumnEncryptionTrustedMasterKeyPaths => _ColumnEncryptionTrustedMasterKeyPaths; @@ -265,7 +265,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); } /// @@ -508,7 +508,7 @@ private void CacheConnectionStringProperties() // connect the parser to the object. // if there is no parser at this time we need to connect it after creation. [DefaultValue(false)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Data))] + [ResCategory(nameof(Strings.DataCategory_Data))] [ResDescription(nameof(Strings.SqlConnection_StatisticsEnabled))] public bool StatisticsEnabled { @@ -642,9 +642,9 @@ internal int ConnectRetryInterval #pragma warning disable 618 // ignore obsolete warning about RecommendedAsConfigurable to use SettingsBindableAttribute [RecommendedAsConfigurable(true)] #pragma warning restore 618 - [SettingsBindableAttribute(true)] + [SettingsBindable(true)] [RefreshProperties(RefreshProperties.All)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Data))] + [ResCategory(nameof(Strings.DataCategory_Data))] [ResDescription(nameof(Strings.SqlConnection_ConnectionString))] public override string ConnectionString { @@ -1190,7 +1190,7 @@ protected override DbProviderFactory DbProviderFactory // /// - [ResCategoryAttribute(nameof(Strings.DataCategory_InfoMessage))] + [ResCategory(nameof(Strings.DataCategory_InfoMessage))] [ResDescription(nameof(Strings.DbConnection_InfoMessage))] #if NET public event SqlInfoMessageEventHandler InfoMessage; @@ -1320,7 +1320,7 @@ 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); - + try { statistics = SqlStatistics.StartTimer(Statistics); @@ -1396,7 +1396,7 @@ public override void Close() SqlStatistics statistics = null; Exception e = null; - + try { statistics = SqlStatistics.StartTimer(Statistics); @@ -1889,7 +1889,7 @@ internal void Abort(Exception e) } /// - public override Task OpenAsync(CancellationToken cancellationToken) + public override Task OpenAsync(CancellationToken cancellationToken) => OpenAsync(SqlConnectionOverrides.None, cancellationToken); /// diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs index 28c3c965ec..3b07ab0706 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs @@ -66,8 +66,8 @@ private SqlDataAdapter(SqlDataAdapter from) : base(from) /// [DefaultValue(null)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Update))] - [ResDescriptionAttribute(nameof(Strings.DbDataAdapter_DeleteCommand))] + [ResCategory(nameof(Strings.DataCategory_Update))] + [ResDescription(nameof(Strings.DbDataAdapter_DeleteCommand))] new public SqlCommand DeleteCommand { get { return _deleteCommand; } @@ -83,8 +83,8 @@ IDbCommand IDbDataAdapter.DeleteCommand /// [DefaultValue(null)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Update))] - [ResDescriptionAttribute(nameof(Strings.DbDataAdapter_InsertCommand))] + [ResCategory(nameof(Strings.DataCategory_Update))] + [ResDescription(nameof(Strings.DbDataAdapter_InsertCommand))] new public SqlCommand InsertCommand { get { return _insertCommand; } @@ -100,8 +100,8 @@ IDbCommand IDbDataAdapter.InsertCommand /// [DefaultValue(null)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Fill))] - [ResDescriptionAttribute(nameof(Strings.DbDataAdapter_SelectCommand))] + [ResCategory(nameof(Strings.DataCategory_Fill))] + [ResDescription(nameof(Strings.DbDataAdapter_SelectCommand))] new public SqlCommand SelectCommand { get { return _selectCommand; } @@ -117,8 +117,8 @@ IDbCommand IDbDataAdapter.SelectCommand /// [DefaultValue(null)] - [ResCategoryAttribute(nameof(Strings.DataCategory_Update))] - [ResDescriptionAttribute(nameof(Strings.DbDataAdapter_UpdateCommand))] + [ResCategory(nameof(Strings.DataCategory_Update))] + [ResDescription(nameof(Strings.DbDataAdapter_UpdateCommand))] new public SqlCommand UpdateCommand { get { return _updateCommand; } @@ -243,8 +243,8 @@ protected override RowUpdatingEventArgs CreateRowUpdatingEvent(DataRow dataRow, } /// - [ResCategoryAttribute(nameof(Strings.DataCategory_Update))] - [ResDescriptionAttribute(nameof(Strings.DbDataAdapter_RowUpdated))] + [ResCategory(nameof(Strings.DataCategory_Update))] + [ResDescription(nameof(Strings.DbDataAdapter_RowUpdated))] public event SqlRowUpdatedEventHandler RowUpdated { add @@ -258,8 +258,8 @@ public event SqlRowUpdatedEventHandler RowUpdated } /// - [ResCategoryAttribute(nameof(Strings.DataCategory_Update))] - [ResDescriptionAttribute(nameof(Strings.DbDataAdapter_RowUpdating))] + [ResCategory(nameof(Strings.DataCategory_Update))] + [ResDescription(nameof(Strings.DbDataAdapter_RowUpdating))] public event SqlRowUpdatingEventHandler RowUpdating { add diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 649c73147c..e316bb3c08 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -2075,7 +2075,7 @@ override public TextReader GetTextReader(int i) } /// - [EditorBrowsableAttribute(EditorBrowsableState.Never)] + [EditorBrowsable(EditorBrowsableState.Never)] override public char GetChar(int i) { throw ADP.NotSupported(); @@ -2388,7 +2388,7 @@ internal long GetStreamingXmlChars(int i, long dataIndex, char[] buffer, int buf #if NETFRAMEWORK /// - [EditorBrowsableAttribute(EditorBrowsableState.Never)] // MDAC 69508 + [EditorBrowsable(EditorBrowsableState.Never)] // MDAC 69508 IDataReader IDataRecord.GetData(int i) { throw ADP.NotSupported(); 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 9304f3051a..836fbb7dd4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -236,7 +236,7 @@ public SqlClientObjRef(SqlDependencyProcessDispatcher dispatcher) : base() private List _eventList = new(); private readonly object _eventHandlerLock = new(); // Lock for event serialization. // Track the time that this dependency should time out. If the server didn't send a change - // notification or a time-out before this point then the client will perform a client-side + // notification or a time-out before this point then the client will perform a client-side // timeout. private DateTime _expirationTime = DateTime.MaxValue; // Used for invalidation of dependencies based on which servers they rely upon. @@ -247,8 +247,8 @@ public SqlClientObjRef(SqlDependencyProcessDispatcher dispatcher) : base() private static readonly object s_startStopLock = new(); private static readonly string s_appDomainKey = Guid.NewGuid().ToString(); - // Hashtable containing all information to match from a server, user, database triple to the service started for that - // triple. For each server, there can be N users. For each user, there can be N databases. For each server, user, + // Hashtable containing all information to match from a server, user, database triple to the service started for that + // triple. For each server, there can be N users. For each user, there can be N databases. For each server, user, // database, there can only be one service. private static readonly Dictionary>> s_serverUserHash = new(StringComparer.OrdinalIgnoreCase); @@ -257,7 +257,7 @@ public SqlClientObjRef(SqlDependencyProcessDispatcher dispatcher) : base() // The following two strings are used for AppDomain.CreateInstance. private static readonly string s_assemblyName = (typeof(SqlDependencyProcessDispatcher)).Assembly.FullName; private static readonly string s_typeName = (typeof(SqlDependencyProcessDispatcher)).FullName; -#endif +#endif // EventSource members private static int s_objectTypeCount; // EventSourceCounter counter @@ -266,7 +266,7 @@ public SqlClientObjRef(SqlDependencyProcessDispatcher dispatcher) : base() /// // Constructors #if NETFRAMEWORK - [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)] + [HostProtection(ExternalThreading = true)] #endif public SqlDependency() : this(null, null, SQL.SqlDependencyTimeoutDefault) { @@ -274,7 +274,7 @@ public SqlDependency() : this(null, null, SQL.SqlDependencyTimeoutDefault) /// #if NETFRAMEWORK - [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)] + [HostProtection(ExternalThreading = true)] #endif public SqlDependency(SqlCommand command) : this(command, null, SQL.SqlDependencyTimeoutDefault) { @@ -313,8 +313,8 @@ public SqlDependency(SqlCommand command, string options, int timeout) /// #if NETFRAMEWORK [ - ResCategoryAttribute(nameof(Strings.DataCategory_Data)), - ResDescriptionAttribute(nameof(Strings.SqlDependency_HasChanges)) + ResCategory(nameof(Strings.DataCategory_Data)), + ResDescription(nameof(Strings.SqlDependency_HasChanges)) ] #endif public bool HasChanges => _dependencyFired; @@ -322,8 +322,8 @@ public SqlDependency(SqlCommand command, string options, int timeout) /// #if NETFRAMEWORK [ - ResCategoryAttribute(nameof(Strings.DataCategory_Data)), - ResDescriptionAttribute(nameof(Strings.SqlDependency_Id)) + ResCategory(nameof(Strings.DataCategory_Data)), + ResDescription(nameof(Strings.SqlDependency_Id)) ] #endif public string Id => _id; @@ -344,8 +344,8 @@ public SqlDependency(SqlCommand command, string options, int timeout) /// #if NETFRAMEWORK [ - ResCategoryAttribute(nameof(Strings.DataCategory_Data)), - ResDescriptionAttribute(nameof(Strings.SqlDependency_OnChange)) + ResCategory(nameof(Strings.DataCategory_Data)), + ResDescription(nameof(Strings.SqlDependency_OnChange)) ] #endif public event OnChangeEventHandler OnChange @@ -422,8 +422,8 @@ public event OnChangeEventHandler OnChange /// #if NETFRAMEWORK [ - ResCategoryAttribute(nameof(Strings.DataCategory_Data)), - ResDescriptionAttribute(nameof(Strings.SqlDependency_AddCommandDependency)) + ResCategory(nameof(Strings.DataCategory_Data)), + ResDescription(nameof(Strings.SqlDependency_AddCommandDependency)) ] #endif public void AddCommandDependency(SqlCommand command) @@ -466,9 +466,9 @@ 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 = AppDomain.CurrentDomain; - + ObjectHandle handle = CreateProcessDispatcher(masterDomain); if (handle != null) { @@ -484,9 +484,9 @@ private static void ObtainProcessDispatcher() 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()); + SqlDependencyProcessDispatcherStorage.NativeSetData(stream.ToArray()); } } else @@ -914,7 +914,7 @@ private static void RemoveFromServerUserHash(string server, IdentityUserNamePair internal static string GetDefaultComposedOptions(string server, string failoverServer, IdentityUserNamePair identityUser, string database) { - // Server must be an exact match, but user and database only needs to match exactly if there is more than one + // Server must be an exact match, but user and database only needs to match exactly if there is more than one // for the given user or database passed. That is ambiguous and we must fail. long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" server: '{0}', failoverServer: '{1}', database: '{2}'", server, failoverServer, database); try @@ -1014,7 +1014,7 @@ internal static string GetDefaultComposedOptions(string server, string failoverS // Internal Methods - // Called by SqlCommand upon execution of a SqlNotificationRequest class created by this dependency. We + // Called by SqlCommand upon execution of a SqlNotificationRequest class created by this dependency. We // use this list for a reverse lookup based on server. internal void AddToServerList(string server) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs index a1735fabe7..d06a02413f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs @@ -25,15 +25,15 @@ public sealed partial class SqlException : System.Data.Common.DbException private readonly SqlErrorCollection _errors; #if NETFRAMEWORK - [System.Runtime.Serialization.OptionalFieldAttribute(VersionAdded = 4)] + [OptionalField(VersionAdded = 4)] #endif private Guid _clientConnectionId = Guid.Empty; #if NETFRAMEWORK - [System.Runtime.Serialization.IgnoreDataMember] + [IgnoreDataMember] #endif private SqlBatchCommand _batchCommand; #if NETFRAMEWORK - [System.Runtime.Serialization.IgnoreDataMember] + [IgnoreDataMember] #endif // Do not serialize this field! It is used to indicate that no reconnection attempts are required internal bool _doNotReconnect = false; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameterCollection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameterCollection.cs index 80f9540a9a..e79edb9092 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameterCollection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameterCollection.cs @@ -15,7 +15,7 @@ // New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future. namespace Microsoft.Data.SqlClient { - /// + /// [ ListBindable(false) ] @@ -107,8 +107,8 @@ public SqlParameter Add(SqlParameter value) /// public override object SyncRoot => ((System.Collections.ICollection)InnerList).SyncRoot; - /// - [EditorBrowsableAttribute(EditorBrowsableState.Never)] + /// + [EditorBrowsable(EditorBrowsableState.Never)] public override int Add(object value) { IsDirty = true; From ef6aacfa970eaf5fbb11bb4d809eeb0ca99cba90 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 12 May 2026 16:24:25 -0300 Subject: [PATCH 094/298] Fix TryOpenInner InvalidCast race and add unit regression tests (#3314) (#4179) --- .../Microsoft/Data/SqlClient/SqlConnection.cs | 13 +- .../SqlConnectionConcurrentOpenTests.cs | 113 ++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 8b06a4f90f..9021d3e259 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -2242,7 +2242,18 @@ private bool TryOpenInner(TaskCompletionSource retry) } // does not require GC.KeepAlive(this) because of ReRegisterForFinalize below. - var tdsInnerConnection = (SqlConnectionInternal)InnerConnection; + // Capture InnerConnection once into a local to avoid a TOCTOU race: another thread + // concurrently calling Open() on the same SqlConnection instance can change + // _innerConnection to DbConnectionClosedConnecting between the TryOpenConnection() + // call above and the cast below. Without this local capture the second read of + // InnerConnection may return DbConnectionClosedConnecting, which is not assignable + // to SqlConnectionInternal and would produce an opaque InvalidCastException. + // See GitHub issue #3314. + var innerConnection = InnerConnection; + if (innerConnection is not SqlConnectionInternal tdsInnerConnection) + { + throw ADP.ConnectionAlreadyOpen(innerConnection.State); + } Debug.Assert(tdsInnerConnection.Parser != null, "Where's the parser?"); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs new file mode 100644 index 0000000000..c38a30d317 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs @@ -0,0 +1,113 @@ +// 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.Reflection; +using System.Threading.Tasks; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.Connection; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.Microsoft.Data.SqlClient +{ + /// + /// Regression tests for GitHub issue #3314. + /// + /// Root cause: TryOpenInner() read InnerConnection twice - once for TryOpenConnection() and + /// again for the cast to SqlConnectionInternal. Between those two reads another thread could + /// change _innerConnection to DbConnectionClosedConnecting, which is not assignable to + /// SqlConnectionInternal, causing an opaque InvalidCastException. + /// + /// Fix: InnerConnection is now captured into a local variable once; if it is not a + /// SqlConnectionInternal an InvalidOperationException with a descriptive message is thrown + /// instead of an InvalidCastException. + /// + public class SqlConnectionConcurrentOpenTests + { + private static readonly MethodInfo s_tryOpenInner = typeof(SqlConnection) + .GetMethod("TryOpenInner", BindingFlags.Instance | BindingFlags.NonPublic)!; + + private static DbConnectionInternal GetConnectingSingleton() + { + return DbConnectionClosedConnecting.SingletonInstance; + } + + private static void ForceInnerConnection(SqlConnection connection, DbConnectionInternal innerConnectionValue) + { + connection.SetInnerConnectionTo(innerConnectionValue); + } + + private static bool InvokeTryOpenInner(SqlConnection connection, TaskCompletionSource retry) + { + try + { + return (bool)s_tryOpenInner.Invoke(connection, [retry])!; + } + catch (TargetInvocationException tie) when (tie.InnerException != null) + { + throw tie.InnerException; + } + } + + [Fact] + public void InnerConnection_DbConnectionClosedConnecting_IsNotAssignableToSqlConnectionInternal() + { + DbConnectionInternal connectingSingleton = GetConnectingSingleton(); + + Assert.False( + connectingSingleton is SqlConnectionInternal, + "DbConnectionClosedConnecting must NOT be assignable to SqlConnectionInternal. " + + "If it were, the race condition in #3314 would not manifest."); + } + + [Fact] + public void InnerConnection_InConnectingState_ReportsConnectingState() + { + DbConnectionInternal connectingSingleton = GetConnectingSingleton(); + + var connection = new SqlConnection("Data Source=localhost"); + ForceInnerConnection(connection, connectingSingleton); + + Assert.Equal(ConnectionState.Connecting, connection.State); + } + + [Fact] + public void Open_WhenAlreadyConnecting_ThrowsInvalidOperation() + { + DbConnectionInternal connectingSingleton = GetConnectingSingleton(); + + var connection = new SqlConnection("Data Source=localhost"); + ForceInnerConnection(connection, connectingSingleton); + + Assert.Throws(() => connection.Open()); + } + + [Fact] + public void TryOpenInner_WhenInnerConnectionRacesToNonSqlConnectionInternalState_ThrowsInvalidOperation_NotInvalidCast() + { + DbConnectionInternal initialConnectingState = GetConnectingSingleton(); + DbConnectionInternal racedNonSqlConnectionInternalState = DbConnectionOpenBusy.SingletonInstance; + + var connection = new SqlConnection("Data Source=localhost"); + ForceInnerConnection(connection, initialConnectingState); + + TaskCompletionSource completedRetry = new(); + completedRetry.SetResult(racedNonSqlConnectionInternalState); + + Exception ex = Assert.ThrowsAny(() => + { + InvokeTryOpenInner(connection, completedRetry); + }); + + Assert.True( + ex is InvalidOperationException, + $"Expected InvalidOperationException but got {ex.GetType().Name}: {ex.Message}. " + + "The fix for #3314 must throw InvalidOperationException (not InvalidCastException) " + + "when _innerConnection races to a non-SqlConnectionInternal state inside TryOpenInner."); + + Assert.Contains("connection", ex.Message, StringComparison.OrdinalIgnoreCase); + } + } +} \ No newline at end of file From b700269fb1be1b8882738d8172107b954a0b3647 Mon Sep 17 00:00:00 2001 From: priyankatiwari08 Date: Wed, 13 May 2026 02:42:23 +0530 Subject: [PATCH 095/298] Add agentic workflow for automated issue triage (#4177) * Add agentic workflow for automated issue triage Introduces a GitHub Agentic Workflow (gh-aw) that automatically triages new issues using Copilot. The workflow: - Classifies issue type (Bug/Feature/Question/Task) - Validates environment details for bugs - Assigns area labels based on issue content - Searches for duplicate issues in the repo - Checks for regressions - Posts a single consolidated triage summary comment - Assigns Copilot coding agent for confirmed bugs with complete info Files added: - .github/workflows/issue-triage.md (workflow definition) - .github/workflows/issue-triage.lock.yml (compiled Actions YAML) - .github/aw/actions-lock.json (pinned action versions) * Align triage label names with issue template conventions * Constrain to single area label to stay within max:5 safe-outputs limit * Address PR review: roles:all, min-integrity:none, explicit permissions, fix Bug classification wording, fix Needs More Info label format * Recompile triage workflow with gh-aw v0.68.1 (MCP gateway v0.2.17, AWF v0.25.18) * Update issue-triage: add Required Context, Phase 2 assign-to-agent, AI disclaimer * Fix label name: align 'Triage Needed :new:' with repo convention --- .gitattributes | 2 + .github/aw/actions-lock.json | 14 + .github/workflows/issue-triage.lock.yml | 1162 +++++++++++++++++++++++ .github/workflows/issue-triage.md | 147 +++ 4 files changed, 1325 insertions(+) create mode 100644 .github/aw/actions-lock.json create mode 100644 .github/workflows/issue-triage.lock.yml create mode 100644 .github/workflows/issue-triage.md diff --git a/.gitattributes b/.gitattributes index 1ff0c42304..b678750c67 100644 --- a/.gitattributes +++ b/.gitattributes @@ -61,3 +61,5 @@ #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain + +.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json new file mode 100644 index 0000000000..4f7d36b947 --- /dev/null +++ b/.github/aw/actions-lock.json @@ -0,0 +1,14 @@ +{ + "entries": { + "actions/github-script@v8": { + "repo": "actions/github-script", + "version": "v8", + "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" + }, + "github/gh-aw-actions/setup@v0.68.1": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.68.1", + "sha": "2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc" + } + } +} diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml new file mode 100644 index 0000000000..8f4f2d04d4 --- /dev/null +++ b/.github/workflows/issue-triage.lock.yml @@ -0,0 +1,1162 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ddda208c8014c64e57436e95b5e1c92431c6815b5d0f782bb6a01ee1e84a63d9","compiler_version":"v0.68.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc","version":"v0.68.1"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.68.1). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + +name: "SqlClient Issue Auto-Triage" +"on": + issues: + types: + - opened + # roles: all # Roles processed as role check in pre-activation job + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}" + +run-name: "SqlClient Issue Auto-Triage" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: "" + comment_repo: "" + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} + GH_AW_INFO_VERSION: "1.0.21" + GH_AW_INFO_AGENT_VERSION: "1.0.21" + GH_AW_INFO_CLI_VERSION: "v0.68.1" + GH_AW_INFO_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.18" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_WORKFLOW_FILE: "issue-triage.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_COMPILED_VERSION: "v0.68.1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_bef3a8b6a480c883_EOF' + + GH_AW_PROMPT_bef3a8b6a480c883_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_bef3a8b6a480c883_EOF' + + Tools: add_comment, add_labels(max:5), missing_tool, missing_data, noop + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_bef3a8b6a480c883_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_bef3a8b6a480c883_EOF' + + {{#runtime-import .github/workflows/issue-triage.md}} + GH_AW_PROMPT_bef3a8b6a480c883_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: activation + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: issuetriage + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Set runtime paths + id: set-runtime-paths + run: | + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_OUTPUT" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_OUTPUT" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_6f7dd6f136148d14_EOF' + {"add_comment":{"hide_older_comments":true,"max":1},"add_labels":{"max":5},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_6f7dd6f136148d14_EOF + - name: Write Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added.", + "add_labels": " CONSTRAINTS: Maximum 5 label(s) can be added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_47425e2eb31c9c9e_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + }, + "guard-policies": { + "allow-only": { + "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, + "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, + "min-integrity": "none", + "repos": "all", + "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_47425e2eb31c9c9e_EOF + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Clean git credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.68.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect inference access error + id: detect-inference-error + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + if-no-files-found: ignore + - name: Upload firewall audit logs + if: always() + continue-on-error: true + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: firewall-audit-logs + path: | + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-issue-triage" + cancel-in-progress: false + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "issue-triage" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_TIMEOUT_MINUTES: "20" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.68.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-triage" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_WORKFLOW_ID: "issue-triage" + GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} + comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"max\":5},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: safe-outputs-items + path: /tmp/gh-aw/safe-output-items.jsonl + if-no-files-found: ignore + diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md new file mode 100644 index 0000000000..8523f53dd1 --- /dev/null +++ b/.github/workflows/issue-triage.md @@ -0,0 +1,147 @@ +--- +on: + issues: + types: [opened] + roles: all + +engine: copilot + +permissions: + contents: read + issues: read + pull-requests: read + +tools: + github: + min-integrity: none + +safe-outputs: + add-comment: + max: 1 + hide-older-comments: true + add-labels: + max: 5 + # Phase 2: Uncomment when ready to enable automatic Copilot coding agent assignment + # assign-to-agent: + # github-token: ${{ secrets.GH_AW_AGENT_TOKEN }} +--- + +# SqlClient Issue Auto-Triage + +You are a triage specialist for **Microsoft.Data.SqlClient**. +A new issue has just been opened. Your job is to: + +1. Read the issue silently using GitHub read tools +2. Apply labels silently using `add_labels` +3. Post **one** triage summary comment using `add_comment` + +That is the entire workflow. Do NOT call `add_comment` more than once. +Do NOT post intermediate findings. Do NOT post separate comments for +area detection, duplicate checking, or environment validation. +Everything goes into the single triage summary at the end. + +--- + +## Required Context + +Before analyzing the issue, you MUST read all project knowledge base files +from the checked-out repository. Recursively list the `.github/` directory +and read every markdown file (`.md`) found under it, excluding the `workflows/` +subdirectory. This includes but is not limited to instructions, prompts, +issue templates, skills, plans, and any other documentation files present. + +Use these files to inform your area classification, duplicate detection, +environment validation, and analysis. Do not skip this step. + +--- + +## Instructions + +Read the issue body. Then do ALL of the following analysis silently +(using read tools and search only — no comments, no outputs): + +**A. Classify issue type**: Bug (reports unexpected behavior, crash, regression, or incorrect results), Feature (has proposal), Question, or Task. + +**B. Validate environment** (bugs only): Check for these required fields: +SqlClient version, .NET target framework, SQL Server version, OS, +repro steps, expected vs actual behavior. +If any are missing, list them explicitly in the triage summary (e.g. "Missing: SQL Server version, OS"). +Proceed with all remaining triage steps regardless of missing environment details. + +**C. Classify area**: Based on the issue content, pick the single best matching area label from this list: + +| Label | Scope | +|-------|-------| +| `Area\Connection Pooling` | Pool behavior, timeouts, pool size, pool exhaustion | +| `Area\AKV Provider` | Always Encrypted Azure Key Vault provider | +| `Area\Json` | JSON data type support | +| `Area\Managed SNI` | Managed SNI / network layer | +| `Area\Native SNI` | Native SNI / network layer | +| `Area\Sql Bulk Copy` | SqlBulkCopy operations | +| `Area\Netcore` | .NET runtime / netcore specific | +| `Area\Netfx` | .NET Framework specific | +| `Area\Tests` | Test code / test projects | +| `Area\Documentation` | Docs and samples | +| `Area\Azure Connectivity` | Azure connectivity | +| `Area\Engineering` | Build, CI/CD, infrastructure | +| `Area\Vector` | Vector feature | +| `Area\Async` | Async operations | + +**D. Search for duplicates**: Search `repo:dotnet/SqlClient ` for similar issues. + +**E. Check for regression**: If the reporter mentions a previously working version, note the version boundary. + +--- + +## Actions (execute in this order) + +**First**: Call `add_labels` with at most 5 labels total (safe-outputs limit): +- `Triage Needed :new:` (always) +- Exactly **one** `Area\*` label from the table above (pick the single best match) +- `Needs More Info :information_source:` if critical environment details are missing (bugs only) +- `Repro Available :heavy_check_mark:` if repro steps are provided +- `Regression :boom:` if this appears to be a regression + +If the issue spans multiple areas, pick the primary area as the label and mention +other relevant areas in the triage summary comment instead. + +**Then**: Call `add_comment` exactly **once** with this markdown: + +``` +## 🔍 Triage Summary + +| Check | Result | +|-------|--------| +| Issue type | | +| Environment | | +| Area | | +| Duplicates | | +| Regression | | + +### Analysis + +<2-4 sentences: what the issue is about, which component is likely affected, +and severity assessment (P0-P3)> + +### Next Steps + + handling logic present in the synchronous path.") +- If duplicates were found: recommend reviewing the linked issues before proceeding. +- If regression: note the version boundary and state that bisection is recommended.> + +> **Note**: This triage summary is auto-generated by an AI agent. The analysis and suggestions above have not been verified by a human maintainer. Please treat as preliminary guidance only. +``` + + + +If the issue is spam or no action is needed, call the `noop` tool instead. \ No newline at end of file From 97b2c47283ee01f9d129f04acf69bcbe928cce4b Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Wed, 13 May 2026 00:21:37 -0700 Subject: [PATCH 096/298] Upgrade and regenerate outdated agentic workflow artifacts (#4279) * Upgrade and regenerate outdated aw artifacts * + update other gh workflows --- .github/agents/agentic-workflows.agent.md | 196 ++++++++++++ .github/aw/actions-lock.json | 29 +- .github/workflows/cherry-pick-hotfix.yml | 4 +- .github/workflows/codeql.yml | 4 +- .github/workflows/copilot-setup-steps.yml | 26 ++ .github/workflows/issue-triage.lock.yml | 360 ++++++++++++++++------ .github/workflows/issue-triage.md | 8 - 7 files changed, 515 insertions(+), 112 deletions(-) create mode 100644 .github/agents/agentic-workflows.agent.md create mode 100644 .github/workflows/copilot-setup-steps.yml diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md new file mode 100644 index 0000000000..cd376c5b1b --- /dev/null +++ b/.github/agents/agentic-workflows.agent.md @@ -0,0 +1,196 @@ +--- +description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing +disable-model-invocation: true +--- + +# GitHub Agentic Workflows Agent + +This agent helps you work with **GitHub Agentic Workflows (gh-aw)**, a CLI extension for creating AI-powered workflows in natural language using markdown files. + +## What This Agent Does + +This is a **dispatcher agent** that routes your request to the appropriate specialized prompt based on your task: + +- **Creating new workflows**: Routes to `create` prompt +- **Updating existing workflows**: Routes to `update` prompt +- **Debugging workflows**: Routes to `debug` prompt +- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt +- **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments +- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt +- **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes +- **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs +- **CLI commands and triggering workflows**: Routes to `cli-commands` guide — consult this whenever the user asks how to run, compile, debug, or manage workflows from the command line, or when they need the MCP tool equivalent of a `gh aw` command + +Workflows may optionally include: + +- **Project tracking / monitoring** (GitHub Projects updates, status reporting) +- **Orchestration / coordination** (one workflow assigning agents or dispatching and coordinating other workflows) + +## Files This Applies To + +- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` +- Workflow lock files: `.github/workflows/*.lock.yml` +- Shared components: `.github/workflows/shared/*.md` +- Configuration: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/github-agentic-workflows.md + +## Problems This Solves + +- **Workflow Creation**: Design secure, validated agentic workflows with proper triggers, tools, and permissions +- **Workflow Debugging**: Analyze logs, identify missing tools, investigate failures, and fix configuration issues +- **Version Upgrades**: Migrate workflows to new gh-aw versions, apply codemods, fix breaking changes +- **Component Design**: Create reusable shared workflow components that wrap MCP servers + +## How to Use + +When you interact with this agent, it will: + +1. **Understand your intent** - Determine what kind of task you're trying to accomplish +2. **Route to the right prompt** - Load the specialized prompt file for your task +3. **Execute the task** - Follow the detailed instructions in the loaded prompt + +## Available Prompts + +### Create New Workflow +**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/create-agentic-workflow.md + +**Use cases**: +- "Create a workflow that triages issues" +- "I need a workflow to label pull requests" +- "Design a weekly research automation" + +### Update Existing Workflow +**Load when**: User wants to modify, improve, or refactor an existing workflow + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/update-agentic-workflow.md + +**Use cases**: +- "Add web-fetch tool to the issue-classifier workflow" +- "Update the PR reviewer to use discussions instead of issues" +- "Improve the prompt for the weekly-research workflow" + +### Debug Workflow +**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/debug-agentic-workflow.md + +**Use cases**: +- "Why is this workflow failing?" +- "Analyze the logs for workflow X" +- "Investigate missing tool calls in run #12345" + +### Upgrade Agentic Workflows +**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/upgrade-agentic-workflows.md + +**Use cases**: +- "Upgrade all workflows to the latest version" +- "Fix deprecated fields in workflows" +- "Apply breaking changes from the new release" + +### Create a Report-Generating Workflow +**Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/report.md + +**Use cases**: +- "Create a weekly CI health report" +- "Post a daily security audit to Discussions" +- "Add a status update comment to open PRs" + +### Create Shared Agentic Workflow +**Load when**: User wants to create a reusable workflow component or wrap an MCP server + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/create-shared-agentic-workflow.md + +**Use cases**: +- "Create a shared component for Notion integration" +- "Wrap the Slack MCP server as a reusable component" +- "Design a shared workflow for database queries" + +### Fix Dependabot PRs +**Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/dependabot.md + +**Use cases**: +- "Fix the open Dependabot PRs for npm dependencies" +- "Bundle and close the Dependabot PRs for workflow dependencies" +- "Update @playwright/test to fix the Dependabot PR" + +### Analyze Test Coverage +**Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/test-coverage.md + +**Use cases**: +- "Create a workflow that comments coverage on PRs" +- "Analyze coverage trends over time" +- "Add a coverage gate that blocks PRs below a threshold" + +### CLI Commands Reference +**Load when**: The user asks how to run, compile, debug, or manage workflows from the command line; needs the MCP tool equivalent of a `gh aw` command; or is in a restricted environment (e.g., Copilot Cloud) without direct CLI access. + +**Reference file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/cli-commands.md + +**Use cases**: +- "How do I trigger workflow X on the main branch?" +- "What's the MCP equivalent of `gh aw logs`?" +- "I'm in Copilot Cloud — how do I compile a workflow?" +- "Show me all available gh aw commands" + +## Instructions + +When a user interacts with you: + +1. **Identify the task type** from the user's request +2. **Load the appropriate prompt** from the GitHub repository URLs listed above +3. **Follow the loaded prompt's instructions** exactly +4. **If uncertain**, ask clarifying questions to determine the right prompt + +## Quick Reference + +```bash +# Initialize repository for agentic workflows +gh aw init + +# Generate the lock file for a workflow +gh aw compile [workflow-name] + +# Trigger a workflow on demand (preferred over gh workflow run) +gh aw run # interactive input collection +gh aw run --ref main # run on a specific branch + +# Debug workflow runs +gh aw logs [workflow-name] +gh aw audit + +# Upgrade workflows +gh aw fix --write +gh aw compile --validate +``` + +## Key Features of gh-aw + +- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter +- **AI Engine Support**: Copilot, Claude, Codex, or custom engines +- **MCP Server Integration**: Connect to Model Context Protocol servers for tools +- **Safe Outputs**: Structured communication between AI and GitHub API +- **Strict Mode**: Security-first validation and sandboxing +- **Shared Components**: Reusable workflow building blocks +- **Repo Memory**: Persistent git-backed storage for agents +- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default + +## Important Notes + +- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/github-agentic-workflows.md for complete documentation +- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud +- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions +- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF +- Follow security best practices: minimal permissions, explicit network access, no template injection +- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. +- **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. +- **Triggering runs**: Always use `gh aw run ` to trigger a workflow on demand — not `gh workflow run .lock.yml`. `gh aw run` handles workflow resolution by short name, input parsing and validation, and correct run-tracking for agentic workflows. Use `--ref ` to run on a specific branch. +- **CLI commands reference**: For a complete guide on all `gh aw` commands and their MCP tool equivalents (for restricted environments), see https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/cli-commands.md diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 4f7d36b947..79ade5bd6f 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -1,14 +1,31 @@ { "entries": { - "actions/github-script@v8": { + "actions/github-script@v9.0.0": { "repo": "actions/github-script", - "version": "v8", - "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" + "version": "v9.0.0", + "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" }, - "github/gh-aw-actions/setup@v0.68.1": { + "github/gh-aw-actions/setup@v0.72.1": { "repo": "github/gh-aw-actions/setup", - "version": "v0.68.1", - "sha": "2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc" + "version": "v0.72.1", + "sha": "bc56a0cad2f450c562810785ef38649c04db812a" + } + }, + "containers": { + "ghcr.io/github/gh-aw-firewall/agent:0.25.41": { + "image": "ghcr.io/github/gh-aw-firewall/agent:0.25.41", + "digest": "sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770", + "pinned_image": "ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770" + }, + "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41": { + "image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41", + "digest": "sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0", + "pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0" + }, + "ghcr.io/github/gh-aw-firewall/squid:0.25.41": { + "image": "ghcr.io/github/gh-aw-firewall/squid:0.25.41", + "digest": "sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4", + "pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4" } } } diff --git a/.github/workflows/cherry-pick-hotfix.yml b/.github/workflows/cherry-pick-hotfix.yml index 794d8e41d3..678a6b20b3 100644 --- a/.github/workflows/cherry-pick-hotfix.yml +++ b/.github/workflows/cherry-pick-hotfix.yml @@ -57,7 +57,7 @@ jobs: versions: ${{ steps.extract.outputs.versions }} steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: # Only the scripts directory is needed; skip full history. sparse-checkout: .github/scripts @@ -91,7 +91,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: # Full history is required so the merge commit and target branch are available # for the cherry-pick operation. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c1e985ea03..75e4411e4b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -66,10 +66,10 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v5.0.1 + uses: actions/setup-dotnet@v5.2.0 with: global-json-file: global.json diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000000..0ae42f6f5c --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,26 @@ +name: "Copilot Setup Steps" + +# This workflow configures the environment for GitHub Copilot Agent with gh-aw MCP server +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called 'copilot-setup-steps' to be recognized by GitHub Copilot Agent + copilot-setup-steps: + runs-on: ubuntu-latest + + # Set minimal permissions for setup steps + # Copilot Agent receives its own token with appropriate permissions + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Install gh-aw extension + uses: github/gh-aw-actions/setup-cli@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + with: + version: v0.72.1 diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 8f4f2d04d4..08a2e176b1 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ddda208c8014c64e57436e95b5e1c92431c6815b5d0f782bb6a01ee1e84a63d9","compiler_version":"v0.68.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc","version":"v0.68.1"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ddda208c8014c64e57436e95b5e1c92431c6815b5d0f782bb6a01ee1e84a63d9","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.68.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.72.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -32,9 +32,18 @@ # Custom actions used: # - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 -# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 -# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 -# - github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 +# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c +# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 +# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f name: "SqlClient Issue Auto-Triage" "on": @@ -60,6 +69,7 @@ jobs: body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} @@ -70,30 +80,34 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "1.0.21" - GH_AW_INFO_AGENT_VERSION: "1.0.21" - GH_AW_INFO_CLI_VERSION: "v0.68.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_AGENT_VERSION: "1.0.40" + GH_AW_INFO_CLI_VERSION: "v0.72.1" GH_AW_INFO_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.18" + GH_AW_INFO_AWF_VERSION: "v0.25.41" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -112,11 +126,23 @@ jobs: sparse-checkout: | .github .agents + .claude + .codex + .crush + .gemini + .opencode + .pi sparse-checkout-cone-mode: true fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file id: check-lock-file - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_WORKFLOW_FILE: "issue-triage.lock.yml" GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" @@ -127,9 +153,9 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); await main(); - name: Check compile-agentic version - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.68.1" + GH_AW_COMPILED_VERSION: "v0.72.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -138,7 +164,9 @@ jobs: await main(); - name: Compute current body text id: sanitized - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -172,6 +200,9 @@ jobs: Tools: add_comment, add_labels(max:5), missing_tool, missing_data, noop + GH_AW_PROMPT_bef3a8b6a480c883_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_bef3a8b6a480c883_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -208,9 +239,10 @@ jobs: GH_AW_PROMPT_bef3a8b6a480c883_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -218,7 +250,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); await main(); - name: Substitute placeholders - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_GITHUB_ACTOR: ${{ github.actor }} @@ -229,6 +261,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -247,7 +280,8 @@ jobs: GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST } }); - name: Validate prompt placeholders @@ -262,13 +296,18 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: activation + include-hidden-files: true path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents if-no-files-found: ignore retention-days: 1 @@ -287,28 +326,37 @@ jobs: GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs GH_AW_WORKFLOW_ID_SANITIZED: issuetriage outputs: + agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} + inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" - name: Set runtime paths id: set-runtime-paths run: | - echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_OUTPUT" - echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_OUTPUT" - echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_OUTPUT" + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -336,7 +384,7 @@ jobs: id: checkout-pr if: | github.event.pull_request || github.event.issue.pull_request - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: @@ -347,11 +395,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -359,9 +407,25 @@ jobs: GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - - name: Write Safe Outputs Config + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs @@ -369,12 +433,12 @@ jobs: cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_6f7dd6f136148d14_EOF' {"add_comment":{"hide_older_comments":true,"max":1},"add_labels":{"max":5},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_6f7dd6f136148d14_EOF - - name: Write Safe Outputs Tools + - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added.", + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Supports reply_to_id for discussion threading.", "add_labels": " CONSTRAINTS: Maximum 5 label(s) can be added." }, "repo_params": {}, @@ -394,6 +458,10 @@ jobs: "item_number": { "issueOrPRNumber": true }, + "reply_to_id": { + "type": "string", + "maxLength": 256 + }, "repo": { "type": "string", "maxLength": 256 @@ -493,7 +561,7 @@ jobs: } } } - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -549,11 +617,12 @@ jobs: GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | set -eo pipefail - mkdir -p /tmp/gh-aw/mcp-config + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${MCP_GATEWAY_API_KEY}" export MCP_GATEWAY_API_KEY @@ -563,15 +632,19 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_47425e2eb31c9c9e_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_47425e2eb31c9c9e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "container": "ghcr.io/github/github-mcp-server:v1.0.3", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -611,14 +684,27 @@ jobs: } } GH_AW_MCP_CONFIG_47425e2eb31c9c9e_EOF - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: - name: activation - path: /tmp/gh-aw - - name: Clean git credentials + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -626,21 +712,27 @@ jobs: run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41,squid=sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4,agent=sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770,api-proxy=sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.68.1 + GH_AW_VERSION: v0.72.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -652,11 +744,11 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] XDG_CONFIG_HOME: /home/runner - - name: Detect inference access error - id: detect-inference-error + - name: Detect Copilot errors + id: detect-copilot-errors if: always() continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} @@ -685,7 +777,7 @@ jobs: bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -711,7 +803,7 @@ jobs: - name: Ingest agent output id: collect_output if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" @@ -725,7 +817,7 @@ jobs: await main(); - name: Parse agent logs for step summary if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ with: @@ -737,7 +829,7 @@ jobs: - name: Parse MCP Gateway logs for step summary if: always() id: parse-mcp-gateway - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -750,9 +842,9 @@ jobs: env: AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs run: | - # Fix permissions on firewall logs so they can be uploaded as artifacts + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts # AWF runs with sudo, creating files owned by root - sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) if command -v awf &> /dev/null; then awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" @@ -762,13 +854,23 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -778,7 +880,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: agent path: | @@ -790,22 +892,17 @@ jobs: !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt /tmp/gh-aw/agent/ /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch /tmp/gh-aw/aw-*.bundle - if-no-files-found: ignore - - name: Upload firewall audit logs - if: always() - continue-on-error: true - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - with: - name: firewall-audit-logs - path: | + /tmp/gh-aw/awf-config.json /tmp/gh-aw/sandbox/firewall/logs/ /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json if-no-files-found: ignore conclusion: @@ -834,11 +931,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -853,9 +954,9 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Process No-Op Messages + - name: Process no-op messages id: noop - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" @@ -870,9 +971,25 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); - name: Record missing tool id: missing_tool - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" @@ -886,7 +1003,7 @@ jobs: await main(); - name: Record incomplete id: report_incomplete - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" @@ -901,21 +1018,28 @@ jobs: - name: Handle agent failure id: handle_agent_failure if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "issue-triage" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -936,15 +1060,20 @@ jobs: contents: read outputs: detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -965,8 +1094,12 @@ jobs: with: persist-credentials: false # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 - name: Check if detection needed id: detection_guard if: always() @@ -981,10 +1114,10 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | - rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" rm -f /home/runner/.copilot/mcp-config.json rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files @@ -1003,7 +1136,7 @@ jobs: ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - name: Setup threat detection if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "SqlClient Issue Auto-Triage" WORKFLOW_DESCRIPTION: "No description provided" @@ -1019,33 +1152,45 @@ jobs: run: | mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.68.1 + GH_AW_VERSION: v0.72.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1058,7 +1203,7 @@ jobs: XDG_CONFIG_HOME: /home/runner - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1066,15 +1211,33 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } safe_outputs: needs: @@ -1091,9 +1254,12 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-triage" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.40" GH_AW_WORKFLOW_ID: "issue-triage" GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" outputs: @@ -1108,11 +1274,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.40" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1138,7 +1308,7 @@ jobs: echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - name: Process Safe Outputs id: process_safe_outputs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" @@ -1154,9 +1324,11 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: safe-outputs-items - path: /tmp/gh-aw/safe-output-items.jsonl + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json if-no-files-found: ignore diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index 8523f53dd1..8974ec6a38 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -21,9 +21,6 @@ safe-outputs: hide-older-comments: true add-labels: max: 5 - # Phase 2: Uncomment when ready to enable automatic Copilot coding agent assignment - # assign-to-agent: - # github-token: ${{ secrets.GH_AW_AGENT_TOKEN }} --- # SqlClient Issue Auto-Triage @@ -139,9 +136,4 @@ and severity assessment (P0-P3)> > **Note**: This triage summary is auto-generated by an AI agent. The analysis and suggestions above have not been verified by a human maintainer. Please treat as preliminary guidance only. ``` - - If the issue is spam or no action is needed, call the `noop` tool instead. \ No newline at end of file From 6de7d3c06693248707bd7bca353fe4969bc8572f Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 13 May 2026 07:04:46 -0700 Subject: [PATCH 097/298] Test | Convert DateTimeVariantTests to xunit (#4196) --- .github/instructions/testing.instructions.md | 32 + .../tests/Common/DisposableArray.cs | 20 +- ...icrosoft.Data.SqlClient.ManualTests.csproj | 5 - .../SQL/ParameterTest/DateTimeVariant.bsl | 1158 -------------- .../SQL/ParameterTest/DateTimeVariantTest.cs | 1304 ---------------- .../SQL/ParameterTest/DateTimeVariantTests.cs | 1334 ++++++++++++++++- 6 files changed, 1303 insertions(+), 2550 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariant.bsl delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index b5e7d28479..761c056399 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -321,6 +321,38 @@ Extended assertions for SqlClient: AssertExtensions.ThrowsContains(() => action(), "expected message"); ``` +### RAII Database Object Classes +When writing manual integration tests that require transient database objects, use the RAII classes from `Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects` instead of manually writing `try/finally` blocks with DDL `DROP`/`CREATE` statements. + +**Available classes:** + +| Class | SQL generated | Example definition argument | +|-------|--------------|----------------------------| +| `Table` | `CREATE TABLE {Name} {definition}` | `"(Id INT, Value NVARCHAR(100))"` | +| `StoredProcedure` | `CREATE PROCEDURE {Name} {definition}` | `"AS BEGIN SELECT 1 END"` | +| `UserDefinedType` | `CREATE TYPE [dbo].{Name} AS {definition}` | `"TABLE (f1 INT)"` | + +Each class generates a unique object name from the given prefix (incorporating a timestamp-based GUID, username, and machine name), creates the object on construction (requiring the connection to already be open), and drops it when disposed. The generated name is available via the `.Name` property. + +**Pattern:** +```csharp +using SqlConnection conn = new(DataTestUtility.TCPConnectionString); +conn.Open(); + +using Table testTable = new(conn, "MyTable", "(Id INT, Name NVARCHAR(100))"); +using StoredProcedure proc = new(conn, "MyProc", $"AS BEGIN SELECT * FROM {testTable.Name} END"); + +using SqlCommand cmd = conn.CreateCommand(); +cmd.CommandText = proc.Name; +cmd.CommandType = CommandType.StoredProcedure; +// ... objects are automatically dropped when the scope ends +``` + +**Rules:** +- Open the connection **before** constructing any database object (the constructor executes DDL immediately) +- When objects depend on each other (e.g., a stored procedure that references a table), declare the dependent object **last** so it is disposed first — `using` declarations are disposed in reverse order +- Use the `.Name` property directly wherever you need to reference the object in SQL; for `UserDefinedType` this already includes the `[dbo].` schema prefix, making it suitable for use as a TVP `TypeName` + ## Code Coverage ### Running with Coverage diff --git a/src/Microsoft.Data.SqlClient/tests/Common/DisposableArray.cs b/src/Microsoft.Data.SqlClient/tests/Common/DisposableArray.cs index a5ca8e639a..8baca10e21 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/DisposableArray.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/DisposableArray.cs @@ -60,16 +60,32 @@ public T this[int i] /// /// Disposes all elements in the current instance. Each element will be checked for - /// null before disposing it. + /// null before disposing it. All elements are disposed even if earlier disposals + /// throw; any exceptions are collected and rethrown as an + /// after all elements have been attempted. /// public void Dispose() { + List? exceptions = null; + foreach (T element in _elements) { - element?.Dispose(); + try + { + element?.Dispose(); + } + catch (Exception ex) + { + (exceptions ??= new List()).Add(ex); + } } GC.SuppressFinalize(this); + + if (exceptions is not null) + { + throw new AggregateException(exceptions); + } } /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 8ea8a7cecc..71d0f0b0cb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -206,7 +206,6 @@ - @@ -262,10 +261,6 @@ CopyToOutputDirectory="PreserveNewest" /> - - PreserveNewest - DateTimeVariant.bsl - PreserveNewest StreamInputParameter_DebugMode.bsl diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariant.bsl b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariant.bsl deleted file mode 100644 index aa19a20d9a..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariant.bsl +++ /dev/null @@ -1,1158 +0,0 @@ - ------------------------------- TestSimpleParameter_Type [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleParameter_Variant [type: date value:1/1/0001 00:00:00] ------------------------------ -TestSimpleParameter_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: date value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataRecordParameterToTVP_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: date value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReaderParameterToTVP_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReader_TVP_Type [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataReader_TVP_Variant [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : date -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleDataReader_Type [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleDataReader_Variant [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : date -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopySqlDataReader_Type [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : date -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataTable_Type [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataTable_Variant [type: date value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataTable_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataRow_Type [type: date value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataRow_Variant [type: date value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataRow_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSimpleParameter_Type [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -Test Simple Parameter [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSimpleParameter_Variant [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test Simple Parameter [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = date] -Test Simple Parameter [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -Test SqlDataRecord Parameter To TVP [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = date] -Test SqlDataRecord Parameter To TVP [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -Test SqlDataReader Parameter To TVP [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = date] -Test SqlDataReader Parameter To TVP [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReader_TVP_Type [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -Test SqlDataReader TVP [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReader_TVP_Variant [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : date -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -Test SqlDataReader TVP [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSimpleDataReader_Type [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -Test Simple Data Reader [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSimpleDataReader_Variant [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : date -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -Test Simple Data Reader [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopySqlDataReader_Type [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -SqlBulkCopy From SqlDataReader [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : date -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -SqlBulkCopy From SqlDataReader [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataTable_Type [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -SqlBulkCopy From Data Table [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataTable_Variant [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = date] -SqlBulkCopy From Data Table [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataRow_Type [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378112000000000 -SqlBulkCopy From Data Row [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 12:00:00 AM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataRow_Variant [type: date value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == date : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = date] -SqlBulkCopy From Data Row [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSimpleParameter_Type [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleParameter_Variant [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -TestSimpleParameter_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataRecordParameterToTVP_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReaderParameterToTVP_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReader_TVP_Type [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataReader_TVP_Variant [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleDataReader_Type [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleDataReader_Variant [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopySqlDataReader_Type [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataTable_Type [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataTable_Variant [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataTable_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataRow_Type [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataRow_Variant [type: datetime2 value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataRow_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSimpleParameter_Type [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSimpleParameter_Variant [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test Simple Parameter [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -Test Simple Parameter [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReader_TVP_Type [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataReader_TVP_Variant [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSimpleDataReader_Type [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSimpleDataReader_Variant [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopySqlDataReader_Type [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopyDataTable_Type [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopyDataTable_Variant [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataRow_Type [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopyDataRow_Variant [type: datetime2 value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSimpleParameter_Type [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSimpleParameter_Type[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSimpleParameter_Variant [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSimpleParameter_Variant[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataRecordParameterToTVP_Type[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataRecordParameterToTVP_Variant[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReaderParameterToTVP_Type[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReaderParameterToTVP_Variant[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReader_TVP_Type [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReader_TVP_Type[EXPECTED EXCEPTION] The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- TestSqlDataReader_TVP_Variant [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReader_TVP_Variant[EXPECTED EXCEPTION] The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- TestSimpleDataReader_Type [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSimpleDataReader_Type[EXPECTED EXCEPTION] The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- TestSimpleDataReader_Variant [type: datetime value:1/1/0001 00:00:00] ------------------------------ -TestSimpleDataReader_Variant[EXPECTED EXCEPTION] The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- SqlBulkCopySqlDataReader_Type [type: datetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopySqlDataReader_Type[EXPECTED EXCEPTION] The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: datetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopySqlDataReader_Variant[EXPECTED EXCEPTION] The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- SqlBulkCopyDataTable_Type [type: datetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataTable_Type[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataTable_Variant [type: datetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataTable_Variant[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataRow_Type [type: datetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataRow_Type[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataRow_Variant [type: datetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataRow_Variant[EXPECTED EXCEPTION] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSimpleParameter_Type [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test Simple Parameter [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSimpleParameter_Variant [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test Simple Parameter [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataRecord Parameter To TVP [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataRecord Parameter To TVP [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataReader Parameter To TVP [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataReader Parameter To TVP [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReader_TVP_Type [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSqlDataReader_TVP_Type[EXPECTED EXCEPTION] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSqlDataReader_TVP_Variant [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSqlDataReader_TVP_Variant[EXPECTED EXCEPTION] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSimpleDataReader_Type [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSimpleDataReader_Type[EXPECTED EXCEPTION] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSimpleDataReader_Variant [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSimpleDataReader_Variant[EXPECTED EXCEPTION] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopySqlDataReader_Type [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -SqlBulkCopySqlDataReader_Type[EXPECTED EXCEPTION] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -SqlBulkCopySqlDataReader_Variant[EXPECTED EXCEPTION] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopyDataTable_Type [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Table [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataTable_Variant [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Table [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataRow_Type [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Row [Data Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataRow_Variant [type: datetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Row [Variant Type][EXPECTED ERROR]: VALUE MISMATCH - [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSimpleParameter_Type [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleParameter_Variant [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataReader_TVP_Type [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSqlDataReader_TVP_Variant [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleDataReader_Type [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleDataReader_Variant [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopySqlDataReader_Type [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataTable_Type [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataTable_Variant [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataRow_Type [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 0 : 0 - ------------------------------- SqlBulkCopyDataRow_Variant [type: datetimeoffset value:1/1/0001 00:00:00 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 0 : 0 - ------------------------------- TestSimpleParameter_Type [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSimpleParameter_Variant [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataReader_TVP_Type [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSqlDataReader_TVP_Variant [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSimpleDataReader_Type [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSimpleDataReader_Variant [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopySqlDataReader_Type [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopyDataTable_Type [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopyDataTable_Variant [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopyDataRow_Type [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- SqlBulkCopyDataRow_Variant [type: datetimeoffset value:12/31/9999 23:59:59.9999999 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 3155378975999999999 : 3155378975999999999 - ------------------------------- TestSimpleParameter_Type [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- TestSimpleParameter_Variant [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- TestSqlDataReader_TVP_Type [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 630822815999999999 : 630823121999999999 - ------------------------------- TestSqlDataReader_TVP_Variant [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 630822815999999999 : 630823121999999999 - ------------------------------- TestSimpleDataReader_Type [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 630822815999999999 : 630823121999999999 - ------------------------------- TestSimpleDataReader_Variant [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 630822815999999999 : 630823121999999999 - ------------------------------- SqlBulkCopySqlDataReader_Type [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 630822815999999999 : 630823121999999999 - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 630822815999999999 : 630823121999999999 - ------------------------------- SqlBulkCopyDataTable_Type [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- SqlBulkCopyDataTable_Variant [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- SqlBulkCopyDataRow_Type [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == : -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- SqlBulkCopyDataRow_Variant [type: datetimeoffset value:12/31/1999 23:59:59.9999999 -08:30:00] ------------------------------ -Type => Expected : Actual == System.DateTimeOffset : System.DateTimeOffset -Base Type => Expected : Actual == datetimeoffset : datetimeoffset -Value => Expected : Actual == 630822815999999999 : 630822815999999999 - ------------------------------- TestSimpleParameter_Type [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- TestSimpleParameter_Variant [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 630192959999950000 : 630192959999970000 -Test Simple Parameter [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -Test Simple Parameter [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 1/1/1998 11:59:59 PM] [Expected = 1/1/1998 11:59:59 PM] - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 630192959999950000 : 630192959999970000 -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 1/1/1998 11:59:59 PM] [Expected = 1/1/1998 11:59:59 PM] - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 630192959999950000 : 630192959999970000 -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 1/1/1998 11:59:59 PM] [Expected = 1/1/1998 11:59:59 PM] - ------------------------------- TestSqlDataReader_TVP_Type [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- TestSqlDataReader_TVP_Variant [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- TestSimpleDataReader_Type [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- TestSimpleDataReader_Variant [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- SqlBulkCopySqlDataReader_Type [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime2 -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- SqlBulkCopyDataTable_Type [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- SqlBulkCopyDataTable_Variant [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 630192959999950000 : 630192959999970000 -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 1/1/1998 11:59:59 PM] [Expected = 1/1/1998 11:59:59 PM] - ------------------------------- SqlBulkCopyDataRow_Type [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == : -Value => Expected : Actual == 630192959999950000 : 630192959999950000 - ------------------------------- SqlBulkCopyDataRow_Variant [type: datetime2 value:1/1/1998 23:59:59.9950000] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == datetime2 : datetime -Value => Expected : Actual == 630192959999950000 : 630192959999970000 -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = datetime2] -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 1/1/1998 11:59:59 PM] [Expected = 1/1/1998 11:59:59 PM] - ------------------------------- TestSimpleParameter_Type [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSimpleParameter_Type>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSimpleParameter_Variant [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSimpleParameter_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataRecordParameterToTVP_Type>>> EXCEPTION: [System.ArgumentException] Invalid value for this metadata. - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataRecordParameterToTVP_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReaderParameterToTVP_Type>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReaderParameterToTVP_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReader_TVP_Type [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReader_TVP_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- TestSqlDataReader_TVP_Variant [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReader_TVP_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- TestSimpleDataReader_Type [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSimpleDataReader_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- TestSimpleDataReader_Variant [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -TestSimpleDataReader_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- SqlBulkCopySqlDataReader_Type [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopySqlDataReader_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopySqlDataReader_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value. -The statement has been terminated. - ------------------------------- SqlBulkCopyDataTable_Type [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataTable_Type>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataTable_Variant [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataTable_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataRow_Type [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataRow_Type>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataRow_Variant [type: smalldatetime value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataRow_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSimpleParameter_Type [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSimpleParameter_Type>>> EXCEPTION: [System.ArgumentOutOfRangeException] The added or subtracted value results in an un-representable DateTime. -Parameter name: value - ------------------------------- TestSimpleParameter_Variant [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == smalldatetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test Simple Parameter [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = smalldatetime] -Test Simple Parameter [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSqlDataRecordParameterToTVP_Type>>> EXCEPTION: [System.ArgumentException] Invalid value for this metadata. - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == smalldatetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = smalldatetime] -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSqlDataReaderParameterToTVP_Type>>> EXCEPTION: [System.ArgumentOutOfRangeException] The added or subtracted value results in an un-representable DateTime. -Parameter name: value - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == smalldatetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = smalldatetime] -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReader_TVP_Type [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSqlDataReader_TVP_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting character string to smalldatetime data type. - ------------------------------- TestSqlDataReader_TVP_Variant [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSqlDataReader_TVP_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting character string to smalldatetime data type. - ------------------------------- TestSimpleDataReader_Type [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSimpleDataReader_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting character string to smalldatetime data type. - ------------------------------- TestSimpleDataReader_Variant [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSimpleDataReader_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting character string to smalldatetime data type. - ------------------------------- SqlBulkCopySqlDataReader_Type [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -SqlBulkCopySqlDataReader_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting character string to smalldatetime data type. - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -SqlBulkCopySqlDataReader_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting character string to smalldatetime data type. - ------------------------------- SqlBulkCopyDataTable_Type [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -SqlBulkCopyDataTable_Type>>> EXCEPTION: [System.ArgumentOutOfRangeException] The added or subtracted value results in an un-representable DateTime. -Parameter name: value - ------------------------------- SqlBulkCopyDataTable_Variant [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == smalldatetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = smalldatetime] -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataRow_Type [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -SqlBulkCopyDataRow_Type>>> EXCEPTION: [System.ArgumentOutOfRangeException] The added or subtracted value results in an un-representable DateTime. -Parameter name: value - ------------------------------- SqlBulkCopyDataRow_Variant [type: smalldatetime value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == smalldatetime : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = smalldatetime] -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSimpleParameter_Type [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSimpleParameter_Type>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '-10675199.02:48:05.4775808' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSimpleParameter_Variant [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSimpleParameter_Variant>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '-10675199.02:48:05.4775808' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSqlDataRecordParameterToTVP_Type>>> EXCEPTION: [System.ArgumentException] Invalid value for this metadata. - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: time value:-10675199.02:48:05.4775808] ------------------------------ -Type => Expected : Actual == System.TimeSpan : System.TimeSpan -Base Type => Expected : Actual == time : time -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 00:00:00] [Expected = -10675199.02:48:05.4775808] - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSqlDataReaderParameterToTVP_Type>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '-10675199.02:48:05.4775808' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSqlDataReaderParameterToTVP_Variant>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '-10675199.02:48:05.4775808' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSqlDataReader_TVP_Type [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSqlDataReader_TVP_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSqlDataReader_TVP_Variant [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSqlDataReader_TVP_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSimpleDataReader_Type [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSimpleDataReader_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSimpleDataReader_Variant [type: time value:-10675199.02:48:05.4775808] ------------------------------ -TestSimpleDataReader_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopySqlDataReader_Type [type: time value:-10675199.02:48:05.4775808] ------------------------------ -SqlBulkCopySqlDataReader_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: time value:-10675199.02:48:05.4775808] ------------------------------ -SqlBulkCopySqlDataReader_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopyDataTable_Type [type: time value:-10675199.02:48:05.4775808] ------------------------------ -SqlBulkCopyDataTable_Type>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '-10675199.02:48:05.4775808' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- SqlBulkCopyDataTable_Variant [type: time value:-10675199.02:48:05.4775808] ------------------------------ -SqlBulkCopyDataTable_Variant>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '-10675199.02:48:05.4775808' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- SqlBulkCopyDataRow_Type [type: time value:-10675199.02:48:05.4775808] ------------------------------ -SqlBulkCopyDataRow_Type>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '-10675199.02:48:05.4775808' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- SqlBulkCopyDataRow_Variant [type: time value:-10675199.02:48:05.4775808] ------------------------------ -SqlBulkCopyDataRow_Variant>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '-10675199.02:48:05.4775808' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSimpleParameter_Type [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSimpleParameter_Type>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '10675199.02:48:05.4775807' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSimpleParameter_Variant [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSimpleParameter_Variant>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '10675199.02:48:05.4775807' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSqlDataRecordParameterToTVP_Type>>> EXCEPTION: [System.ArgumentException] Invalid value for this metadata. - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSqlDataRecordParameterToTVP_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] The incoming tabular data stream (TDS) remote procedure call (RPC) protocol stream is incorrect. Table-valued parameter 0 (""), row 1, column 1: The supplied value is not a valid instance of data type sql_variant. Check the source data for invalid values. An example of an invalid value is data of numeric type with scale greater than precision. -The statement has been terminated. - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSqlDataReaderParameterToTVP_Type>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '10675199.02:48:05.4775807' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSqlDataReaderParameterToTVP_Variant>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '10675199.02:48:05.4775807' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSqlDataReader_TVP_Type [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSqlDataReader_TVP_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSqlDataReader_TVP_Variant [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSqlDataReader_TVP_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSimpleDataReader_Type [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSimpleDataReader_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- TestSimpleDataReader_Variant [type: time value:10675199.02:48:05.4775807] ------------------------------ -TestSimpleDataReader_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopySqlDataReader_Type [type: time value:10675199.02:48:05.4775807] ------------------------------ -SqlBulkCopySqlDataReader_Type>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: time value:10675199.02:48:05.4775807] ------------------------------ -SqlBulkCopySqlDataReader_Variant>>> EXCEPTION: [Microsoft.Data.SqlClient.SqlException] Conversion failed when converting date and/or time from character string. - ------------------------------- SqlBulkCopyDataTable_Type [type: time value:10675199.02:48:05.4775807] ------------------------------ -SqlBulkCopyDataTable_Type>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '10675199.02:48:05.4775807' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- SqlBulkCopyDataTable_Variant [type: time value:10675199.02:48:05.4775807] ------------------------------ -SqlBulkCopyDataTable_Variant>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '10675199.02:48:05.4775807' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- SqlBulkCopyDataRow_Type [type: time value:10675199.02:48:05.4775807] ------------------------------ -SqlBulkCopyDataRow_Type>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '10675199.02:48:05.4775807' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- SqlBulkCopyDataRow_Variant [type: time value:10675199.02:48:05.4775807] ------------------------------ -SqlBulkCopyDataRow_Variant>>> EXCEPTION: [System.OverflowException] SqlDbType.Time overflow. Value '10675199.02:48:05.4775807' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999. - ------------------------------- TestSimpleParameter_Type [type: time value:1/1/0001 00:00:00] ------------------------------ -TestSimpleParameter_Type>>> EXCEPTION: [System.InvalidCastException] Failed to convert parameter value from a DateTime to a TimeSpan. - ------------------------------- TestSimpleParameter_Variant [type: time value:1/1/0001 00:00:00] ------------------------------ -TestSimpleParameter_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: time value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataRecordParameterToTVP_Type>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: time value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataRecordParameterToTVP_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: time value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReaderParameterToTVP_Type>>> EXCEPTION: [System.InvalidCastException] Failed to convert parameter value from a DateTime to a TimeSpan. - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: time value:1/1/0001 00:00:00] ------------------------------ -TestSqlDataReaderParameterToTVP_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSqlDataReader_TVP_Type [type: time value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == : -TestSqlDataReader_TVP_Type>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- TestSqlDataReader_TVP_Variant [type: time value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == time : time -TestSqlDataReader_TVP_Variant>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- TestSimpleDataReader_Type [type: time value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == : -TestSimpleDataReader_Type>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- TestSimpleDataReader_Variant [type: time value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == time : time -TestSimpleDataReader_Variant>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- SqlBulkCopySqlDataReader_Type [type: time value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == : -SqlBulkCopySqlDataReader_Type>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: time value:1/1/0001 00:00:00] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == time : time -SqlBulkCopySqlDataReader_Variant>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- SqlBulkCopyDataTable_Type [type: time value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataTable_Type[EXPECTED INVALID OPERATION EXCEPTION] The given value '1/1/0001 00:00:00' of type DateTime from the data source cannot be converted to type time for Column 0 [f1] Row 1. - ------------------------------- SqlBulkCopyDataTable_Variant [type: time value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataTable_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- SqlBulkCopyDataRow_Type [type: time value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataRow_Type[EXPECTED INVALID OPERATION EXCEPTION] The given value '1/1/0001 00:00:00' of type DateTime from the data source cannot be converted to type time for Column 0 [f1] Row 1. - ------------------------------- SqlBulkCopyDataRow_Variant [type: time value:1/1/0001 00:00:00] ------------------------------ -SqlBulkCopyDataRow_Variant>>> EXCEPTION: [System.Data.SqlTypes.SqlTypeException] SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM. - ------------------------------- TestSimpleParameter_Type [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSimpleParameter_Type>>> EXCEPTION: [System.InvalidCastException] Failed to convert parameter value from a DateTime to a TimeSpan. - ------------------------------- TestSimpleParameter_Variant [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == time : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test Simple Parameter [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = time] -Test Simple Parameter [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataRecordParameterToTVP_Type [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSqlDataRecordParameterToTVP_Type>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- TestSqlDataRecordParameterToTVP_Variant [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == time : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = time] -Test SqlDataRecord Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReaderParameterToTVP_Type [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -TestSqlDataReaderParameterToTVP_Type>>> EXCEPTION: [System.InvalidCastException] Failed to convert parameter value from a DateTime to a TimeSpan. - ------------------------------- TestSqlDataReaderParameterToTVP_Variant [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == time : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = time] -Test SqlDataReader Parameter To TVP [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- TestSqlDataReader_TVP_Type [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == : -TestSqlDataReader_TVP_Type>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- TestSqlDataReader_TVP_Variant [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == time : time -TestSqlDataReader_TVP_Variant>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- TestSimpleDataReader_Type [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == : -TestSimpleDataReader_Type>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- TestSimpleDataReader_Variant [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == time : time -TestSimpleDataReader_Variant>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- SqlBulkCopySqlDataReader_Type [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == : -SqlBulkCopySqlDataReader_Type>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- SqlBulkCopySqlDataReader_Variant [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.TimeSpan -Base Type => Expected : Actual == time : time -SqlBulkCopySqlDataReader_Variant>>> EXCEPTION: [System.InvalidCastException] Specified cast is not valid. - ------------------------------- SqlBulkCopyDataTable_Type [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -SqlBulkCopyDataTable_Type[EXPECTED INVALID OPERATION EXCEPTION] The given value '12/31/9999 23:59:59.9999999' of type DateTime from the data source cannot be converted to type time for Column 0 [f1] Row 1. - ------------------------------- SqlBulkCopyDataTable_Variant [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == time : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = time] -SqlBulkCopy From Data Table [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] - ------------------------------- SqlBulkCopyDataRow_Type [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -SqlBulkCopyDataRow_Type[EXPECTED INVALID OPERATION EXCEPTION] The given value '12/31/9999 23:59:59.9999999' of type DateTime from the data source cannot be converted to type time for Column 0 [f1] Row 1. - ------------------------------- SqlBulkCopyDataRow_Variant [type: time value:12/31/9999 23:59:59.9999999] ------------------------------ -Type => Expected : Actual == System.DateTime : System.DateTime -Base Type => Expected : Actual == time : datetime -Value => Expected : Actual == 3155378975999999999 : 3155378975999970000 -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = datetime] [Expected = time] -SqlBulkCopy From Data Row [Variant Type]>>> ERROR: VALUE MISMATCH!!! [Actual = 12/31/9999 11:59:59 PM] [Expected = 12/31/9999 11:59:59 PM] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs deleted file mode 100644 index e984a379b8..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTest.cs +++ /dev/null @@ -1,1304 +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 Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests -{ - public static class DateTimeVariantTest - { - private static string s_connStr; - - /// - /// Tests all 2008 DateTime types inside sql_variant to server using sql_variant parameter, SqlBulkCopy, and TVP parameter with sql_variant inside. - /// - public static void TestAllDateTimeWithDataTypeAndVariant(string connStr) - { - s_connStr = connStr; - - SendInfo(System.DateTime.MinValue, "System.DateTime", "date"); - SendInfo(System.DateTime.MaxValue, "System.DateTime", "date"); - - SendInfo(System.DateTime.MinValue, "System.DateTime", "datetime2"); - SendInfo(System.DateTime.MaxValue, "System.DateTime", "datetime2"); - - SendInfo(System.DateTime.MinValue, "System.DateTime", "datetime"); - SendInfo(System.DateTime.MaxValue, "System.DateTime", "datetime"); - - SendInfo(System.DateTimeOffset.MinValue, "System.DateTimeOffset", "datetimeoffset"); - SendInfo(System.DateTimeOffset.MaxValue, "System.DateTimeOffset", "datetimeoffset"); - - SendInfo(System.DateTimeOffset.Parse("12/31/1999 23:59:59.9999999 -08:30"), "System.DateTimeOffset", "datetimeoffset"); - SendInfo(System.DateTime.Parse("1998-01-01 23:59:59.995"), "System.DateTime", "datetime2"); - - SendInfo(System.DateTime.MinValue, "System.DateTime", "smalldatetime"); - SendInfo(System.DateTime.MaxValue, "System.DateTime", "smalldatetime"); - - SendInfo(System.TimeSpan.MinValue, "System.TimeSpan", "time"); - SendInfo(System.TimeSpan.MaxValue, "System.TimeSpan", "time"); - - SendInfo(System.DateTime.MinValue, "System.DateTime", "time"); - SendInfo(System.DateTime.MaxValue, "System.DateTime", "time"); - } - - private static void SendInfo(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - TestSimpleParameter_Type(paramValue, expectedTypeName, expectedBaseTypeName); - TestSimpleParameter_Variant(paramValue, expectedTypeName, expectedBaseTypeName); - - TestSqlDataRecordParameterToTVP_Type(paramValue, expectedTypeName, expectedBaseTypeName); - TestSqlDataRecordParameterToTVP_Variant(paramValue, expectedTypeName, expectedBaseTypeName); - - TestSqlDataReaderParameterToTVP_Type(paramValue, expectedTypeName, expectedBaseTypeName); - TestSqlDataReaderParameterToTVP_Variant(paramValue, expectedTypeName, expectedBaseTypeName); - - TestSqlDataReader_TVP_Type(paramValue, expectedTypeName, expectedBaseTypeName); - TestSqlDataReader_TVP_Variant(paramValue, expectedTypeName, expectedBaseTypeName); - - TestSimpleDataReader_Type(paramValue, expectedTypeName, expectedBaseTypeName); - TestSimpleDataReader_Variant(paramValue, expectedTypeName, expectedBaseTypeName); - - SqlBulkCopySqlDataReader_Type(paramValue, expectedTypeName, expectedBaseTypeName); - SqlBulkCopySqlDataReader_Variant(paramValue, expectedTypeName, expectedBaseTypeName); - - SqlBulkCopyDataTable_Type(paramValue, expectedTypeName, expectedBaseTypeName); - SqlBulkCopyDataTable_Variant(paramValue, expectedTypeName, expectedBaseTypeName); - - SqlBulkCopyDataRow_Type(paramValue, expectedTypeName, expectedBaseTypeName); - SqlBulkCopyDataRow_Variant(paramValue, expectedTypeName, expectedBaseTypeName); - } - - private static void TestSimpleParameter_Type(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSimpleParameter_Type"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string procName = DataTestUtility.GetLongName("paramProc1"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropStoredProcedure(conn, procName); - xsql(conn, string.Format("create proc {0} (@param {1}) as begin select @param end;", procName, expectedBaseTypeName)); - - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = procName; - cmd.CommandType = CommandType.StoredProcedure; - SqlParameter p = cmd.Parameters.AddWithValue("@param", paramValue); - cmd.Parameters[0].SqlDbType = GetSqlDbType(expectedBaseTypeName); - using (SqlDataReader dr = cmd.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test Simple Parameter [Data Type]", expectedBaseTypeName, expectedTypeName, dr[0], expectedTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropStoredProcedure(conn, procName); - } - } - - // sql_variant parameters and sql_variant TVPs store all datetime values internally - // as datetime, hence breaking for 2008 types - private static void TestSimpleParameter_Variant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSimpleParameter_Variant"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string procName = DataTestUtility.GetLongName("paramProc2"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropStoredProcedure(conn, procName); - xsql(conn, string.Format("create proc {0} (@param sql_variant) as begin select @param, sql_variant_property(@param,'BaseType') as BaseType end;", procName)); - - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = procName; - cmd.CommandType = CommandType.StoredProcedure; - SqlParameter p = cmd.Parameters.AddWithValue("@param", paramValue); - cmd.Parameters[0].SqlDbType = SqlDbType.Variant; - using (SqlDataReader dr = cmd.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test Simple Parameter [Variant Type]", "SqlDbType.Variant", dr, expectedTypeName, expectedBaseTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropStoredProcedure(conn, procName); - } - } - - private static void TestSqlDataRecordParameterToTVP_Type(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSqlDataRecordParameterToTVP_Type"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetLongName("tvpType"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropType(conn, tvpTypeName); - xsql(conn, string.Format("create type dbo.{0} as table (f1 {1})", tvpTypeName, expectedBaseTypeName)); - - // Send TVP using SqlMetaData. - SqlMetaData[] metadata = new SqlMetaData[1]; - metadata[0] = new SqlMetaData("f1", GetSqlDbType(expectedBaseTypeName)); - SqlDataRecord[] record = new SqlDataRecord[1]; - record[0] = new SqlDataRecord(metadata); - record[0].SetValue(0, paramValue); - - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = "select f1 from @tvpParam"; - SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", record); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = string.Format("dbo.{0}", tvpTypeName); - using (SqlDataReader dr = cmd.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test SqlDataRecord Parameter To TVP [Data Type]", expectedBaseTypeName, expectedTypeName, dr[0], expectedTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropType(conn, tvpTypeName); - } - } - - // sql_variant parameters and sql_variant TVPs store all datetime values internally - // as datetime, hence breaking for 2008 types - private static void TestSqlDataRecordParameterToTVP_Variant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSqlDataRecordParameterToTVP_Variant"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropType(conn, tvpTypeName); - xsql(conn, string.Format("create type dbo.{0} as table (f1 sql_variant)", tvpTypeName)); - - // Send TVP using SqlMetaData. - SqlMetaData[] metadata = new SqlMetaData[1]; - metadata[0] = new SqlMetaData("f1", SqlDbType.Variant); - SqlDataRecord[] record = new SqlDataRecord[1]; - record[0] = new SqlDataRecord(metadata); - record[0].SetValue(0, paramValue); - - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; - SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", record); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = string.Format("dbo.{0}", tvpTypeName); - using (SqlDataReader dr = cmd.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test SqlDataRecord Parameter To TVP [Variant Type]", "SqlDbType.Variant", dr, expectedTypeName, expectedBaseTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropType(conn, tvpTypeName); - } - } - - private static void TestSqlDataReaderParameterToTVP_Type(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSqlDataReaderParameterToTVP_Type"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetLongName("tvpType"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropType(conn, tvpTypeName); - xsql(conn, string.Format("create type dbo.{0} as table (f1 {1})", tvpTypeName, expectedBaseTypeName)); - using (SqlConnection connInput = new(s_connStr)) - { - connInput.Open(); - using (SqlCommand cmdInput = connInput.CreateCommand()) - { - cmdInput.CommandText = "select @p1 as f1"; - cmdInput.Parameters.Add("@p1", GetSqlDbType(expectedBaseTypeName)); - cmdInput.Parameters["@p1"].Value = paramValue; - using SqlDataReader drInput = cmdInput.ExecuteReader(CommandBehavior.CloseConnection); - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = "select f1 from @tvpParam"; - SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", drInput); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = string.Format("dbo.{0}", tvpTypeName); - using (SqlDataReader dr = cmd.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test SqlDataReader Parameter To TVP [Data Type]", expectedBaseTypeName, expectedTypeName, dr[0], expectedTypeName, paramValue); - } - } - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropType(conn, tvpTypeName); - } - } - - // sql_variant parameters and sql_variant TVPs store all datetime values internally - // as datetime, hence breaking for 2008 types - private static void TestSqlDataReaderParameterToTVP_Variant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSqlDataReaderParameterToTVP_Variant"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetLongName("tvpVariant"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropType(conn, tvpTypeName); - xsql(conn, string.Format("create type dbo.{0} as table (f1 sql_variant)", tvpTypeName)); - - // Send TVP using SqlDataReader. - using (SqlConnection connInput = new(s_connStr)) - { - connInput.Open(); - using (SqlCommand cmdInput = connInput.CreateCommand()) - { - cmdInput.CommandText = "select @p1 as f1"; - cmdInput.Parameters.Add("@p1", SqlDbType.Variant); - cmdInput.Parameters["@p1"].Value = paramValue; - using SqlDataReader drInput = cmdInput.ExecuteReader(CommandBehavior.CloseConnection); - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; - SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", drInput); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = string.Format("dbo.{0}", tvpTypeName); - using (SqlDataReader dr = cmd.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test SqlDataReader Parameter To TVP [Variant Type]", "SqlDbType.Variant", dr, expectedTypeName, expectedBaseTypeName, paramValue); - } - } - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropType(conn, tvpTypeName); - } - } - - // sql_variant parameters and sql_variant TVPs store all datetime values internally - // as datetime, hence breaking for 2008 types - private static void TestSqlDataReader_TVP_Type(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSqlDataReader_TVP_Type"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetLongName("tvpType"); - string InputTableName = DataTestUtility.GetLongName("InputTable"); - string OutputTableName = DataTestUtility.GetLongName("OutputTable"); - string ProcName = DataTestUtility.GetLongName("spTVPProc"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - - DropStoredProcedure(conn, ProcName); - DropTable(conn, InputTableName); - DropTable(conn, OutputTableName); - DropType(conn, $"dbo.{tvpTypeName}"); - - xsql(conn, string.Format("create type dbo.{0} as table (f1 {1})", tvpTypeName, expectedBaseTypeName)); - xsql(conn, string.Format("create table {0} (f1 {1})", InputTableName, expectedBaseTypeName)); - xsql(conn, string.Format("create table {0} (f1 {1})", OutputTableName, expectedBaseTypeName)); - - string value = string.Empty; - if (paramValue.GetType() == typeof(DateTimeOffset)) - { - DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; - value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; - } - else if (paramValue.GetType() == typeof(TimeSpan)) - { - value = ((TimeSpan)paramValue).ToString(); - } - else - { - value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; - } - xsql(conn, string.Format("insert into {0} values(CAST('{1}' AS {2}))", InputTableName, value, expectedBaseTypeName)); - xsql(conn, string.Format("create proc {0} (@P {1} READONLY) as begin insert into {2} select * from @P; end", ProcName, tvpTypeName, OutputTableName)); - - SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format("SELECT * FROM {0}", InputTableName); - using SqlDataReader r = cmd.ExecuteReader(); - using (SqlConnection conn2 = new(s_connStr)) - { - conn2.Open(); - SqlCommand cmd2 = new(ProcName, conn2) - { - CommandType = CommandType.StoredProcedure - }; - SqlParameter p = cmd2.Parameters.AddWithValue("@P", r); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = tvpTypeName; - cmd2.ExecuteNonQuery(); - - cmd2.CommandText = string.Format("SELECT f1 FROM {0}", OutputTableName); - cmd2.CommandType = CommandType.Text; - using (SqlDataReader dr = cmd2.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test SqlDataReader TVP [Data Type]", expectedBaseTypeName, expectedTypeName, dr[0], expectedTypeName, paramValue); - } - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropStoredProcedure(conn, ProcName); - DropTable(conn, InputTableName); - DropTable(conn, OutputTableName); - DropType(conn, tvpTypeName); - } - } - - private static void TestSqlDataReader_TVP_Variant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSqlDataReader_TVP_Variant"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string tvpTypeName = DataTestUtility.GetLongName("tvpVariant_DRdrTVPVar"); - string InputTableName = DataTestUtility.GetLongName("InputTable"); - string OutputTableName = DataTestUtility.GetLongName("OutputTable"); - string ProcName = DataTestUtility.GetLongName("spTVPProc_DRdrTVPVar"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - - DropStoredProcedure(conn, ProcName); - DropTable(conn, InputTableName); - DropTable(conn, OutputTableName); - DropType(conn, tvpTypeName); - - xsql(conn, string.Format("create type {0} as table (f1 sql_variant)", tvpTypeName)); - xsql(conn, string.Format("create table {0} (f1 sql_variant)", InputTableName)); - xsql(conn, string.Format("create table {0} (f1 sql_variant)", OutputTableName)); - - string value = string.Empty; - if (paramValue.GetType() == typeof(DateTimeOffset)) - { - DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; - value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; - } - else if (paramValue.GetType() == typeof(TimeSpan)) - { - value = ((TimeSpan)paramValue).ToString(); - } - else - { - value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; - } - xsql(conn, string.Format("insert into {0} values(CAST('{1}' AS {2}))", InputTableName, value, expectedBaseTypeName)); - xsql(conn, string.Format("create proc {0} (@P {1} READONLY) as begin insert into {2} select * from @P; end", ProcName, tvpTypeName, OutputTableName)); - - SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format("SELECT * FROM {0}", InputTableName); - using SqlDataReader r = cmd.ExecuteReader(); - using (SqlConnection conn2 = new(s_connStr)) - { - conn2.Open(); - using (SqlCommand cmd2 = new(ProcName, conn2)) - { - cmd2.CommandType = CommandType.StoredProcedure; - SqlParameter p = cmd2.Parameters.AddWithValue("@P", r); - p.SqlDbType = SqlDbType.Structured; - p.TypeName = tvpTypeName; - cmd2.ExecuteNonQuery(); - - cmd2.CommandText = string.Format("SELECT f1, sql_variant_property(f1,'BaseType') as BaseType FROM {0}", OutputTableName); - ; - cmd2.CommandType = CommandType.Text; - using (SqlDataReader dr = cmd2.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test SqlDataReader TVP [Variant Type]", "SqlDbType.Variant", dr, expectedTypeName, expectedBaseTypeName, paramValue); - } - } - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropStoredProcedure(conn, ProcName); - DropTable(conn, InputTableName); - DropTable(conn, OutputTableName); - DropType(conn, tvpTypeName); - } - } - - // sql_variant parameters and sql_variant TVPs store all datetime values internally - // as datetime, hence breaking for 2008 types - private static void TestSimpleDataReader_Type(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSimpleDataReader_Type"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string inputTable = DataTestUtility.GetLongName("inputTable"); - string procName = DataTestUtility.GetLongName("paramProc3"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, inputTable); - DropStoredProcedure(conn, procName); - - string value = string.Empty; - if (paramValue.GetType() == typeof(DateTimeOffset)) - { - DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; - value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; - } - else if (paramValue.GetType() == typeof(TimeSpan)) - { - value = ((TimeSpan)paramValue).ToString(); - } - else - { - value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; - } - xsql(conn, string.Format("create table {0} (f1 {1})", inputTable, expectedBaseTypeName)); - xsql(conn, string.Format("insert into {0}(f1) values('{1}');", inputTable, value)); - xsql(conn, string.Format("create proc {0} as begin select f1 from {1} end;", procName, inputTable)); - - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = procName; - cmd.CommandType = CommandType.StoredProcedure; - using (SqlDataReader dr = cmd.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test Simple Data Reader [Data Type]", expectedBaseTypeName, expectedTypeName, dr[0], expectedTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropStoredProcedure(conn, procName); - DropTable(conn, inputTable); - } - } - - private static void TestSimpleDataReader_Variant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "TestSimpleDataReader_Variant"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string inputTable = DataTestUtility.GetLongName("inputTable"); - string procName = DataTestUtility.GetLongName("paramProc4"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, inputTable); - DropStoredProcedure(conn, procName); - - string value = string.Empty; - if (paramValue.GetType() == typeof(DateTimeOffset)) - { - DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; - value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; - } - else if (paramValue.GetType() == typeof(TimeSpan)) - { - value = ((TimeSpan)paramValue).ToString(); - } - else - { - value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; - } - xsql(conn, string.Format("create table {0} (f1 sql_variant)", inputTable)); - xsql(conn, string.Format("insert into {0}(f1) values(CAST('{1}' AS {2}));", inputTable, value, expectedBaseTypeName)); - xsql(conn, string.Format("create proc {0} as begin select f1, sql_variant_property(f1,'BaseType') as BaseType from {1} end;", procName, inputTable)); - - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = procName; - cmd.CommandType = CommandType.StoredProcedure; - using (SqlDataReader dr = cmd.ExecuteReader()) - { - dr.Read(); - VerifyReaderTypeAndValue("Test Simple Data Reader [Variant Type]", "SqlDbType.Variant", dr, expectedTypeName, expectedBaseTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropStoredProcedure(conn, procName); - DropTable(conn, inputTable); - } - } - - private static void SqlBulkCopySqlDataReader_Type(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "SqlBulkCopySqlDataReader_Type"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopySrcTableName = DataTestUtility.GetLongName("bulkSrcTable"); - string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestTable"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - xsql(conn, string.Format("create table {0} (f1 {1})", bulkCopyTableName, expectedBaseTypeName)); - - DropTable(conn, bulkCopySrcTableName); - xsql(conn, string.Format("create table {0} (f1 {1})", bulkCopySrcTableName, expectedBaseTypeName)); - string value = string.Empty; - if (paramValue.GetType() == typeof(DateTimeOffset)) - { - DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; - value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; - } - else if (paramValue.GetType() == typeof(TimeSpan)) - { - value = ((TimeSpan)paramValue).ToString(); - } - else - { - value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; - } - xsql(conn, string.Format("insert into {0}(f1) values(CAST('{1}' AS {2}));", bulkCopySrcTableName, value, expectedBaseTypeName)); - - using SqlConnection connInput = new(s_connStr); - connInput.Open(); - using (SqlCommand cmdInput = connInput.CreateCommand()) - { - cmdInput.CommandText = string.Format("select * from {0}", bulkCopySrcTableName); - using SqlDataReader drInput = cmdInput.ExecuteReader(); - // Perform bulk copy to target. - using (SqlBulkCopy bulkCopy = new(conn)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(drInput); - } - - // Verify target. - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format("select f1 from {0}", bulkCopyTableName); - using (SqlDataReader drVerify = cmd.ExecuteReader()) - { - drVerify.Read(); - VerifyReaderTypeAndValue("SqlBulkCopy From SqlDataReader [Data Type]", expectedBaseTypeName, expectedTypeName, drVerify[0], expectedTypeName, paramValue); - } - } - connInput.Close(); - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - DropTable(conn, bulkCopySrcTableName); - } - - } - - private static void SqlBulkCopySqlDataReader_Variant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "SqlBulkCopySqlDataReader_Variant"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopySrcTableName = DataTestUtility.GetLongName("bulkSrcTable"); - string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestTable"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - xsql(conn, string.Format("create table {0} (f1 sql_variant)", bulkCopyTableName)); - - DropTable(conn, bulkCopySrcTableName); - xsql(conn, string.Format("create table {0} (f1 {1})", bulkCopySrcTableName, expectedBaseTypeName)); - string value = string.Empty; - if (paramValue.GetType() == typeof(DateTimeOffset)) - { - DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; - value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; - } - else if (paramValue.GetType() == typeof(TimeSpan)) - { - value = ((TimeSpan)paramValue).ToString(); - } - else - { - value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; - } - xsql(conn, string.Format("insert into {0}(f1) values(CAST('{1}' AS {2}));", bulkCopySrcTableName, value, expectedBaseTypeName)); - - using (SqlConnection connInput = new(s_connStr)) - { - connInput.Open(); - using (SqlCommand cmdInput = connInput.CreateCommand()) - { - cmdInput.CommandText = string.Format("select * from {0}", bulkCopySrcTableName); - using SqlDataReader drInput = cmdInput.ExecuteReader(); - { - // Perform bulk copy to target. - using (SqlBulkCopy bulkCopy = new(conn)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(drInput); - } - - // Verify target. - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using (SqlDataReader drVerify = cmd.ExecuteReader()) - { - drVerify.Read(); - VerifyReaderTypeAndValue("SqlBulkCopy From SqlDataReader [Variant Type]", "SqlDbType.Variant", drVerify, expectedTypeName, expectedBaseTypeName, paramValue); - } - } - } - } - - conn.Close(); - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - DropTable(conn, bulkCopySrcTableName); - } - } - - private static void SqlBulkCopyDataTable_Type(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "SqlBulkCopyDataTable_Type"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestType"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - xsql(conn, string.Format("create table {0} (f1 {1})", bulkCopyTableName, expectedBaseTypeName)); - - // Send using DataTable as source. - DataTable t = new(); - t.Columns.Add("f1", paramValue.GetType()); - t.Rows.Add(new object[] { paramValue }); - - // Perform bulk copy to target. - using (SqlBulkCopy bulkCopy = new(conn)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(t, DataRowState.Added); - } - - // Verify target. - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format("select f1 from {0}", bulkCopyTableName); - using (SqlDataReader drVerify = cmd.ExecuteReader()) - { - drVerify.Read(); - VerifyReaderTypeAndValue("SqlBulkCopy From Data Table [Data Type]", expectedBaseTypeName, expectedTypeName, drVerify[0], expectedTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else if (IsExpectedInvalidOperationException(e, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED INVALID OPERATION EXCEPTION] " + AmendTheGivenMessageDateValueException(e.Message, paramValue)); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - } - } - - // sql_variant parameters and sql_variant TVPs store all datetime values internally - // as datetime, hence breaking for 2008 types - private static void SqlBulkCopyDataTable_Variant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "SqlBulkCopyDataTable_Variant"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestVariant"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - xsql(conn, string.Format("create table {0} (f1 sql_variant)", bulkCopyTableName)); - - // Send using DataTable as source. - DataTable t = new(); - t.Columns.Add("f1", typeof(object)); - t.Rows.Add(new object[] { paramValue }); - - // Perform bulk copy to target. - using (SqlBulkCopy bulkCopy = new(conn)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(t, DataRowState.Added); - } - - // Verify target. - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using (SqlDataReader drVerify = cmd.ExecuteReader()) - { - drVerify.Read(); - VerifyReaderTypeAndValue("SqlBulkCopy From Data Table [Variant Type]", "SqlDbType.Variant", drVerify, expectedTypeName, expectedBaseTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - } - } - - private static void SqlBulkCopyDataRow_Type(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "SqlBulkCopyDataRow_Type"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestType"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - xsql(conn, string.Format("create table {0} (f1 {1})", bulkCopyTableName, expectedBaseTypeName)); - DataTable t = new(); - t.Columns.Add("f1", paramValue.GetType()); - t.Rows.Add(new object[] { paramValue }); - DataRow[] rowToSend = t.Select(); - using (SqlBulkCopy bulkCopy = new(conn)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(rowToSend); - } - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format("select f1 from {0}", bulkCopyTableName); - using (SqlDataReader drVerify = cmd.ExecuteReader()) - { - drVerify.Read(); - VerifyReaderTypeAndValue("SqlBulkCopy From Data Row [Data Type]", expectedBaseTypeName, expectedTypeName, drVerify[0], expectedTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else if (IsExpectedInvalidOperationException(e, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED INVALID OPERATION EXCEPTION] " + AmendTheGivenMessageDateValueException(e.Message, paramValue)); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - } - } - - // sql_variant parameters and sql_variant TVPs store all datetime values internally - // as datetime, hence breaking for 2008 types - private static void SqlBulkCopyDataRow_Variant(object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - string tag = "SqlBulkCopyDataRow_Variant"; - DisplayHeader(tag, paramValue, expectedBaseTypeName); - string bulkCopyTableName = DataTestUtility.GetLongName("bulkDestVariant"); - try - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - xsql(conn, string.Format("create table {0} (f1 sql_variant)", bulkCopyTableName)); - DataTable t = new(); - t.Columns.Add("f1", typeof(object)); - t.Rows.Add(new object[] { paramValue }); - DataRow[] rowToSend = t.Select(); - using (SqlBulkCopy bulkCopy = new(conn)) - { - bulkCopy.BulkCopyTimeout = 60; - bulkCopy.BatchSize = 1; - bulkCopy.DestinationTableName = bulkCopyTableName; - bulkCopy.WriteToServer(rowToSend); - } - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTableName); - using (SqlDataReader drVerify = cmd.ExecuteReader()) - { - drVerify.Read(); - VerifyReaderTypeAndValue("SqlBulkCopy From Data Row [Variant Type]", "SqlDbType.Variant", drVerify, expectedTypeName, expectedBaseTypeName, paramValue); - } - } - catch (Exception e) - { - if (IsExpectedException(e, paramValue, expectedTypeName, expectedBaseTypeName)) - { - LogMessage(tag, "[EXPECTED EXCEPTION] " + e.Message); - } - else - { - DisplayError(tag, e); - } - } - finally - { - using SqlConnection conn = new(s_connStr); - conn.Open(); - DropTable(conn, bulkCopyTableName); - } - } - - // NOTE: Actions - - private static SqlDbType GetSqlDbType(string expectedBaseTypeName) - { - return expectedBaseTypeName.ToLowerInvariant() switch - { - "time" => SqlDbType.Time, - "date" => SqlDbType.Date, - "smalldatetime" => SqlDbType.SmallDateTime, - "datetime" => SqlDbType.DateTime, - "datetime2" => SqlDbType.DateTime2, - "datetimeoffset" => SqlDbType.DateTimeOffset, - _ => SqlDbType.Variant, - }; - } - - private static void xsql(SqlConnection conn, string sql) - { - using SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = sql; - cmd.ExecuteNonQuery(); - } - - private static void DropStoredProcedure(SqlConnection conn, string procName) - { - xsql(conn, string.Format("if exists(select 1 from sys.procedures where name='{0}') begin drop proc {1} end", procName.Substring(1, procName.Length - 2), procName)); - } - - private static void DropTable(SqlConnection conn, string tableName) - { - xsql(conn, string.Format("if exists(select 1 from sys.tables where name='{0}') begin drop table {1} end", tableName.Substring(1, tableName.Length - 2), tableName)); - } - - private static void DropType(SqlConnection conn, string typeName) - { - xsql(conn, string.Format("if exists(select 1 from sys.types where name='{0}') begin drop type {1} end", typeName.Substring(1, typeName.Length - 2), typeName)); - } - - // NOTE: Checking and Verification - private static void VerifyReaderTypeAndValue(string tag, string expectedBaseTypeName, string type, object actualValue, string expectedTypeName, object expectedValue) - { - string actualTypeName = actualValue.GetType().ToString(); - - LogValues(tag, expectedTypeName, string.Empty, expectedValue, actualTypeName, string.Empty, actualValue); - - if (!actualTypeName.Equals(expectedTypeName)) - { - string ErrorMessage = string.Format(">>> ERROR: TYPE MISMATCH!!! [Actual = {0}] [Expected = {1}]", - actualTypeName, - expectedTypeName); - - LogMessage(tag, ErrorMessage); - } - if (!actualValue.Equals(expectedValue)) - { - string ErrorMessage; - if (IsValueCorrectForType(expectedBaseTypeName, expectedValue, actualValue)) - { - ErrorMessage = string.Format("[EXPECTED ERROR]: VALUE MISMATCH - [Actual = {0}] [Expected = {1}]", - DataTestUtility.GetValueString(actualValue), - DataTestUtility.GetValueString(expectedValue)); - } - else - { - ErrorMessage = string.Format(">>> ERROR: VALUE MISMATCH!!! [Actual = {0}] [Expected = {1}]", - DataTestUtility.GetValueString(actualValue), - DataTestUtility.GetValueString(expectedValue)); - } - LogMessage(tag, ErrorMessage); - } - } - - private static void VerifyReaderTypeAndValue(string tag, string type, SqlDataReader dr, string expectedTypeName, string expectedBaseTypeName, object expectedValue) - { - object actualValue = dr[0]; - string actualTypeName = actualValue.GetType().ToString(); - string actualBaseTypeName = dr.GetString(1); - - LogValues(tag, expectedTypeName, expectedBaseTypeName, expectedValue, actualTypeName, actualBaseTypeName, actualValue); - - if (!actualTypeName.Equals(expectedTypeName)) - { - string ErrorMessage = string.Format(">>> ERROR: TYPE MISMATCH!!! [Actual = {0}] [Expected = {1}]", - actualTypeName, - expectedTypeName); - LogMessage(tag, ErrorMessage); - } - if (!actualBaseTypeName.Equals(expectedBaseTypeName)) - { - if (((actualTypeName.ToLowerInvariant() != "system.datetime") || (actualTypeName.ToLowerInvariant() != "system.datetimeoffset")) - && (actualBaseTypeName != "datetime2")) - { - string ErrorMessage = string.Format(">>> ERROR: VARIANT BASE TYPE MISMATCH!!! [Actual = {0}] [Expected = {1}]", - actualBaseTypeName, - expectedBaseTypeName); - LogMessage(tag, ErrorMessage); - } - } - if (!actualValue.Equals(expectedValue)) - { - string ErrorMessage; - if (IsValueCorrectForType(expectedBaseTypeName, expectedValue, actualValue)) - { - ErrorMessage = string.Format("[EXPECTED ERROR]: VALUE MISMATCH - [Actual = {0}] [Expected = {1}]", - DataTestUtility.GetValueString(actualValue), - DataTestUtility.GetValueString(expectedValue)); - } - else - { - ErrorMessage = string.Format(">>> ERROR: VALUE MISMATCH!!! [Actual = {0}] [Expected = {1}]", - DataTestUtility.GetValueString(actualValue), - DataTestUtility.GetValueString(expectedValue)); - } - LogMessage(tag, ErrorMessage); - } - } - - private static bool IsValueCorrectForType(string expectedBaseTypeName, object expectedValue, object actualValue) - { - switch (expectedBaseTypeName) - { - case "date": - if (((DateTime)expectedValue).ToString("M/d/yyyy").Equals(((DateTime)actualValue).ToString("M/d/yyyy"))) - { - return true; - } - else - { - return false; - } - case "datetime": - if ((((DateTime)expectedValue).Ticks == 3155378975999999999) && - (((DateTime)actualValue).Ticks == 3155378975999970000)) - { - return true; - } - else - { - return false; - } - default: - return false; - } - } - - private static bool IsExpectedException(Exception e, object paramValue, string expectedTypeName, string expectedBaseTypeName) - { - if ((e.GetType() == typeof(System.Data.SqlTypes.SqlTypeException)) && - (expectedBaseTypeName == "datetime") && - (e.Message.Contains("1753")) && - (((DateTime)paramValue).Year < 1753)) - { - return true; - } - else if ((e.GetType() == typeof(SqlException)) && - (expectedBaseTypeName == "datetime") && - (e.Message.Contains("conversion of a varchar data type to a datetime data type")) && - (((DateTime)paramValue).Year < 1753)) - { - return true; - } - else if ((e.GetType() == typeof(SqlException)) && - (expectedBaseTypeName == "datetime") && - (e.Message.Contains("converting date and/or time from character string")) && - (((DateTime)paramValue) == DateTime.MaxValue)) - { - return true; - } - else - { - return false; - } - } - - private static bool IsExpectedInvalidOperationException(Exception e, string expectedBaseTypeName) - { - return ((e.GetType() == typeof(InvalidOperationException)) && - (expectedBaseTypeName == "time") && - (e.Message.Contains("The given value "))); - } - - private static string AmendTheGivenMessageDateValueException(string message, object paramValue) - { - string value; - if (paramValue.GetType() == typeof(DateTimeOffset)) - { - DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; - value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; - } - else if (paramValue.GetType() == typeof(TimeSpan)) - { - value = ((TimeSpan)paramValue).ToString(); - } - else - { - value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; - } - - return message.Replace(paramValue.ToString(), value); - } - - // NOTE: Logging and Display - private static void DisplayHeader(string tag, object paramValue, string expectedBaseTypeName) - { - Console.WriteLine(""); - string value; - if (paramValue.GetType() == typeof(DateTimeOffset)) - { - DateTimeOffset dt = (DateTimeOffset)paramValue; - value = dt.DateTime.ToString("M/d/yyyy") + " " + dt.DateTime.TimeOfDay + " " + dt.Offset; - } - else if (paramValue.GetType() == typeof(TimeSpan)) - { - value = ((TimeSpan)paramValue).ToString(); - } - else - { - value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; - } - - Console.WriteLine(string.Format("------------------------------ {0} [type: {1} value:{2}] ------------------------------", tag, expectedBaseTypeName, value)); - } - - private static void DisplayError(string tag, Exception e) - { - string ExceptionMessage = string.Format(">>> EXCEPTION: [{0}] {1}", e.GetType(), e.Message); - - // InvalidCastException message is different between core and framework for this cast attempt. - // Make them the same for the purposes of comparing test output. - if (e is InvalidCastException && - e.Message.Equals("Unable to cast object of type 'System.TimeSpan' to type 'System.DateTime'.")) - { - ExceptionMessage = string.Format(">>> EXCEPTION: [{0}] {1}", e.GetType(), "Specified cast is not valid."); - } - - if (e is ArgumentOutOfRangeException && - e.Message.Equals("The added or subtracted value results in an un-representable DateTime. (Parameter 'value')")) - { - ExceptionMessage = string.Format(">>> EXCEPTION: [{0}] {1}", e.GetType(), "The added or subtracted value results in an un-representable DateTime.\nParameter name: value"); - } - - LogMessage(tag, ExceptionMessage); - } - - private static void LogMessage(string tag, string message) - { - Console.WriteLine(string.Format("{0}{1}", tag, message)); - } - - private static void LogValues(string tag, string expectedTypeName, string expectedBaseTypeName, object expectedValue, string actualTypeName, string actualBaseTypeName, object actualValue) - { - Console.WriteLine(string.Format("Type => Expected : Actual == {0} : {1}", expectedTypeName, actualTypeName)); - Console.WriteLine(string.Format("Base Type => Expected : Actual == {0} : {1}", expectedBaseTypeName, actualBaseTypeName)); - if (expectedTypeName == "System.DateTimeOffset") - { - Console.WriteLine(string.Format("Value => Expected : Actual == {0} : {1}", ((DateTimeOffset)expectedValue).Ticks.ToString(), ((DateTimeOffset)actualValue).Ticks.ToString())); - } - else if (expectedTypeName == "System.DateTime") - { - Console.WriteLine(string.Format("Value => Expected : Actual == {0} : {1}", ((DateTime)expectedValue).Ticks.ToString(), ((DateTime)actualValue).Ticks.ToString())); - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs index 8bb470a369..ec9f516260 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs @@ -3,125 +3,1297 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; +using System.Data; using System.Globalization; -using System.IO; -using System.Text; using System.Threading; +using Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; +#nullable enable namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + public enum TestVariations + { + TestSimpleParameter_Type, + TestSimpleParameter_Variant, + TestSqlDataRecordParameterToTVP_Type, + TestSqlDataRecordParameterToTVP_Variant, + TestSqlDataReaderParameterToTVP_Type, + TestSqlDataReaderParameterToTVP_Variant, + TestSqlDataReader_TVP_Type, + TestSqlDataReader_TVP_Variant, + TestSimpleDataReader_Type, + TestSimpleDataReader_Variant, + SqlBulkCopySqlDataReader_Type, + SqlBulkCopySqlDataReader_Variant, + SqlBulkCopyDataTable_Type, + SqlBulkCopyDataTable_Variant, + SqlBulkCopyDataRow_Type, + SqlBulkCopyDataRow_Variant + } + + public struct TestResult + { + public object Value { get; } + public string BaseTypeName { get; } + + public TestResult(object value, string baseTypeName) + { + Value = value; + BaseTypeName = baseTypeName; + } + } + + public delegate bool ExceptionChecker(Exception e, object paramValue); + /// /// Tests for DateTime variant parameters with different date/time types. - /// These tests run independently with their own baseline comparison. /// - [Collection("ParameterBaselineTests")] + [Trait("Category", "flaky")] public class DateTimeVariantTests { - private readonly string _connStr; + private static void RunTest( + TestVariations tag, + Func action, + object paramValue, + string expectedBaseTypeName, + string connStr, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + CultureInfo previousCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + TestResult? result = null; + Exception? caughtException = null; + try + { + result = action(paramValue, expectedBaseTypeName, connStr); + } + catch (Exception e) + { + caughtException = e; + } + finally + { + Thread.CurrentThread.CurrentCulture = previousCulture; + } + + if (caughtException != null) + { + if (expectedExceptions.TryGetValue(tag, out var isExpectedException)) + { + Assert.True(isExpectedException(caughtException, paramValue), caughtException.Message); + } + else + { + Assert.Fail($"Unexpected exception was thrown for test variation {tag} with parameter value {paramValue}. Exception: {caughtException}"); + } + } + else + { + expectedValueOverrides.TryGetValue(tag, out var expectedValueOverride); + expectedBaseTypeOverrides.TryGetValue(tag, out var expectedBaseTypeOverride); - public DateTimeVariantTests() + TestResult testResult = result!.Value; + Assert.Equal(expectedValueOverride ?? paramValue, testResult.Value); + Assert.Equal(expectedBaseTypeOverride ?? expectedBaseTypeName, testResult.BaseTypeName); + } + } + + private static SqlDbType GetSqlDbType(string expectedBaseTypeName) { - _connStr = DataTestUtility.TCPConnectionString; + return expectedBaseTypeName.ToLowerInvariant() switch + { + "time" => SqlDbType.Time, + "date" => SqlDbType.Date, + "smalldatetime" => SqlDbType.SmallDateTime, + "datetime" => SqlDbType.DateTime, + "datetime2" => SqlDbType.DateTime2, + "datetimeoffset" => SqlDbType.DateTimeOffset, + _ => SqlDbType.Variant, + }; } - [Trait("Category", "flaky")] - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void DateTimeVariantParameterTest() + private static void xsql(SqlConnection conn, string sql) { - Assert.True(RunTestAndCompareWithBaseline()); + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = sql; + cmd.ExecuteNonQuery(); } - private bool RunTestAndCompareWithBaseline() + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSimpleParameter_Type( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) { - CultureInfo previousCulture = Thread.CurrentThread.CurrentCulture; - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); - try - { - string outputPath = "DateTimeVariant.out"; - string baselinePath = "DateTimeVariant.bsl"; + RunTest( + TestVariations.TestSimpleParameter_Type, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + using StoredProcedure proc = new(conn, "paramProc1", $"(@param {expectedBaseTypeName}) AS BEGIN SELECT @param, sql_variant_property(@param,'BaseType') AS BaseType END;"); + + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = proc.Name; + cmd.CommandType = CommandType.StoredProcedure; + SqlParameter p = cmd.Parameters.AddWithValue("@param", paramValue); + cmd.Parameters[0].SqlDbType = GetSqlDbType(expectedBaseTypeName); + using (SqlDataReader dr = cmd.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } - var fstream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read); - var swriter = new StreamWriter(fstream, Encoding.UTF8); - var twriter = new TvpTest.CarriageReturnLineFeedReplacer(swriter); - Console.SetOut(twriter); + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSimpleParameter_Variant( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.TestSimpleParameter_Variant, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + using StoredProcedure proc = new(conn, "paramProc2", "(@param sql_variant) AS BEGIN SELECT @param, sql_variant_property(@param,'BaseType') AS BaseType END;"); - // Run Test - DateTimeVariantTest.TestAllDateTimeWithDataTypeAndVariant(_connStr); + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = proc.Name; + cmd.CommandType = CommandType.StoredProcedure; + SqlParameter p = cmd.Parameters.AddWithValue("@param", paramValue); + cmd.Parameters[0].SqlDbType = SqlDbType.Variant; + using (SqlDataReader dr = cmd.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } - Console.Out.Flush(); - Console.Out.Dispose(); + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSqlDataRecordParameterToTVP_Type( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.TestSqlDataRecordParameterToTVP_Type, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + using UserDefinedType tvpType = new(conn, "tvpType", $"TABLE (f1 {expectedBaseTypeName})"); - // Recover the standard output stream - StreamWriter standardOutput = new(Console.OpenStandardOutput()); - standardOutput.AutoFlush = true; - Console.SetOut(standardOutput); + // Send TVP using SqlMetaData. + SqlMetaData[] metadata = new SqlMetaData[1]; + metadata[0] = new SqlMetaData("f1", GetSqlDbType(expectedBaseTypeName)); + SqlDataRecord[] record = new SqlDataRecord[1]; + record[0] = new SqlDataRecord(metadata); + record[0].SetValue(0, paramValue); - // Compare output file - var comparisonResult = FindDiffFromBaseline(baselinePath, outputPath); + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; + SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", record); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = tvpType.Name; + using (SqlDataReader dr = cmd.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } - if (string.IsNullOrEmpty(comparisonResult)) - { - return true; - } + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSqlDataRecordParameterToTVP_Variant( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.TestSqlDataRecordParameterToTVP_Variant, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + using UserDefinedType tvpType = new(conn, "tvpVariant", "TABLE (f1 sql_variant)"); - Console.WriteLine("DateTimeVariantParameterTest 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; - } - finally - { - Thread.CurrentThread.CurrentCulture = previousCulture; - } + // Send TVP using SqlMetaData. + SqlMetaData[] metadata = new SqlMetaData[1]; + metadata[0] = new SqlMetaData("f1", SqlDbType.Variant); + SqlDataRecord[] record = new SqlDataRecord[1]; + record[0] = new SqlDataRecord(metadata); + record[0].SetValue(0, paramValue); + + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; + SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", record); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = tvpType.Name; + using (SqlDataReader dr = cmd.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); } - private static string FindDiffFromBaseline(string baselinePath, string outputPath) + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSqlDataReaderParameterToTVP_Type( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) { - var expectedLines = File.ReadAllLines(baselinePath); - var outputLines = File.ReadAllLines(outputPath); + RunTest( + TestVariations.TestSqlDataReaderParameterToTVP_Type, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + using UserDefinedType tvpType = new(conn, "tvpType", $"TABLE (f1 {expectedBaseTypeName})"); + using (SqlConnection connInput = new(connStr)) + { + connInput.Open(); + using (SqlCommand cmdInput = connInput.CreateCommand()) + { + cmdInput.CommandText = "select @p1 as f1"; + cmdInput.Parameters.Add("@p1", GetSqlDbType(expectedBaseTypeName)); + cmdInput.Parameters["@p1"].Value = paramValue; + using SqlDataReader drInput = cmdInput.ExecuteReader(CommandBehavior.CloseConnection); + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; + SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", drInput); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = tvpType.Name; + using (SqlDataReader dr = cmd.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + } + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } - var comparisonSb = new StringBuilder(); + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSqlDataReaderParameterToTVP_Variant( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.TestSqlDataReaderParameterToTVP_Variant, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + using UserDefinedType tvpType = new(conn, "tvpVariant", "TABLE (f1 sql_variant)"); - var expectedLength = expectedLines.Length; - var outputLength = outputLines.Length; - var findDiffLength = Math.Min(expectedLength, outputLength); + // Send TVP using SqlDataReader. + using (SqlConnection connInput = new(connStr)) + { + connInput.Open(); + using (SqlCommand cmdInput = connInput.CreateCommand()) + { + cmdInput.CommandText = "select @p1 as f1"; + cmdInput.Parameters.Add("@p1", SqlDbType.Variant); + cmdInput.Parameters["@p1"].Value = paramValue; + using SqlDataReader drInput = cmdInput.ExecuteReader(CommandBehavior.CloseConnection); + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = "select f1, sql_variant_property(f1,'BaseType') as BaseType from @tvpParam"; + SqlParameter p = cmd.Parameters.AddWithValue("@tvpParam", drInput); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = tvpType.Name; + using (SqlDataReader dr = cmd.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + } + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } - for (var lineNo = 0; lineNo < findDiffLength; lineNo++) - { - if (!expectedLines[lineNo].Equals(outputLines[lineNo])) + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSqlDataReader_TVP_Type( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.TestSqlDataReader_TVP_Type, + (paramValue, expectedBaseTypeName, connStr) => { - comparisonSb.AppendFormat("** DIFF at line {0} \n", lineNo); - comparisonSb.AppendFormat("A : {0} \n", outputLines[lineNo]); - comparisonSb.AppendFormat("E : {0} \n", expectedLines[lineNo]); - } - } + using SqlConnection conn = new(connStr); + conn.Open(); - var startIndex = findDiffLength - 1; - if (startIndex < 0) - { - startIndex = 0; - } + using UserDefinedType tvpType = new(conn, "tvpType", $"TABLE (f1 {expectedBaseTypeName})"); + using Table inputTable = new(conn, "InputTable", $"(f1 {expectedBaseTypeName})"); + using Table outputTable = new(conn, "OutputTable", $"(f1 {expectedBaseTypeName})"); - if (findDiffLength < expectedLength) - { - comparisonSb.AppendFormat("** MISSING \n"); - for (var lineNo = startIndex; lineNo < expectedLength; lineNo++) + string value = string.Empty; + if (paramValue.GetType() == typeof(DateTimeOffset)) + { + DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; + value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; + } + else if (paramValue.GetType() == typeof(TimeSpan)) + { + value = ((TimeSpan)paramValue).ToString(); + } + else + { + value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; + } + xsql(conn, string.Format("insert into {0} values(CAST('{1}' AS {2}))", inputTable.Name, value, expectedBaseTypeName)); + using StoredProcedure proc = new(conn, "spTVPProc", $"(@P {tvpType.Name} READONLY) AS BEGIN INSERT INTO {outputTable.Name} SELECT * FROM @P; END"); + + SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format("SELECT * FROM {0}", inputTable.Name); + using SqlDataReader r = cmd.ExecuteReader(); + using (SqlConnection conn2 = new(connStr)) + { + conn2.Open(); + SqlCommand cmd2 = new(proc.Name, conn2) + { + CommandType = CommandType.StoredProcedure + }; + SqlParameter p = cmd2.Parameters.AddWithValue("@P", r); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = tvpType.Name; + cmd2.ExecuteNonQuery(); + + cmd2.CommandText = string.Format("SELECT f1, sql_variant_property(f1,'BaseType') as BaseType FROM {0}", outputTable.Name); + cmd2.CommandType = CommandType.Text; + using (SqlDataReader dr = cmd2.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSqlDataReader_TVP_Variant( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.TestSqlDataReader_TVP_Variant, + (paramValue, expectedBaseTypeName, connStr) => { - comparisonSb.AppendFormat("{0} : {1}", lineNo, expectedLines[lineNo]); - } - } - if (findDiffLength < outputLength) - { - comparisonSb.AppendFormat("** EXTRA \n"); - for (var lineNo = startIndex; lineNo < outputLength; lineNo++) + using SqlConnection conn = new(connStr); + conn.Open(); + + using UserDefinedType tvpType = new(conn, "tvpVariant_DRdrTVPVar", "TABLE (f1 sql_variant)"); + using Table inputTable = new(conn, "InputTable", "(f1 sql_variant)"); + using Table outputTable = new(conn, "OutputTable", "(f1 sql_variant)"); + + string value = string.Empty; + if (paramValue.GetType() == typeof(DateTimeOffset)) + { + DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; + value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; + } + else if (paramValue.GetType() == typeof(TimeSpan)) + { + value = ((TimeSpan)paramValue).ToString(); + } + else + { + value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; + } + xsql(conn, string.Format("insert into {0} values(CAST('{1}' AS {2}))", inputTable.Name, value, expectedBaseTypeName)); + using StoredProcedure proc = new(conn, "spTVPProc_DRdrTVPVar", $"(@P {tvpType.Name} READONLY) AS BEGIN INSERT INTO {outputTable.Name} SELECT * FROM @P; END"); + + SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format("SELECT * FROM {0}", inputTable.Name); + using SqlDataReader r = cmd.ExecuteReader(); + using (SqlConnection conn2 = new(connStr)) + { + conn2.Open(); + using (SqlCommand cmd2 = new(proc.Name, conn2)) + { + cmd2.CommandType = CommandType.StoredProcedure; + SqlParameter p = cmd2.Parameters.AddWithValue("@P", r); + p.SqlDbType = SqlDbType.Structured; + p.TypeName = tvpType.Name; + cmd2.ExecuteNonQuery(); + + cmd2.CommandText = string.Format("SELECT f1, sql_variant_property(f1,'BaseType') as BaseType FROM {0}", outputTable.Name); + cmd2.CommandType = CommandType.Text; + using (SqlDataReader dr = cmd2.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + } + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSimpleDataReader_Type( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.TestSimpleDataReader_Type, + (paramValue, expectedBaseTypeName, connStr) => { - comparisonSb.AppendFormat("{0} : {1}", lineNo, outputLines[lineNo]); - } - } + using SqlConnection conn = new(connStr); + conn.Open(); + + using Table inputTable = new(conn, "inputTable", $"(f1 {expectedBaseTypeName})"); + string value = string.Empty; + if (paramValue.GetType() == typeof(DateTimeOffset)) + { + DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; + value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; + } + else if (paramValue.GetType() == typeof(TimeSpan)) + { + value = ((TimeSpan)paramValue).ToString(); + } + else + { + value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; + } + xsql(conn, string.Format("insert into {0}(f1) values('{1}');", inputTable.Name, value)); + using StoredProcedure proc = new(conn, "paramProc3", $"AS BEGIN SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM {inputTable.Name} END;"); + + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = proc.Name; + cmd.CommandType = CommandType.StoredProcedure; + using (SqlDataReader dr = cmd.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void TestSimpleDataReader_Variant( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.TestSimpleDataReader_Variant, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + + using Table inputTable = new(conn, "inputTable", "(f1 sql_variant)"); + string value = string.Empty; + if (paramValue.GetType() == typeof(DateTimeOffset)) + { + DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; + value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; + } + else if (paramValue.GetType() == typeof(TimeSpan)) + { + value = ((TimeSpan)paramValue).ToString(); + } + else + { + value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; + } + xsql(conn, string.Format("insert into {0}(f1) values(CAST('{1}' AS {2}));", inputTable.Name, value, expectedBaseTypeName)); + using StoredProcedure proc = new(conn, "paramProc4", $"AS BEGIN SELECT f1, sql_variant_property(f1,'BaseType') AS BaseType FROM {inputTable.Name} END;"); + + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = proc.Name; + cmd.CommandType = CommandType.StoredProcedure; + using (SqlDataReader dr = cmd.ExecuteReader()) + { + dr.Read(); + return new TestResult(dr[0], dr.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void SqlBulkCopySqlDataReader_Type( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.SqlBulkCopySqlDataReader_Type, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + + using Table bulkCopyDestTable = new(conn, "bulkDestTable", $"(f1 {expectedBaseTypeName})"); + using Table bulkCopySrcTable = new(conn, "bulkSrcTable", $"(f1 {expectedBaseTypeName})"); + string value = string.Empty; + if (paramValue.GetType() == typeof(DateTimeOffset)) + { + DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; + value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; + } + else if (paramValue.GetType() == typeof(TimeSpan)) + { + value = ((TimeSpan)paramValue).ToString(); + } + else + { + value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; + } + xsql(conn, string.Format("insert into {0}(f1) values(CAST('{1}' AS {2}));", bulkCopySrcTable.Name, value, expectedBaseTypeName)); + + using SqlConnection connInput = new(connStr); + connInput.Open(); + using (SqlCommand cmdInput = connInput.CreateCommand()) + { + cmdInput.CommandText = string.Format("select * from {0}", bulkCopySrcTable.Name); + using SqlDataReader drInput = cmdInput.ExecuteReader(); + // Perform bulk copy to target. + using (SqlBulkCopy bulkCopy = new(conn)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyDestTable.Name; + bulkCopy.WriteToServer(drInput); + } - return comparisonSb.ToString(); + // Verify target. + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyDestTable.Name); + using (SqlDataReader drVerify = cmd.ExecuteReader()) + { + drVerify.Read(); + return new TestResult(drVerify[0], drVerify.GetString(1)); + } + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void SqlBulkCopySqlDataReader_Variant( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.SqlBulkCopySqlDataReader_Variant, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + + using Table bulkCopyDestTable = new(conn, "bulkDestTable", "(f1 sql_variant)"); + using Table bulkCopySrcTable = new(conn, "bulkSrcTable", $"(f1 {expectedBaseTypeName})"); + string value = string.Empty; + if (paramValue.GetType() == typeof(DateTimeOffset)) + { + DateTime dt = ((DateTimeOffset)paramValue).UtcDateTime; + value = dt.ToString("M/d/yyyy") + " " + dt.TimeOfDay; + } + else if (paramValue.GetType() == typeof(TimeSpan)) + { + value = ((TimeSpan)paramValue).ToString(); + } + else + { + value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; + } + xsql(conn, string.Format("insert into {0}(f1) values(CAST('{1}' AS {2}));", bulkCopySrcTable.Name, value, expectedBaseTypeName)); + + using (SqlConnection connInput = new(connStr)) + { + connInput.Open(); + using (SqlCommand cmdInput = connInput.CreateCommand()) + { + cmdInput.CommandText = string.Format("select * from {0}", bulkCopySrcTable.Name); + using SqlDataReader drInput = cmdInput.ExecuteReader(); + { + // Perform bulk copy to target. + using (SqlBulkCopy bulkCopy = new(conn)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyDestTable.Name; + bulkCopy.WriteToServer(drInput); + } + + // Verify target. + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyDestTable.Name); + using (SqlDataReader drVerify = cmd.ExecuteReader()) + { + drVerify.Read(); + return new TestResult(drVerify[0], drVerify.GetString(1)); + } + } + } + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void SqlBulkCopyDataTable_Type( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.SqlBulkCopyDataTable_Type, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + + using Table bulkCopyTable = new(conn, "bulkDestType", $"(f1 {expectedBaseTypeName})"); + + // Send using DataTable as source. + DataTable t = new(); + t.Columns.Add("f1", paramValue.GetType()); + t.Rows.Add(new object[] { paramValue }); + + // Perform bulk copy to target. + using (SqlBulkCopy bulkCopy = new(conn)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyTable.Name; + bulkCopy.WriteToServer(t, DataRowState.Added); + } + + // Verify target. + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTable.Name); + using (SqlDataReader drVerify = cmd.ExecuteReader()) + { + drVerify.Read(); + return new TestResult(drVerify[0], drVerify.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void SqlBulkCopyDataTable_Variant( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.SqlBulkCopyDataTable_Variant, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + + using Table bulkCopyTable = new(conn, "bulkDestVariant", "(f1 sql_variant)"); + + // Send using DataTable as source. + DataTable t = new(); + t.Columns.Add("f1", typeof(object)); + t.Rows.Add(new object[] { paramValue }); + + // Perform bulk copy to target. + using (SqlBulkCopy bulkCopy = new(conn)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyTable.Name; + bulkCopy.WriteToServer(t, DataRowState.Added); + } + + // Verify target. + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTable.Name); + using (SqlDataReader drVerify = cmd.ExecuteReader()) + { + drVerify.Read(); + return new TestResult(drVerify[0], drVerify.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void SqlBulkCopyDataRow_Type( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.SqlBulkCopyDataRow_Type, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + + using Table bulkCopyTable = new(conn, "bulkDestType", $"(f1 {expectedBaseTypeName})"); + DataTable t = new(); + t.Columns.Add("f1", paramValue.GetType()); + t.Rows.Add(new object[] { paramValue }); + DataRow[] rowToSend = t.Select(); + using (SqlBulkCopy bulkCopy = new(conn)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyTable.Name; + bulkCopy.WriteToServer(rowToSend); + } + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTable.Name); + using (SqlDataReader drVerify = cmd.ExecuteReader()) + { + drVerify.Read(); + return new TestResult(drVerify[0], drVerify.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] + public void SqlBulkCopyDataRow_Variant( + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, + Dictionary expectedValueOverrides, + Dictionary expectedBaseTypeOverrides) + { + RunTest( + TestVariations.SqlBulkCopyDataRow_Variant, + (paramValue, expectedBaseTypeName, connStr) => + { + using SqlConnection conn = new(connStr); + conn.Open(); + + using Table bulkCopyTable = new(conn, "bulkDestVariant", "(f1 sql_variant)"); + DataTable t = new(); + t.Columns.Add("f1", typeof(object)); + t.Rows.Add(new object[] { paramValue }); + DataRow[] rowToSend = t.Select(); + using (SqlBulkCopy bulkCopy = new(conn)) + { + bulkCopy.BulkCopyTimeout = 60; + bulkCopy.BatchSize = 1; + bulkCopy.DestinationTableName = bulkCopyTable.Name; + bulkCopy.WriteToServer(rowToSend); + } + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = string.Format("select f1, sql_variant_property(f1,'BaseType') as BaseType from {0}", bulkCopyTable.Name); + using (SqlDataReader drVerify = cmd.ExecuteReader()) + { + drVerify.Read(); + return new TestResult(drVerify[0], drVerify.GetString(1)); + } + }, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, + expectedValueOverrides, + expectedBaseTypeOverrides); + } + + /// + /// Gets parameter combinations for MemberData. Each entry yields the full parameter value, + /// expected base type name, expected exceptions, expected value overrides, and expected base + /// type overrides. is set to + /// avoid xUnit serialization issues with the complex types used in each row. + /// + public static IEnumerable GetParameterCombinations() + { + yield return new object[] { DateTime.MinValue, "date", + new Dictionary { + { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary(), + new Dictionary()}; + yield return new object[] { DateTime.MaxValue, "date", + new Dictionary(), + new Dictionary + { + { TestVariations.TestSimpleParameter_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSimpleParameter_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataReader_TVP_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataReader_TVP_Variant, new DateTime(3155378112000000000) }, + { TestVariations.TestSimpleDataReader_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSimpleDataReader_Variant, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopySqlDataReader_Type, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopyDataTable_Type, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopyDataTable_Variant, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopyDataRow_Type, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopyDataRow_Variant, new DateTime(3155378975999970000) } + }, + new Dictionary + { + {TestVariations.TestSimpleParameter_Variant, "datetime"}, + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, "datetime"}, + {TestVariations.TestSqlDataReaderParameterToTVP_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataTable_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataRow_Variant, "datetime"} + } + }; + yield return new object[] { DateTime.MinValue, "datetime2", + new Dictionary { + { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary(), + new Dictionary()}; + yield return new object[] { DateTime.MaxValue, "datetime2", + new Dictionary(), + new Dictionary { + {TestVariations.TestSimpleParameter_Variant, new DateTime(3155378975999970000)}, + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, new DateTime(3155378975999970000)}, + {TestVariations.TestSqlDataReaderParameterToTVP_Variant, new DateTime(3155378975999970000)}, + {TestVariations.SqlBulkCopyDataTable_Variant, new DateTime(3155378975999970000)}, + {TestVariations.SqlBulkCopyDataRow_Variant, new DateTime(3155378975999970000)} + }, + new Dictionary + { + {TestVariations.TestSimpleParameter_Variant, "datetime"}, + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, "datetime"}, + {TestVariations.TestSqlDataReaderParameterToTVP_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataTable_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataRow_Variant, "datetime"} + }}; + yield return new object[] { DateTime.MinValue, "datetime", + new Dictionary { + { TestVariations.TestSimpleParameter_Type, SqlDateTimeOverflow }, + { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReader_TVP_Type, VarcharToDateTimeOutOfRange}, + { TestVariations.TestSqlDataReader_TVP_Variant, VarcharToDateTimeOutOfRange}, + { TestVariations.TestSimpleDataReader_Type, VarcharToDateTimeOutOfRange}, + { TestVariations.TestSimpleDataReader_Variant, VarcharToDateTimeOutOfRange}, + { TestVariations.SqlBulkCopySqlDataReader_Type, VarcharToDateTimeOutOfRange}, + { TestVariations.SqlBulkCopySqlDataReader_Variant, VarcharToDateTimeOutOfRange}, + { TestVariations.SqlBulkCopyDataTable_Type, SqlDateTimeOverflow}, + { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow}, + { TestVariations.SqlBulkCopyDataRow_Type, SqlDateTimeOverflow}, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow}}, + new Dictionary(), + new Dictionary()}; + yield return new object[] { DateTime.MaxValue, "datetime", + new Dictionary { + { TestVariations.TestSqlDataReader_TVP_Type, CannotConvertCharacterStringToDateOrTime}, + { TestVariations.TestSqlDataReader_TVP_Variant, CannotConvertCharacterStringToDateOrTime}, + { TestVariations.TestSimpleDataReader_Type, CannotConvertCharacterStringToDateOrTime}, + { TestVariations.TestSimpleDataReader_Variant, CannotConvertCharacterStringToDateOrTime}, + { TestVariations.SqlBulkCopySqlDataReader_Type, CannotConvertCharacterStringToDateOrTime}, + { TestVariations.SqlBulkCopySqlDataReader_Variant, CannotConvertCharacterStringToDateOrTime}}, + new Dictionary + { + { TestVariations.TestSimpleParameter_Type, new DateTime(3155378975999970000) }, + { TestVariations.TestSimpleParameter_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataReader_TVP_Type, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataReader_TVP_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSimpleDataReader_Type, new DateTime(3155378975999970000) }, + { TestVariations.TestSimpleDataReader_Variant, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopySqlDataReader_Type, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopyDataTable_Type, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopyDataTable_Variant, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopyDataRow_Type, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopyDataRow_Variant, new DateTime(3155378975999970000) } + }, + new Dictionary()}; + yield return new object[] { DateTimeOffset.MinValue, "datetimeoffset", + new Dictionary(), + new Dictionary(), + new Dictionary()}; + yield return new object[] { DateTimeOffset.MaxValue, "datetimeoffset", + new Dictionary(), + new Dictionary(), + new Dictionary()}; + yield return new object[] { DateTimeOffset.Parse("12/31/1999 23:59:59.9999999 -08:30"), "datetimeoffset", + new Dictionary(), + new Dictionary(), + new Dictionary()}; + yield return new object[] { DateTime.Parse("1998-01-01 23:59:59.995"), "datetime2", + new Dictionary(), + new Dictionary + { + {TestVariations.TestSimpleParameter_Variant, DateTime.Parse("1998-01-01 23:59:59.997")}, + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, DateTime.Parse("1998-01-01 23:59:59.997")}, + {TestVariations.TestSqlDataReaderParameterToTVP_Variant, DateTime.Parse("1998-01-01 23:59:59.997")}, + {TestVariations.SqlBulkCopyDataTable_Variant, DateTime.Parse("1998-01-01 23:59:59.997")}, + {TestVariations.SqlBulkCopyDataRow_Variant, DateTime.Parse("1998-01-01 23:59:59.997")} + }, + new Dictionary + { + {TestVariations.TestSimpleParameter_Variant, "datetime"}, + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, "datetime"}, + {TestVariations.TestSqlDataReaderParameterToTVP_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataTable_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataRow_Variant, "datetime"} + }}; + yield return new object[] { DateTime.MinValue, "smalldatetime", + new Dictionary { + { TestVariations.TestSimpleParameter_Type, SqlDateTimeOverflow }, + { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, InvalidValueForMetadata }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReader_TVP_Type, VarcharToSmallDateTimeOutOfRange }, + { TestVariations.TestSqlDataReader_TVP_Variant, VarcharToSmallDateTimeOutOfRange }, + { TestVariations.TestSimpleDataReader_Type, VarcharToSmallDateTimeOutOfRange }, + { TestVariations.TestSimpleDataReader_Variant, VarcharToSmallDateTimeOutOfRange }, + { TestVariations.SqlBulkCopySqlDataReader_Type, VarcharToSmallDateTimeOutOfRange }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, VarcharToSmallDateTimeOutOfRange }, + { TestVariations.SqlBulkCopyDataTable_Type, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Type, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary(), + new Dictionary()}; + yield return new object[] { DateTime.MaxValue, "smalldatetime", + new Dictionary { + { TestVariations.TestSimpleParameter_Type, UnRepresentableDateTime }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, InvalidValueForMetadata }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, UnRepresentableDateTime }, + { TestVariations.TestSqlDataReader_TVP_Type, ConversionFailedCharStringToSmallDateTime }, + { TestVariations.TestSqlDataReader_TVP_Variant, ConversionFailedCharStringToSmallDateTime }, + { TestVariations.TestSimpleDataReader_Type, ConversionFailedCharStringToSmallDateTime }, + { TestVariations.TestSimpleDataReader_Variant, ConversionFailedCharStringToSmallDateTime }, + { TestVariations.SqlBulkCopySqlDataReader_Type, ConversionFailedCharStringToSmallDateTime }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, ConversionFailedCharStringToSmallDateTime }, + { TestVariations.SqlBulkCopyDataTable_Type, UnRepresentableDateTime }, + { TestVariations.SqlBulkCopyDataRow_Type, UnRepresentableDateTime }}, + new Dictionary { + { TestVariations.TestSimpleParameter_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, new DateTime(3155378975999970000) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopyDataTable_Variant, new DateTime(3155378975999970000) }, + { TestVariations.SqlBulkCopyDataRow_Variant, new DateTime(3155378975999970000) } + }, + new Dictionary + { + {TestVariations.TestSimpleParameter_Variant, "datetime"}, + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, "datetime"}, + {TestVariations.TestSqlDataReaderParameterToTVP_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataTable_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataRow_Variant, "datetime"} + }}; + yield return new object[] { TimeSpan.MinValue, "time", + new Dictionary { + { TestVariations.TestSimpleParameter_Type, TimeOverflow }, + { TestVariations.TestSimpleParameter_Variant, TimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, InvalidValueForMetadata }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, TimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, TimeOverflow }, + { TestVariations.TestSqlDataReader_TVP_Type, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.TestSqlDataReader_TVP_Variant, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.TestSimpleDataReader_Type, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.TestSimpleDataReader_Variant, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.SqlBulkCopySqlDataReader_Type, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.SqlBulkCopyDataTable_Type, TimeOverflow }, + { TestVariations.SqlBulkCopyDataTable_Variant, TimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Type, TimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Variant, TimeOverflow }}, + new Dictionary { + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, TimeSpan.Zero}, + }, + new Dictionary()}; + yield return new object[] { TimeSpan.MaxValue, "time", + new Dictionary { + { TestVariations.TestSimpleParameter_Type, TimeOverflow }, + { TestVariations.TestSimpleParameter_Variant, TimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, InvalidValueForMetadata }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, TdsRpcProtocolStreamIncorrect }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, TimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, TimeOverflow }, + { TestVariations.TestSqlDataReader_TVP_Type, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.TestSqlDataReader_TVP_Variant, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.TestSimpleDataReader_Type, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.TestSimpleDataReader_Variant, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.SqlBulkCopySqlDataReader_Type, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, CannotConvertCharacterStringToDateOrTime }, + { TestVariations.SqlBulkCopyDataTable_Type, TimeOverflow }, + { TestVariations.SqlBulkCopyDataTable_Variant, TimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Type, TimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Variant, TimeOverflow }}, + new Dictionary(), + new Dictionary()}; + yield return new object[] { DateTime.MinValue, "time", + new Dictionary { + { TestVariations.SqlBulkCopyDataTable_Type, CannotConvertMinDateTimeToTime}, + { TestVariations.SqlBulkCopyDataRow_Type, CannotConvertMinDateTimeToTime}, + { TestVariations.TestSimpleParameter_Type, InvalidCastDateTimeToTimeSpan }, + { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, InvalidCastNotValid }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, InvalidCastDateTimeToTimeSpan }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReader_TVP_Type, InvalidCastNotValid }, + { TestVariations.TestSqlDataReader_TVP_Variant, InvalidCastNotValid }, + { TestVariations.TestSimpleDataReader_Type, InvalidCastNotValid }, + { TestVariations.TestSimpleDataReader_Variant, InvalidCastNotValid }, + { TestVariations.SqlBulkCopySqlDataReader_Type, InvalidCastNotValid }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, InvalidCastNotValid }, + { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary { + {TestVariations.SqlBulkCopySqlDataReader_Type, TimeSpan.Zero}, + {TestVariations.SqlBulkCopySqlDataReader_Variant, TimeSpan.Zero}, + {TestVariations.TestSqlDataReader_TVP_Type, TimeSpan.Zero}, + {TestVariations.TestSqlDataReader_TVP_Variant, TimeSpan.Zero}, + {TestVariations.TestSimpleDataReader_Type, TimeSpan.Zero}, + {TestVariations.TestSimpleDataReader_Variant, TimeSpan.Zero}, + }, + new Dictionary()}; + yield return new object[] { DateTime.MaxValue, "time", + new Dictionary { + { TestVariations.SqlBulkCopyDataTable_Type, CannotConvertMaxDateTimeToTime }, + { TestVariations.SqlBulkCopyDataRow_Type, CannotConvertMaxDateTimeToTime }, + { TestVariations.TestSimpleParameter_Type, InvalidCastDateTimeToTimeSpan }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, InvalidCastNotValid }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, InvalidCastDateTimeToTimeSpan }, + { TestVariations.TestSqlDataReader_TVP_Type, InvalidCastNotValid }, + { TestVariations.TestSqlDataReader_TVP_Variant, InvalidCastNotValid }, + { TestVariations.TestSimpleDataReader_Type, InvalidCastNotValid }, + { TestVariations.TestSimpleDataReader_Variant, InvalidCastNotValid }, + { TestVariations.SqlBulkCopySqlDataReader_Type, InvalidCastNotValid }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, InvalidCastNotValid }}, + new Dictionary { + {TestVariations.TestSqlDataReader_TVP_Variant, TimeSpan.Parse("23:59:59.9999999")}, + {TestVariations.SqlBulkCopySqlDataReader_Type, TimeSpan.Parse("23:59:59.9999999")}, + {TestVariations.SqlBulkCopySqlDataReader_Variant, TimeSpan.Parse("23:59:59.9999999")}, + {TestVariations.TestSqlDataReader_TVP_Type, TimeSpan.Parse("23:59:59.9999999")}, + {TestVariations.TestSimpleDataReader_Type, TimeSpan.Parse("23:59:59.9999999")}, + {TestVariations.TestSimpleDataReader_Variant, TimeSpan.Parse("23:59:59.9999999")}, + {TestVariations.TestSimpleParameter_Variant, new DateTime(3155378975999970000)}, + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, new DateTime(3155378975999970000)}, + {TestVariations.TestSqlDataReaderParameterToTVP_Variant, new DateTime(3155378975999970000)}, + {TestVariations.SqlBulkCopyDataTable_Variant, new DateTime(3155378975999970000)}, + {TestVariations.SqlBulkCopyDataRow_Variant, new DateTime(3155378975999970000)} + }, + new Dictionary + { + {TestVariations.TestSimpleParameter_Variant, "datetime"}, + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, "datetime"}, + {TestVariations.TestSqlDataReaderParameterToTVP_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataTable_Variant, "datetime"}, + {TestVariations.SqlBulkCopyDataRow_Variant, "datetime"} + }}; + } + + private static ExceptionChecker SqlDateTimeOverflow = (e, paramValue) => + (e.GetType() == typeof(System.Data.SqlTypes.SqlTypeException)) && + e.Message.Contains("SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM."); + + private static ExceptionChecker VarcharToDateTimeOutOfRange = (e, paramValue) => + (e.GetType() == typeof(SqlException)) && + e.Message.Contains("The conversion of a varchar data type to a datetime data type resulted in an out-of-range value."); + + private static ExceptionChecker CannotConvertMinDateTimeToTime = (e, paramValue) => + (e.GetType() == typeof(InvalidOperationException)) && + e.Message.Contains("The given value '1/1/0001 12:00:00 AM' of type DateTime from the data source cannot be converted to type time for Column 0 [f1] Row 1."); + + private static ExceptionChecker CannotConvertMaxDateTimeToTime = (e, paramValue) => + (e.GetType() == typeof(InvalidOperationException)) && + e.Message.Contains("The given value '12/31/9999 11:59:59 PM' of type DateTime from the data source cannot be converted to type time for Column 0 [f1] Row 1."); + + private static ExceptionChecker CannotConvertCharacterStringToDateOrTime = (e, paramValue) => + (e.GetType() == typeof(SqlException)) && + e.Message.Contains("Conversion failed when converting date and/or time from character string."); + + private static ExceptionChecker InvalidValueForMetadata = (e, paramValue) => + (e.GetType() == typeof(ArgumentException)) && + e.Message.Contains("Invalid value for this metadata."); + + private static ExceptionChecker VarcharToSmallDateTimeOutOfRange = (e, paramValue) => + (e.GetType() == typeof(SqlException)) && + e.Message.Contains("The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value."); + + private static ExceptionChecker ConversionFailedCharStringToSmallDateTime = (e, paramValue) => + (e.GetType() == typeof(SqlException)) && + e.Message.Contains("Conversion failed when converting character string to smalldatetime data type."); + + private static ExceptionChecker UnRepresentableDateTime = (e, paramValue) => + (e.GetType() == typeof(ArgumentOutOfRangeException)) && + e.Message.Contains("The added or subtracted value results in an un-representable DateTime."); + + private static ExceptionChecker TimeOverflow = (e, paramValue) => + (e.GetType() == typeof(OverflowException)) && + e.Message.Contains("SqlDbType.Time overflow."); + + private static ExceptionChecker InvalidCastDateTimeToTimeSpan = (e, paramValue) => + (e.GetType() == typeof(InvalidCastException)) && + e.Message.Contains("Failed to convert parameter value from a DateTime to a TimeSpan."); + + private static ExceptionChecker InvalidCastNotValid = (e, paramValue) => + (e.GetType() == typeof(InvalidCastException)) && + e.Message.Contains("Specified cast is not valid."); + + private static ExceptionChecker TdsRpcProtocolStreamIncorrect = (e, paramValue) => + (e.GetType() == typeof(SqlException)) && + e.Message.Contains("The incoming tabular data stream (TDS) remote procedure call (RPC) protocol stream is incorrect."); + } } From 7a896ef40d3a1b74e8b8f498f9ac940e84a92f24 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 13 May 2026 15:26:42 -0300 Subject: [PATCH 098/298] Use dotnet CLI exclusively (no MSBuild) (#4168) --- .github/copilot-instructions.md | 4 +- .../ado-pipelines.instructions.md | 2 +- .../instructions/architecture.instructions.md | 2 +- .github/instructions/testing.instructions.md | 23 ++- .../apicompat-ref-assembly-validation.md | 12 +- BUILDGUIDE.md | 127 ++++++++++------ TESTGUIDE.md | 62 +++++--- build.proj | 2 +- doc/apps/AzureAuthentication/README.md | 2 +- .../templates/jobs/ci-build-nugets-job.yml | 32 ++-- .../templates/jobs/ci-run-tests-job.yml | 1 - .../templates/steps/ci-project-build-step.yml | 32 ++-- .../templates/steps/run-all-tests-step.yml | 143 +++++++----------- .../jobs/test-abstractions-package-ci-job.yml | 2 +- .../jobs/test-azure-package-ci-job.yml | 2 +- .../onebranch/steps/build-buildproj-step.yml | 9 +- .../onebranch/steps/pack-buildproj-step.yml | 9 +- .../steps/roslyn-analyzers-buildproj-step.yml | 5 +- eng/pipelines/stress/stress-tests-job.yml | 16 +- tools/targets/CompareMdsRefAssemblies.targets | 8 +- 20 files changed, 266 insertions(+), 229 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 51ce83bb10..e024b30d6f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -34,7 +34,7 @@ This project includes several key products and libraries that facilitate SQL Ser ## 🛠️ 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 Entra ID 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: +- **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. @@ -123,7 +123,7 @@ When a new issue is created, follow these steps: - Ensure the PR passes all CI checks before merging. ### ✅ Closing Issues -- Add a comment summarizing the fix and referencing the PR +- Add a comment summarizing the fix and referencing the PR ### ⚙️ Automating Workflows - Auto-label PRs based on folder paths (e.g., changes in `src/Microsoft.Data.SqlClient/src/` → `Area\SqlClient`, changes in `tests/` → `Area\Testing`) and whether they add new public APIs or introduce a breaking change. diff --git a/.github/instructions/ado-pipelines.instructions.md b/.github/instructions/ado-pipelines.instructions.md index 5dfd35a12c..9260a78b3d 100644 --- a/.github/instructions/ado-pipelines.instructions.md +++ b/.github/instructions/ado-pipelines.instructions.md @@ -49,7 +49,7 @@ Key parameters: - `runAlwaysEncryptedTests` — include AE test set; default `true` - `runLegacySqlTests` — include SQL Server 2016/2017 manual-test legs; default `true` - `debug` — enable debug output; default `false` -- `dotnetVerbosity` — MSBuild verbosity; default `normal` +- `dotnetVerbosity` — build verbosity; default `normal` ## Build Stage Order diff --git a/.github/instructions/architecture.instructions.md b/.github/instructions/architecture.instructions.md index e1578592fe..2fd30c9f55 100644 --- a/.github/instructions/architecture.instructions.md +++ b/.github/instructions/architecture.instructions.md @@ -49,7 +49,7 @@ The `netcore/` and `netfx/` directories are legacy artifacts from the old dual-p - `netcore/ref/` and `netfx/ref/` — **STILL ACTIVE**. Reference assemblies remain in these directories and define the public API surface for each target framework. ### OS Targeting with `TargetOs` -The unified project uses a `TargetOs` MSBuild property to handle OS-specific compilation: +The unified project uses a `TargetOs` build property to handle OS-specific compilation: ```xml diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index 761c056399..20fe03b1e8 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -115,7 +115,7 @@ The default test filter is defined in `build.proj` via `TestFilters`: ```xml category!=failing&category!=flaky&category!=interactive ``` -This can be overridden via MSBuild property: `msbuild build.proj -t:TestSqlClientUnit -p:TestFilters="your_filter"`. +This can be overridden via build property: `dotnet build build.proj -t:TestSqlClientUnit -p:TestFilters="your_filter"`. ### Test Attributes ```csharp @@ -139,19 +139,19 @@ public void TestIntermittentlyFails() { ... } ## Running Tests -### Using MSBuild (Recommended) +### Using `build.proj` targets (Recommended) ```bash # Build and run all unit tests -msbuild build.proj -t:TestSqlClientUnit +dotnet build build.proj -t:TestSqlClientUnit # Run functional tests only -msbuild build.proj -t:TestSqlClientFunctional +dotnet build build.proj -t:TestSqlClientFunctional # Run manual tests for specific framework -msbuild build.proj -t:TestSqlClientManual -p:TestFramework=net8.0 +dotnet build build.proj -t:TestSqlClientManual -p:TestFramework=net8.0 # Run specific test set -msbuild build.proj -t:TestSqlClientManual -p:TestSet=1 +dotnet build build.proj -t:TestSqlClientManual -p:TestSet=1 ``` ### Using dotnet CLI @@ -357,7 +357,7 @@ cmd.CommandType = CommandType.StoredProcedure; ### Running with Coverage ```bash -msbuild build.proj -t:TestSqlClientUnit -p:TestCodeCoverage=true +dotnet build build.proj -t:TestSqlClientUnit -p:TestCodeCoverage=true ``` ### Coverage Targets @@ -367,16 +367,11 @@ msbuild build.proj -t:TestSqlClientUnit -p:TestCodeCoverage=true ## Debugging Tests -### Visual Studio +### IDE 1. Set breakpoints in test code -2. Right-click test → Debug Test +2. Right-click test → Debug Test (or use CodeLens "Debug Test" link) 3. Use Test Explorer for navigation -### VS Code -1. Configure C# extension -2. Use CodeLens "Debug Test" link -3. Attach to test process - ### Command Line ```bash # Enable verbose output diff --git a/.github/plans/apicompat-ref-assembly-validation.md b/.github/plans/apicompat-ref-assembly-validation.md index b968dbf678..3ae4f0a46d 100644 --- a/.github/plans/apicompat-ref-assembly-validation.md +++ b/.github/plans/apicompat-ref-assembly-validation.md @@ -9,7 +9,7 @@ The comparison uses `Microsoft.DotNet.ApiCompat.Tool` in **strict mode**, which ## Usage ``` -dotnet msbuild build.proj /t:CompareRefAssemblies /p:BaselinePackageVersion=6.1.4 +dotnet build build.proj /t:CompareMdsRefAssemblies /p:BaselinePackageVersion=6.1.4 ``` - `BaselinePackageVersion` is **required** (no default). The user must specify which published package to compare against. @@ -22,7 +22,7 @@ dotnet msbuild build.proj /t:CompareRefAssemblies /p:BaselinePackageVersion=6.1. Add an entry for version `9.0.200` alongside the existing `dotnet-coverage` entry. This enables `dotnet apicompat` after `dotnet tool restore`. -### 2. Create `tools/targets/CompareRefAssemblies.targets` +### 2. Create `tools/targets/CompareMdsRefAssemblies.targets` A single new file containing all properties, items, and targets (steps 3–11 below). Follows the naming convention of existing files like `GenerateMdsPackage.targets`. @@ -85,7 +85,7 @@ Runs ``. - Preceded by `` labelling each comparison - Uses item metadata to map `net462` to `$(LegacyNetFxRefDir)` and others to `$(LegacyNetCoreRefDir)` -### 11. `CompareRefAssemblies` target (public entry point) +### 11. `CompareMdsRefAssemblies` target (public entry point) - Declared with `DependsOnTargets="_RunRefApiCompat"` - Emits a final `` summarizing completion @@ -95,12 +95,12 @@ Runs ``. Add one line after the existing `.targets` imports (after line 7): ```xml - + ``` ## Design Decisions -- **Single new file** at `tools/targets/CompareRefAssemblies.targets` — only one `` line added to `build.proj`. +- **Single new file** at `tools/targets/CompareMdsRefAssemblies.targets` — only one `` line added to `build.proj`. - **Internal targets prefixed with `_`** to signal they're not intended to be called directly. - **Strict mode** ensures API additions are also flagged — important for detecting accidental public surface changes during file reorganization. - **`ContinueOnError="ErrorAndContinue"`** on each apicompat `Exec` so all 8 comparisons run and all differences are reported together. @@ -111,7 +111,7 @@ Add one line after the existing `.targets` imports (after line 7): ## Verification ``` -dotnet msbuild build.proj /t:CompareRefAssemblies /p:BaselinePackageVersion=6.1.4 +dotnet build build.proj /t:CompareMdsRefAssemblies /p:BaselinePackageVersion=6.1.4 ``` - Downloads 6.1.4 nupkg, builds both ref project variants, runs 8 comparisons (4 TFMs × 2 variants). diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 16aa709fe5..a927945c05 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -1,3 +1,5 @@ + + # Build Guide for Microsoft.Data.SqlClient and Related Packages This document provides details on how to build the Microsoft.Data.SqlClient package and the other related packages @@ -6,9 +8,10 @@ contained within this repository. ## Prerequisites ### .NET SDK + Projects in this repository require the .NET SDK to be installed in order to build. For the exact version required for -building the current version, see [global.json](global.json). Downloads for .NET SDK can be found at: -https://dotnet.microsoft.com/en-us/download/dotnet +building the current version, see [global.json](global.json). Downloads for .NET SDK can be found at +[.NET Downloads](https://dotnet.microsoft.com/en-us/download/dotnet). The .NET SDK contains support for building for previous versions of .NET, including support for building .NET Framework on operating systems that do not support .NET Framework. As such, it is not necessary to install any version of the @@ -16,9 +19,11 @@ on operating systems that do not support .NET Framework. As such, it is not nece ### Miscellaneous -**PowerShell** is required to run several miscellaneous tasks as part of building and packaging. On Windows systems, -no action is required. On Linux and macOS systems, the `pwsh` command is required to be in the `$PATH` environment -variable. For specific instructions see: [Install PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell) +**PowerShell** is required to run several miscellaneous tasks as part of building and packaging. On +Windows systems, either the built-in `powershell.exe` will be used, or if installed, the modern +`pwsh` will be used. On Linux and macOS systems, the `pwsh` command is required to be in the `$PATH` +environment variable. For specific instructions see: [Install +PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell) The **NuGet** binary is optional for inspection and feed-management workflows, but build and packaging flows in this repository are run through `dotnet build` against `build.proj`. @@ -33,6 +38,8 @@ package the project. The `build.proj` file provides convenient targets to accomp > may be noticed, possibly severe. All official build and test infrastructure uses the `build.proj` entrypoint, and it > is recommended that `build.proj` is used for local development, as well. + + > [!TIP] > This section is not exhaustive of all targets or parameters to `build.proj`. Complete documentation is available in > [`build.proj`](build.proj). @@ -45,11 +52,21 @@ From the root of your repository, run `dotnet build` against `build.proj` with a dotnet build build.proj -t: [optional_parameters] ``` +Since `build.proj` is the only project file in the repo root, it can be omitted when building from +the root: + +```bash +dotnet build -t: [optional_parameters] +``` + +The command-line examples below will assume that `build.proj` is selected by default and will omit +it from the `dotnet build` command. + The following build targets can be used to build the following projects. All targets will implicitly build any other projects they depend on. | `` | Description | -|:----------------------------|:--------------------------------------------------------------------------------| +|-----------------------------|---------------------------------------------------------------------------------| | `Build` | Builds all projects for all platforms | | `BuildAbstractions` | Builds Microsoft.Data.SqlClient.Extensions.Abstractions | | `BuildAkvProvider` | Builds Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider | @@ -64,11 +81,15 @@ projects they depend on. A selection of parameters for build targets in `build.proj` can be found below: + + | `[optional_parameter]` | Allowed Values | Default | Description | |-----------------------------------|----------------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------| | `-p:Configuration=` | `Debug`, `Release` | `Debug` | Build configuration | | `-p:PackageVersion=` | `major.minor.patch[-prerelease]` | `[blank]` | Version to assign to the target package, where `` can be one of: `['Abstractions', 'Azure', 'AkvProvider', 'Logging', 'SqlClient', 'SqlServer']`. Assembly and file versions are derived from this, if it is provided. See Versioning for more details | + + For most projects, build output is placed in `artifacts//Project-/`. `` is the full name of the package, `` is the build configuration, and `` is the target framework moniker. SqlClient deviates slightly from this convention, since it consists of multiple projects and the @@ -82,19 +103,19 @@ placed in `artifacts/Microsoft.Data.SqlClient.ref/Project-/` Build all projects: ```bash -dotnet build build.proj -t:Build +dotnet build -t:Build ``` Build Microsoft.Data.SqlClient in Release configuration: ```bash -dotnet build build.proj -t:BuildSqlClient -p:Configuration=Release +dotnet build -t:BuildSqlClient -p:Configuration=Release ``` Build v1.2.3 of Microsoft.Data.SqlClient.Extensions.Abstractions: ```bash -dotnet build build.proj -t:BuildAbstractions -p:PackageVersionAbstractions=1.2.3 +dotnet build -t:BuildAbstractions -p:PackageVersionAbstractions=1.2.3 ``` ### Testing Projects @@ -105,7 +126,7 @@ information about test procedures, including config file setup, see [TESTGUIDE.m From the root of your repository, run `dotnet build` against `build.proj` with a test target, following this pattern: ```bash -dotnet build build.proj -t: [optional_parameters] +dotnet build -t: [optional_parameters] ``` | `` | Description | @@ -123,6 +144,8 @@ dotnet build build.proj -t: [optional_parameters] A selection of parameters for test targets in `build.proj` relevant to common developer workflows can be found below: + + | `[optional_parameter]` | Default Value | Description | |------------------------|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `-p:Configuration=` | `Debug` | Build configuration. Can be `Debug` or `Release`. | @@ -132,32 +155,38 @@ A selection of parameters for test targets in `build.proj` relevant to common de | `-p:TestFramework=` | `[blank]` | Target framework moniker for the version of .NET to use to execute tests. | | `-p:TestSet=` | `[blank]` | The `TestSqlClientManual` project is very large and is split into multiple sets that can be executed individually. This parameter allows selecting between test sets: `1`, `2`, `3`, and `AE`. | + + #### Examples Run Microsoft.Data.SqlClient unit tests: ```bash -dotnet build build.proj -t:TestSqlClientUnit +dotnet build -t:TestSqlClientUnit ``` Run Microsoft.Data.SqlClient manual test set 2: + ```bash -dotnet build build.proj -t:TestSqlClientManual -p:TestSet=2 +dotnet build -t:TestSqlClientManual -p:TestSet=2 ``` Run Microsoft.Data.SqlClient functional tests against x86 dotnet: + ```bash -dotnet build build.proj -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' +dotnet build -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' ``` Run all Microsoft.Data.SqlClient.Extensions.Azure unit tests, including interactive, but excluding failing tests: + ```bash -dotnet build build.proj -t:TestAzure -p:TestFilters=category!=failing +dotnet build -t:TestAzure -p:TestFilters=category!=failing ``` Run Microsoft.Data.SqlClient functional tests against net8.0 runtime: + ```bash -dotnet build build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 +dotnet build -t:TestSqlClientFunctional -p:TestFramework=net8.0 ``` ### Packaging Projects @@ -167,10 +196,10 @@ also handled by `build.proj`. From the root of your repository, run `dotnet buil following this pattern: ```bash -dotnet build build.proj -t: [optional_parameters] +dotnet build -t: [optional_parameters] ``` -| `` | Description | +| `` | Description | |--------------------|-------------------------------------------------------------------------------------| | `Pack` | Packages all projects in the repository. | | `PackAbstractions` | Packages the Microsoft.Data.SqlClient.Extensions.Abstractions package | @@ -185,12 +214,16 @@ dotnet build build.proj -t: [optional_parameters] A selection of parameters for pack targets in `build.proj` relevant to common developer workflows can be found below: + + | `[optional_parameter]` | Default Value | Allowed Values | Description | |------------------------------------|---------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| | `-p:Configuration=` | `Debug` | `Debug`, `Release` | Build configuration. Only applies if project and dependencies are being built. | | `-p:PackBuild=` | `true` | `true`, `false` | Whether or not to build the project before packing. If `false`, project must be built using the same parameters. | | `-p:PackageVersion=` | `[blank]` | eg. `1.2.3-dev123` | Version to assign to the package, where `` can be one of: `['Abstractions', 'Azure', 'AkvProvider', 'Logging', 'SqlClient', 'SqlServer']`. If `PackBuild` is `true`, the assembly and file versions will be derived from this version. See Versioning for more details. | + + For `PackSqlClient`, these additional parameters are optional overrides for dependency versions injected into the SqlClient nuspec during pack: - `-p:PackageVersionAbstractions=` @@ -201,23 +234,27 @@ If omitted, `PackSqlClient` computes `AbstractionsPackageVersion` and `LoggingPa #### Examples Package Microsoft.Data.SqlClient.Internal.Logging into a NuGet package: + ```bash -dotnet build build.proj -t:PackLogging +dotnet build -t:PackLogging ``` Package Microsoft.Data.SqlClient: + ```bash -dotnet build build.proj -t:PackSqlClient +dotnet build -t:PackSqlClient ``` Package version 1.2.3 of Microsoft.Data.SqlClient.Extensions.Abstractions: + ```bash -dotnet build build.proj -t:PackAbstractions -p:PackageVersionAbstractions=1.2.3 +dotnet build -t:PackAbstractions -p:PackageVersionAbstractions=1.2.3 ``` Package Microsoft.Data.SqlClient.Extensions.Azure without building it beforehand: + ```bash -dotnet build build.proj -t:PackAzure -p:PackBuild=false +dotnet build -t:PackAzure -p:PackBuild=false ``` ## Versioning @@ -255,12 +292,12 @@ build scenarios. For completeness, and debugging of automated builds, this secti To switch to "package mode", set the `ReferenceType` parameter in `build.proj` to `Package`. And, optionally, include one or more of the following parameters: -* `PackageVersionAbstractions` -* `PackageVersionAkvProvider` -* `PackageVersionAzure` -* `PackageVersionLogging` -* `PackageVersionSqlClient` -* `PackageVersionSqlServer` +- `PackageVersionAbstractions` +- `PackageVersionAkvProvider` +- `PackageVersionAzure` +- `PackageVersionLogging` +- `PackageVersionSqlClient` +- `PackageVersionSqlServer` These parameters pull double duty. In targets where the package is being built, the parameter sets the version of the package. In targets where the package is being referenced, the parameter sets the version of the package that is being @@ -277,22 +314,27 @@ run subsequent `build.proj` targets against them. Build Microsoft.Data.SqlClient version 7.1.1 that references Microsoft.Data.SqlClient.Extensions.Abstractions v1.0.1 and Microsoft.Data.SqlClient.Internal.Logging v2.2.2. +Build v2.2.2 of Logging and copy to packages: + ```bash -# Build v2.2.2 of Logging and copy to packages -dotnet build build.proj -t:PackLogging \ - -p:ReferenceType=Package \ - -p:PackageVersionLogging=2.2.2 +dotnet build -t:PackLogging -p:ReferenceType=Package -p:PackageVersionLogging=2.2.2 cp artifacts/Microsoft.Data.SqlClient.Internal.Logging/Debug/*.*pkg packages/ +``` + +Build v1.0.1 of Abstractions that depends on v2.2.2 of Logging: -# Build v1.0.1 of Abstractions that depends on v2.2.2 of Logging -dotnet build build.proj -t:PackAbstractions \ +```bash +dotnet build -t:PackAbstractions \ -p:ReferenceType=Package \ -p:PackageVersionAbstractions=1.0.1 \ - -p:PackageVersionLogging=2.2.2 \ + -p:PackageVersionLogging=2.2.2 cp artifacts/Microsoft.Data.SqlClient.Extensions.Abstractions/Package-Debug/*.*pkg packages/ +``` -# Build SqlClient -dotnet build build.proj -t:PackSqlClient \ +Build SqlClient: + +```bash +dotnet build -t:PackSqlClient \ -p:ReferenceType=Package \ -p:PackageVersionSqlClient=7.1.1 \ -p:PackageVersionAbstractions=1.0.1 \ @@ -301,22 +343,17 @@ cp artifacts/Microsoft.Data.SqlClient/Package-Debug/*.*pkg packages/ ``` Run Microsoft.Data.SqlClient functional tests against the versions built above: + ```bash -dotnet build build.proj -t:TestSqlClientFunctional \ +dotnet build -t:TestSqlClientFunctional \ -p:ReferenceType=Package \ -p:PackageVersionSqlClient=7.1.1 \ -p:PackageVersionAbstractions=1.0.1 \ -p:PackageVersionLogging=2.2.2 ``` ----- - - Manual test prerequisites and configuration are covered in [TESTGUIDE.md](TESTGUIDE.md#manual-test-prerequisites). - ---- - ## Using Managed SNI on Windows Managed SNI can be enabled on Windows by enabling the below AppContext switch: @@ -371,10 +408,14 @@ PowerShell: Bash: + + ```bash $ cd src/Microsoft.Data.SqlClient/tests/PerformanceTests ``` + + ### Create Database Create an empty database for the benchmarks to use. This example assumes diff --git a/TESTGUIDE.md b/TESTGUIDE.md index 349674deee..aab773a080 100644 --- a/TESTGUIDE.md +++ b/TESTGUIDE.md @@ -26,6 +26,16 @@ Use [build.proj](build.proj) from the repository root: dotnet build build.proj -t: [optional_parameters] ``` +Since `build.proj` is the only project file in the repo root, it can be omitted when building from +the root: + +```bash +dotnet build -t: [optional_parameters] +``` + +The command-line examples below will assume that `build.proj` is selected by default and will omit +it from the `dotnet build` command. + Test targets build the projects they depend on, so a separate build step is not required for normal test runs. ## Test Targets @@ -45,55 +55,55 @@ Test targets build the projects they depend on, so a separate build step is not Run the SqlClient unit tests: ```bash -dotnet build build.proj -t:TestSqlClientUnit +dotnet build -t:TestSqlClientUnit ``` Run the SqlClient functional tests: ```bash -dotnet build build.proj -t:TestSqlClientFunctional +dotnet build -t:TestSqlClientFunctional ``` Run the SqlClient manual tests: ```bash -dotnet build build.proj -t:TestSqlClientManual +dotnet build -t:TestSqlClientManual ``` Run only manual test set 2: ```bash -dotnet build build.proj -t:TestSqlClientManual -p:TestSet=2 +dotnet build -t:TestSqlClientManual -p:TestSet=2 ``` Run manual test sets 1 and 3: ```bash -dotnet build build.proj -t:TestSqlClientManual -p:TestSet=13 +dotnet build -t:TestSqlClientManual -p:TestSet=13 ``` Run Always Encrypted manual tests: ```bash -dotnet build build.proj -t:TestSqlClientManual -p:TestSet=AE +dotnet build -t:TestSqlClientManual -p:TestSet=AE ``` Run a specific target framework: ```bash -dotnet build build.proj -t:TestSqlClientFunctional -p:TestFramework=net8.0 +dotnet build -t:TestSqlClientFunctional -p:TestFramework=net8.0 ``` Run functional tests against an x86 `dotnet` installation: ```bash -dotnet build build.proj -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' +dotnet build -t:TestSqlClientFunctional -p:DotnetPath='C:\path\to\dotnet\x86\' ``` Run all Azure extension tests, including `interactive` tests, while still excluding tests marked `failing` or `flaky`: ```bash -dotnet build build.proj -t:TestAzure -p:TestFilters=category!=failing +dotnet build -t:TestAzure -p:TestFilters=category!=failing ``` ## Test Parameters @@ -124,15 +134,22 @@ The most commonly used test parameters are: Examples: +Run a single test by fully-qualified name: + ```bash -# Run a single test by fully-qualified name. -dotnet build build.proj -t:TestSqlClientUnit -p:TestFilters=FullyQualifiedName=Namespace.ClassName.MethodName +dotnet build -t:TestSqlClientUnit -p:TestFilters=FullyQualifiedName=Namespace.ClassName.MethodName +``` + +Run only flaky tests while investigating quarantine failures: + +```bash +dotnet build -t:TestSqlClientManual -p:TestFilters=category=flaky +``` -# Run only flaky tests while investigating quarantine failures. -dotnet build build.proj -t:TestSqlClientManual -p:TestFilters=category=flaky +Disable the default filter: -# Disable the default filter. -dotnet build build.proj -t:TestSqlClientFunctional -p:TestFilters=none +```bash +dotnet build -t:TestSqlClientFunctional -p:TestFilters=none ``` When passing filter expressions that contain shell-sensitive characters such as `&`, quote or escape the value as @@ -216,14 +233,14 @@ For SQL Server in a Linux container, WSL, or another host where SQL authenticati You can override the config file path with the `MDS_TEST_CONFIG` environment variable: ```bash -MDS_TEST_CONFIG=/path/to/config.json dotnet build build.proj -t:TestSqlClientManual -p:TestSet=2 +MDS_TEST_CONFIG=/path/to/config.json dotnet build -t:TestSqlClientManual -p:TestSet=2 ``` On PowerShell: ```powershell $env:MDS_TEST_CONFIG = "C:\path\to\config.json" -dotnet build build.proj -t:TestSqlClientManual -p:TestSet=2 +dotnet build -t:TestSqlClientManual -p:TestSet=2 ``` ## Configuration Properties @@ -280,26 +297,27 @@ If `TestSet` is omitted, all sets are compiled and run. You can combine sets by ## Results and Diagnostics -Test results are written to `test_results` by default. Override the location with `TestResultsFolderPath`: +Test results are written to the `test_results` directory by default. Override the location with +`TestResultsFolderPath`: ```bash -dotnet build build.proj -t:TestSqlClientUnit -p:TestResultsFolderPath=/tmp/sqlclient-test-results +dotnet build -t:TestSqlClientUnit -p:TestResultsFolderPath=/tmp/sqlclient-test-results ``` Hang blame collection is enabled by default with a `10m` timeout. To increase the timeout: ```bash -dotnet build build.proj -t:TestSqlClientManual -p:TestBlameTimeout=30m +dotnet build -t:TestSqlClientManual -p:TestBlameTimeout=30m ``` To disable hang blame collection: ```bash -dotnet build build.proj -t:TestSqlClientManual -p:TestBlameTimeout=0 +dotnet build -t:TestSqlClientManual -p:TestBlameTimeout=0 ``` Code coverage is enabled by default. To disable it for a faster local run: ```bash -dotnet build build.proj -t:TestSqlClientUnit -p:TestCodeCoverage=false +dotnet build -t:TestSqlClientUnit -p:TestCodeCoverage=false ``` diff --git a/build.proj b/build.proj index 14db6015ba..971cfb12fa 100644 --- a/build.proj +++ b/build.proj @@ -72,7 +72,7 @@ - + @@ -134,7 +134,7 @@ Text="--- Comparing %(_RefTfm.Identity) ref vs baseline $(BaselinePackageVersion) ---" /> From a9bde001b8326a4e6ecd266ff8a96e3607abf047 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Wed, 13 May 2026 12:45:44 -0700 Subject: [PATCH 099/298] Setup autosync workflow (#4221) * Setup autosync workflow Co-authored-by: Copilot * Update comment * Updates Co-authored-by: Copilot * Update pipeline definition * Fix error when only 1 commit exists * throw with error * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- eng/pipelines/github-sync-pipeline.yml | 87 ++++++ eng/pipelines/scripts/Sync-GitHubToAdo.ps1 | 293 +++++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 eng/pipelines/github-sync-pipeline.yml create mode 100644 eng/pipelines/scripts/Sync-GitHubToAdo.ps1 diff --git a/eng/pipelines/github-sync-pipeline.yml b/eng/pipelines/github-sync-pipeline.yml new file mode 100644 index 0000000000..699270f4de --- /dev/null +++ b/eng/pipelines/github-sync-pipeline.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. # +################################################################################# + +# This pipeline synchronizes the GitHub dotnet/SqlClient repository's main +# branch into the internal ADO repository by: +# +# 1. Fetching the latest commits from GitHub main. +# 2. Pushing them to a dev/autosync/github-main branch in ADO. +# 3. Creating (or updating) a pull request targeting internal/main. +# +# It runs on a daily schedule at 02:00 UTC and can also be triggered manually. +# The pipeline does not auto-complete the PR — changes must be reviewed and +# merged manually. +# +# Prerequisites: +# +# - The build service identity must have "Contribute", "Create branch", and +# "Contribute to pull requests" permissions on the ADO repository. +# +# This pipeline definition is mapped to the following Azure DevOps pipeline: +# +# - GitHub Sync in the ADO.Net project: +# +# https://sqlclientdrivers.visualstudio.com/ADO.Net/_build?definitionId=2263 + +# Set the pipeline run name to the calendar date (yyyyMMdd) and the daily run counter. +name: Sync-$(Date:yyyyMMdd)$(Rev:.r) + +# Do not trigger this pipeline on commits or PRs. +trigger: none +pr: none + +# Trigger this pipeline on a daily schedule. +schedules: + - cron: '0 2 * * *' + displayName: Daily GitHub Sync (02:00 UTC) + branches: + include: + - internal/main + always: true + +# Pipeline parameters, visible in the Azure DevOps UI. +parameters: + + # The GitHub branch to sync from. + - name: githubBranch + displayName: GitHub Branch + type: string + default: main + + # The ADO target branch to create the PR against. + - name: targetBranch + displayName: ADO Target Branch + type: string + default: internal/main + +jobs: + - job: SyncGitHub + displayName: Sync GitHub to ADO + pool: + vmImage: 'ubuntu-latest' + + steps: + # Check out the ADO repo with full history so we can compare branches. + - checkout: self + persistCredentials: true + fetchDepth: 0 + + # Run the sync script. + - task: PowerShell@2 + displayName: Sync GitHub main to ADO + inputs: + filePath: $(Build.SourcesDirectory)/eng/pipelines/scripts/Sync-GitHubToAdo.ps1 + arguments: >- + -GitHubRepoUrl "https://github.com/dotnet/SqlClient.git" + -GitHubBranch "${{ parameters.githubBranch }}" + -TargetBranch "${{ parameters.targetBranch }}" + -SyncBranchName "dev/autosync/github-${{ parameters.githubBranch }}" + -AdoOrgUrl "$(System.CollectionUri)" + -AdoProject "$(System.TeamProject)" + -AdoRepoName "$(Build.Repository.Name)" + pwsh: true + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) diff --git a/eng/pipelines/scripts/Sync-GitHubToAdo.ps1 b/eng/pipelines/scripts/Sync-GitHubToAdo.ps1 new file mode 100644 index 0000000000..e8b7738021 --- /dev/null +++ b/eng/pipelines/scripts/Sync-GitHubToAdo.ps1 @@ -0,0 +1,293 @@ +<# +.SYNOPSIS + Syncs a GitHub branch into an Azure DevOps repository via a pull request. + +.DESCRIPTION + This script fetches the latest commits from a public GitHub repository, + force-pushes them to a sync branch in the ADO repo, then creates or updates + a pull request targeting the specified ADO branch. + + If there are no new commits (the branches are already in sync), the script + exits cleanly with no changes. + +.PARAMETER GitHubRepoUrl + The HTTPS clone URL of the public GitHub repository. + +.PARAMETER GitHubBranch + The branch to sync from GitHub (e.g. "main"). + +.PARAMETER TargetBranch + The ADO branch the PR should target (e.g. "internal/main"). + +.PARAMETER SyncBranchName + The ADO branch name to push GitHub commits to (e.g. "dev/autosync/github-main"). + +.PARAMETER AdoOrgUrl + The Azure DevOps organization URL (e.g. "https://dev.azure.com/org/"). + +.PARAMETER AdoProject + The Azure DevOps project name. + +.PARAMETER AdoRepoName + The Azure DevOps repository name. + +.PARAMETER AccessToken + The access token for ADO REST API and git push operations. + Defaults to the SYSTEM_ACCESSTOKEN environment variable. + +.NOTES + This pipeline is intended to be run only in the internal ADO.Net project. + It must never be run in the Public project or triggered by changes in GitHub. +#> + +# 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. + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$GitHubRepoUrl, + + [Parameter(Mandatory)] + [string]$GitHubBranch, + + [Parameter(Mandatory)] + [string]$TargetBranch, + + [Parameter(Mandatory)] + [string]$SyncBranchName, + + [Parameter(Mandatory)] + [string]$AdoOrgUrl, + + [Parameter(Mandatory)] + [string]$AdoProject, + + [Parameter(Mandatory)] + [string]$AdoRepoName, + + [string]$AccessToken = $env:SYSTEM_ACCESSTOKEN +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +#region Validation +if ([string]::IsNullOrWhiteSpace($AccessToken)) { + throw "Access token is required. Set SYSTEM_ACCESSTOKEN or pass -AccessToken." +} +#endregion + +#region Helper Functions + +function Invoke-AdoApi { + <# + .SYNOPSIS + Calls an Azure DevOps REST API endpoint. + #> + param( + [Parameter(Mandatory)][string]$Uri, + [string]$Method = 'GET', + [object]$Body = $null + ) + + $headers = @{ + 'Authorization' = "Bearer $AccessToken" + 'Content-Type' = 'application/json' + } + + $params = @{ + Uri = $Uri + Method = $Method + Headers = $headers + } + + if ($null -ne $Body) { + $params['Body'] = ($Body | ConvertTo-Json -Depth 10) + } + + $response = Invoke-RestMethod @params -ErrorAction Stop + return $response +} + +function Get-CommitSummary { + <# + .SYNOPSIS + Returns a markdown-formatted list of commits between two refs. + #> + param( + [Parameter(Mandatory)][string]$BaseRef, + [Parameter(Mandatory)][string]$HeadRef, + [int]$MaxCommits = 50 + ) + + $logOutput = git log --oneline "$BaseRef..$HeadRef" -n $MaxCommits 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Warning "Could not retrieve commit log: $logOutput" + return "_(commit log unavailable)_" + } + + $lines = @($logOutput -split "`n" | Where-Object { $_ -match '\S' }) + if ($lines.Count -eq 0) { + return "_(no commits)_" + } + + $summary = ($lines | ForEach-Object { "- ``$_``" }) -join "`n" + + $totalCount = (git rev-list --count "$BaseRef..$HeadRef" 2>&1) + if ($LASTEXITCODE -eq 0 -and [int]$totalCount -gt $MaxCommits) { + $summary += "`n`n_...and $($totalCount - $MaxCommits) more commit(s)._" + } + + return $summary +} + +#endregion + +#region Git Operations + +Write-Host "=== GitHub to ADO Sync ===" +Write-Host "GitHub : $GitHubRepoUrl @ $GitHubBranch" +Write-Host "ADO : $AdoProject/$AdoRepoName @ $TargetBranch" +Write-Host "Sync : $SyncBranchName" +Write-Host "" + +# Configure git identity for any merge commits (shouldn't be needed for +# force-push, but set it defensively). +git config user.email "ado-sync-bot@microsoft.com" +git config user.name "ADO Sync Bot" + +# Add GitHub as a remote and fetch the branch we want to sync. +Write-Host "Fetching GitHub branch '$GitHubBranch'..." +$remoteExists = git remote | Where-Object { $_ -eq 'github' } +if ($remoteExists) { + git remote set-url github $GitHubRepoUrl +} else { + git remote add github $GitHubRepoUrl +} + +git fetch github $GitHubBranch --verbose +if ($LASTEXITCODE -ne 0) { + throw "Failed to fetch '$GitHubBranch' from GitHub." +} + +# Resolve the SHA of the fetched GitHub branch. +$githubSha = git rev-parse "github/$GitHubBranch" +if ($LASTEXITCODE -ne 0) { + throw "Failed to resolve SHA for 'github/$GitHubBranch'." +} +Write-Host "GitHub HEAD : $githubSha" + +# Check if the target branch exists and compare SHAs. +$targetRef = git rev-parse "origin/$TargetBranch" 2>&1 +$targetExists = ($LASTEXITCODE -eq 0) + +if ($targetExists) { + Write-Host "Target HEAD : $targetRef" +} + +# Check if the sync branch already exists in origin. +$syncRef = git rev-parse "origin/$SyncBranchName" 2>&1 +$syncBranchExists = ($LASTEXITCODE -eq 0) + +if ($syncBranchExists -and $syncRef -eq $githubSha) { + Write-Host "" + Write-Host "Sync branch is already at GitHub HEAD. Nothing to do." + exit 0 +} + +# Create the sync branch pointing to the GitHub HEAD and force-push it. +Write-Host "" +Write-Host "Updating sync branch '$SyncBranchName' to GitHub HEAD..." +git checkout -B $SyncBranchName "github/$GitHubBranch" --quiet +if ($LASTEXITCODE -ne 0) { + throw "Failed to create sync branch." +} + +git push origin $SyncBranchName --force --quiet +if ($LASTEXITCODE -ne 0) { + throw "Failed to push sync branch to ADO." +} +Write-Host "Sync branch pushed successfully." + +#endregion + +#region Pull Request Management + +# Build the commit summary for the PR description / comment. +$commitSummary = if ($targetExists) { + Get-CommitSummary -BaseRef "origin/$TargetBranch" -HeadRef "github/$GitHubBranch" +} else { + "_(target branch does not exist yet — initial sync)_" +} + +# Normalize the ADO org URL (remove trailing slash for consistent URI construction). +$AdoOrgUrl = $AdoOrgUrl.TrimEnd('/') + +# URL-encode the project name for REST API calls. +$encodedProject = [Uri]::EscapeDataString($AdoProject) +$encodedRepo = [Uri]::EscapeDataString($AdoRepoName) +$apiBase = "$AdoOrgUrl/$encodedProject/_apis/git/repositories/$encodedRepo" + +# Search for an existing active PR from the sync branch to the target branch. +Write-Host "" +Write-Host "Checking for existing pull requests..." + +$encodedSyncBranch = [Uri]::EscapeDataString($SyncBranchName) +$encodedTargetBranch = [Uri]::EscapeDataString($TargetBranch) +$searchUri = "$apiBase/pullrequests?searchCriteria.sourceRefName=refs/heads/$encodedSyncBranch" + + "&searchCriteria.targetRefName=refs/heads/$encodedTargetBranch" + + "&searchCriteria.status=active" + + "&api-version=7.1" + +$existingPrs = Invoke-AdoApi -Uri $searchUri +$activePr = $existingPrs.value | Select-Object -First 1 + +if ($activePr) { + # An active PR already exists — update its description and add a comment. + $prId = $activePr.pullRequestId + Write-Host "Active PR #$prId found. Updating description and posting comment..." + + # 1. PATCH the PR description with the refreshed commit summary. + $patchUri = "$apiBase/pullrequests/$prId?api-version=7.1" + $patchBody = @{ + description = "## Automated GitHub Sync`n`nThis PR was updated automatically by the GitHub sync pipeline.`n`nSource: [dotnet/SqlClient@$GitHubBranch](https://github.com/dotnet/SqlClient/tree/$GitHubBranch)`nSync branch: ``$SyncBranchName```n`n### Latest Commits`n`n$commitSummary`n`n---`n_This PR requires manual review and merge. Auto-complete is not enabled._" + } + Invoke-AdoApi -Uri $patchUri -Method 'PATCH' -Body $patchBody | Out-Null + Write-Host "PR #$prId description updated." + + # 2. Post a comment thread summarising the new commits. + $commentUri = "$apiBase/pullrequests/$prId/threads?api-version=7.1" + $commentBody = @{ + comments = @( + @{ + parentCommentId = 0 + content = "## Sync Update`n`nPR updated by the GitHub sync pipeline on $((Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm')) UTC.`n`nNew commits from GitHub ``$GitHubBranch``:`n`n$commitSummary" + commentType = 1 # Text + } + ) + status = 1 # Active + } + Invoke-AdoApi -Uri $commentUri -Method 'POST' -Body $commentBody | Out-Null + Write-Host "Update comment posted to PR #$prId." +} else { + # No active PR — create a new one. + Write-Host "No active PR found. Creating a new pull request..." + + $prUri = "$apiBase/pullrequests?api-version=7.1" + $prBody = @{ + sourceRefName = "refs/heads/$SyncBranchName" + targetRefName = "refs/heads/$TargetBranch" + title = "[GitHub Sync] Update $TargetBranch from GitHub $GitHubBranch" + description = "## Automated GitHub Sync`n`nThis PR was created automatically by the GitHub sync pipeline.`n`nSource: [dotnet/SqlClient@$GitHubBranch](https://github.com/dotnet/SqlClient/tree/$GitHubBranch)`nSync branch: ``$SyncBranchName```n`n### Commits`n`n$commitSummary`n`n---`n_This PR requires manual review and merge. Auto-complete is not enabled._" + } + + $newPr = Invoke-AdoApi -Uri $prUri -Method 'POST' -Body $prBody + Write-Host "Created PR #$($newPr.pullRequestId): $($newPr.title)" +} + +#endregion + +Write-Host "" +Write-Host "=== Sync complete ===" From 7a49600759968413b93562799dc0e981c3226c67 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 14 May 2026 08:49:24 +1000 Subject: [PATCH 100/298] remove redundant StopWatch.Stop in SendBroadcastUDPRequest (#4102) Co-authored-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> --- .../ManagedSni/SsrpClient.netcore.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs index 43b411fb5d..4544d1169f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs @@ -414,24 +414,16 @@ internal static string SendBroadcastUDPRequest() 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(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 + Stopwatch sw = Stopwatch.StartNew(); //for waiting until 15 sec elapsed + while ((receiveTask = clientListener.ReceiveAsync()).Wait(currentTimeOut) && sw.ElapsedMilliseconds <= ReceiveMAXTimeoutsForCLNT_BCAST_EX && receiveTask != null) { - while ((receiveTask = clientListener.ReceiveAsync()).Wait(currentTimeOut) && sw.ElapsedMilliseconds <= ReceiveMAXTimeoutsForCLNT_BCAST_EX && receiveTask != null) + currentTimeOut = ReceiveTimeoutsForCLNT_BCAST_EX; + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "Received instnace info from UDP Client."); + if (receiveTask.Result.Buffer.Length < ValidResponseSizeForCLNT_BCAST_EX) //discard invalid response { - currentTimeOut = ReceiveTimeoutsForCLNT_BCAST_EX; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "Received instance 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) - } + 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) } } - finally - { - sw.Stop(); - } } } return response.ToString(); From fbfd82719c79e28fa968683f6c0568faf8a1bd2a Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 14 May 2026 10:25:34 +0100 Subject: [PATCH 101/298] Fix | Build DataTypes table in code, add `json` data type (#3858) * Create structure of DataTypes static population * Remove redundant data types from metadata SqlClient cannot connect to SQL Server 2000 instances, so the minimum server version is 9.0. * Remove data type entries with a maximum version below this. * Remove the minimum version constraint where this constraint will always be true. * Populate DataTypes table from MetaType information * Add test to verify presence of data types * Add json data type to DataTypes schema collection * Use IsJsonSupported more widely This enables the JSON tests to run against SQL Server 2025. Also correct a comment in LoadDataTypesDataTables. * Plumb minimum version logic to AddStringOrBinaryType * Address MARS JSON test failure This was manually constructing a connection string, which didn't work on one CI leg. --- .../SqlClient/SqlMetaDataFactory.DataTypes.cs | 438 +++++++++++ .../Data/SqlClient/SqlMetaDataFactory.cs | 40 +- .../Microsoft.Data.SqlClient.SqlMetaData.xml | 689 ------------------ .../ManualTests/DataCommon/DataTestUtility.cs | 12 + .../ConnectionSchemaTest.cs | 107 ++- .../SQL/JsonTest/JsonBulkCopyTest.cs | 4 +- .../SQL/JsonTest/JsonStreamTest.cs | 4 +- .../ManualTests/SQL/JsonTest/JsonTest.cs | 19 +- 8 files changed, 551 insertions(+), 762 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs new file mode 100644 index 0000000000..eba61969c3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.DataTypes.cs @@ -0,0 +1,438 @@ +// 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; + +#nullable enable + +namespace Microsoft.Data.SqlClient; + +internal sealed partial class SqlMetaDataFactory +{ + private static void LoadDataTypesDataTables(DataSet metaDataCollectionsDataSet) + { + DataTable dataTypesDataTable = CreateDataTypesDataTable(); + + dataTypesDataTable.BeginLoadData(); + + // SQL Server data types are grouped into the rough categories below: + // 1. Fixed-scale and fixed-precision numeric types: tinyint, smallint, int, bigint, real, money, smallmoney, bit. + // 2. Variable-precision numeric types: float and decimal / numeric. + // 3. Fixed-precision date/time types: datetime, smalldatetime, date. + // 4. Variable-precision date/time types: time, datetime2, datetimeoffset. + // 5. Long types: xml, json, text, ntext, image. + // 6. Fixed-length string and binary types: char, nchar, binary. + // 7. Variable-length string and binary types: varchar, nvarchar, varbinary. + // 8. Miscellaneous fixed-length types: uniqueidentifier, sql_variant, timestamp + + AddFixedNumericType(SqlDbType.TinyInt, isBestMatch: true); + AddFixedNumericType(SqlDbType.SmallInt, isBestMatch: true); + AddFixedNumericType(SqlDbType.Int, isBestMatch: true); + AddFixedNumericType(SqlDbType.BigInt, isBestMatch: true); + AddFixedNumericType(SqlDbType.Real, isBestMatch: true); + // Money and smallmoney are not the best ways to represent System.Decimal values. SQL Server + // provides a variable-precision "numeric" type for that purpose. + AddFixedNumericType(SqlDbType.Money, isBestMatch: false); + AddFixedNumericType(SqlDbType.SmallMoney, isBestMatch: false); + AddFixedNumericType(SqlDbType.Bit, isBestMatch: false); + + AddVariablePrecisionNumericType(SqlDbType.Float, columnSize: 53); + AddVariablePrecisionNumericType(SqlDbType.Decimal, columnSize: 38); + AddVariablePrecisionNumericType(SqlDbType.Decimal, columnSize: 38, + aliasType: "numeric"); + + AddFixedPrecisionDateTimeType(SqlDbType.DateTime, isBestMatch: true); + AddFixedPrecisionDateTimeType(SqlDbType.SmallDateTime, isBestMatch: true); + AddFixedPrecisionDateTimeType(SqlDbType.Date, isBestMatch: false, + minimumVersion: "10.00.000.0"); + + AddVariablePrecisionDateTimeType(SqlDbType.Time, columnSize: 5, isBestMatch: false, + minimumVersion: "10.00.000.0"); + AddVariablePrecisionDateTimeType(SqlDbType.DateTime2, columnSize: 8, + minimumVersion: "10.00.000.0"); + AddVariablePrecisionDateTimeType(SqlDbType.DateTimeOffset, columnSize: 10, + minimumVersion: "10.00.000.0"); + + AddLongStringOrBinaryType(SqlDbType.Xml); + AddLongStringOrBinaryType(SqlDbTypeExtensions.Json, literalPrefix: "'", literalSuffix: "'", + minimumVersion: "17.00.000.0"); + AddLongStringOrBinaryType(SqlDbType.Text, literalPrefix: "'", literalSuffix: "'"); + AddLongStringOrBinaryType(SqlDbType.NText, literalPrefix: "N'", literalSuffix: "'"); + AddLongStringOrBinaryType(SqlDbType.Image, literalPrefix: "0x"); + + AddFixedLengthStringOrBinaryType(SqlDbType.Char, literalPrefix: "'", literalSuffix: "'"); + AddFixedLengthStringOrBinaryType(SqlDbType.NChar, literalPrefix: "N'", literalSuffix: "'"); + AddFixedLengthStringOrBinaryType(SqlDbType.Binary, literalPrefix: "0x"); + + AddVariableLengthStringOrBinaryType(SqlDbType.VarChar, literalPrefix: "'", literalSuffix: "'"); + AddVariableLengthStringOrBinaryType(SqlDbType.NVarChar, literalPrefix: "N'", literalSuffix: "'"); + AddVariableLengthStringOrBinaryType(SqlDbType.VarBinary, literalPrefix: "0x"); + + AddUniqueIdentifierType(); + AddSqlVariantType(); + AddRowVersionType(); + + dataTypesDataTable.EndLoadData(); + dataTypesDataTable.AcceptChanges(); + + metaDataCollectionsDataSet.Tables.Add(dataTypesDataTable); + + void AddFixedNumericType(SqlDbType integerDbType, bool isBestMatch) + { + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(integerDbType, isMultiValued: false); + DataRow typeRow = dataTypesDataTable.NewRow(); + + typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; + typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; + + // A fixed-scale integer always has precision equal to the column size and a scale of 0xFF. + // If the precision is marked as unknown, then report the column size directly. + Debug.Assert(metaType.Scale == TdsEnums.UNKNOWN_PRECISION_SCALE); + typeRow[DbMetaDataColumnNames.ColumnSize] = + metaType.Precision == TdsEnums.UNKNOWN_PRECISION_SCALE + ? metaType.FixedLength + : metaType.Precision; + + typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; + // Of all fixed-scale integer types, only "tinyint", "smallint", "int" and "bigint" are auto-incrementable. + typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = + integerDbType is SqlDbType.TinyInt or SqlDbType.SmallInt or SqlDbType.Int or SqlDbType.BigInt; + typeRow[DbMetaDataColumnNames.IsBestMatch] = isBestMatch; + typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; + typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; + typeRow[DbMetaDataColumnNames.IsFixedLength] = true; + // "real" is an ISO synonym of "float(24)". This means that it's a fixed-scale alias of a dynamic-scale type. + // "bit" is also not considered fixed precision/scale, since SQL Server packs multiple bit columns into the same byte. + typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = integerDbType is not SqlDbType.Real and not SqlDbType.Bit; + typeRow[DbMetaDataColumnNames.IsLong] = false; + typeRow[DbMetaDataColumnNames.IsNullable] = true; + typeRow[DbMetaDataColumnNames.IsSearchable] = true; + typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = false; + // Only "tinyint" is unsigned. "bit" does not have the concept of signed/unsigned. + if (integerDbType is not SqlDbType.Bit) + { + typeRow[DbMetaDataColumnNames.IsUnsigned] = integerDbType is SqlDbType.TinyInt; + } + + dataTypesDataTable.Rows.Add(typeRow); + } + + void AddVariablePrecisionNumericType(SqlDbType numericDbType, int columnSize, + string? aliasType = null) + { + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(numericDbType, isMultiValued: false); + string typeName = aliasType ?? metaType.TypeName; + bool variableScale = numericDbType is SqlDbType.Decimal; + DataRow typeRow = dataTypesDataTable.NewRow(); + + typeRow[DbMetaDataColumnNames.TypeName] = typeName; + typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; + typeRow[DbMetaDataColumnNames.ColumnSize] = columnSize; + + // Both "float" and "decimal" have variable precision, but "decimal" also has variable scale. + if (variableScale) + { + typeRow[DbMetaDataColumnNames.CreateFormat] = $"{typeName}({{0}}, {{1}})"; + typeRow[DbMetaDataColumnNames.CreateParameters] = "precision,scale"; + typeRow[DbMetaDataColumnNames.MinimumScale] = 0; + // The data type is a fixed number of bytes, which can be distributed between the precision + // and the scale. Therefore, the maximum scale is equal to the column size. + typeRow[DbMetaDataColumnNames.MaximumScale] = columnSize; + } + else + { + typeRow[DbMetaDataColumnNames.CreateFormat] = $"{typeName}({{0}})"; + typeRow[DbMetaDataColumnNames.CreateParameters] = "number of bits used to store the mantissa"; + } + + typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; + // Only the "decimal" type is auto-incrementable. + typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = numericDbType is SqlDbType.Decimal; + typeRow[DbMetaDataColumnNames.IsBestMatch] = true; + typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; + typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; + typeRow[DbMetaDataColumnNames.IsFixedLength] = true; + typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; + typeRow[DbMetaDataColumnNames.IsLong] = false; + typeRow[DbMetaDataColumnNames.IsNullable] = true; + typeRow[DbMetaDataColumnNames.IsSearchable] = true; + typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = false; + typeRow[DbMetaDataColumnNames.IsUnsigned] = false; + + dataTypesDataTable.Rows.Add(typeRow); + } + + void AddFixedPrecisionDateTimeType(SqlDbType dateTimeDbType, bool isBestMatch, + string? minimumVersion = null) + { + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(dateTimeDbType, isMultiValued: false); + DataRow typeRow = dataTypesDataTable.NewRow(); + + typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; + // "date" reports an unknown precision and an unknown scale, even though its precision and scale are fixed. + // To that end, we report its column length directly. + typeRow[DbMetaDataColumnNames.ColumnSize] = + metaType.Precision == TdsEnums.UNKNOWN_PRECISION_SCALE + ? metaType.FixedLength + : metaType.Precision; + typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; + typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; + typeRow[DbMetaDataColumnNames.IsBestMatch] = isBestMatch; + typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; + typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; + typeRow[DbMetaDataColumnNames.IsFixedLength] = true; + typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = dateTimeDbType is SqlDbType.Date; + typeRow[DbMetaDataColumnNames.IsLong] = false; + typeRow[DbMetaDataColumnNames.IsNullable] = true; + typeRow[DbMetaDataColumnNames.IsSearchable] = true; + typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = true; + typeRow[DbMetaDataColumnNames.LiteralPrefix] = @"{ts '"; + typeRow[DbMetaDataColumnNames.LiteralSuffix] = @"'}"; + + if (minimumVersion is not null) + { + typeRow[MinimumVersionKey] = minimumVersion; + } + + dataTypesDataTable.Rows.Add(typeRow); + } + + void AddVariablePrecisionDateTimeType(SqlDbType dateTimeDbType, int columnSize, + bool isBestMatch = true, + string? minimumVersion = null) + { + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(dateTimeDbType, isMultiValued: false); + DataRow typeRow = dataTypesDataTable.NewRow(); + + typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; + typeRow[DbMetaDataColumnNames.ColumnSize] = columnSize; + typeRow[DbMetaDataColumnNames.CreateFormat] = $"{metaType.TypeName}({{0}})"; + // The documentation describes these data types as having variable precision, but GetSchema reports that they + // have variable scale. + typeRow[DbMetaDataColumnNames.CreateParameters] = "scale"; + typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; + typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; + typeRow[DbMetaDataColumnNames.IsBestMatch] = isBestMatch; + typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; + typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; + typeRow[DbMetaDataColumnNames.IsFixedLength] = false; + typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; + typeRow[DbMetaDataColumnNames.IsLong] = false; + typeRow[DbMetaDataColumnNames.IsNullable] = true; + typeRow[DbMetaDataColumnNames.IsSearchable] = true; + typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = true; + typeRow[DbMetaDataColumnNames.MinimumScale] = 0; + typeRow[DbMetaDataColumnNames.MaximumScale] = metaType.Scale; + typeRow[DbMetaDataColumnNames.LiteralPrefix] = @"{ts '"; + typeRow[DbMetaDataColumnNames.LiteralSuffix] = @"'}"; + + if (minimumVersion is not null) + { + typeRow[MinimumVersionKey] = minimumVersion; + } + + dataTypesDataTable.Rows.Add(typeRow); + } + + void AddLongStringOrBinaryType(SqlDbType longDbType, + string? literalPrefix = null, string? literalSuffix = null, + string? minimumVersion = null) => + AddStringOrBinaryType(longDbType, + // The column size is measured in elements, not bytes. For ntext, each element is a 2 byte Unicode character. + columnSize: longDbType is SqlDbType.NText ? int.MaxValue / ADP.CharSize : int.MaxValue, + isLong: true, isFixedLength: false, + isSearchable: false, + literalPrefix: literalPrefix, literalSuffix: literalSuffix, + minimumVersion: minimumVersion); + + void AddFixedLengthStringOrBinaryType(SqlDbType fixedLengthDbType, + string? literalPrefix = null, string? literalSuffix = null) => + AddStringOrBinaryType(fixedLengthDbType, + // See the comment on AddLongStringOrBinary regarding column sizes. To add: the "binary" type can be up to 8000 bytes. + columnSize: fixedLengthDbType switch + { + SqlDbType.Binary => 8000, + SqlDbType.NChar => int.MaxValue / ADP.CharSize, + _ => int.MaxValue + }, + isLong: false, isFixedLength: true, + isSearchable: true, + literalPrefix: literalPrefix, literalSuffix: literalSuffix); + + void AddVariableLengthStringOrBinaryType(SqlDbType variableLengthDbType, + string? literalPrefix = null, string? literalSuffix = null) => + AddStringOrBinaryType(variableLengthDbType, + // See the comment on AddLongStringOrBinary regarding column sizes. Unlike the "binary" type, varbinary is reported to + // have a maximum column size of (2^32-1) / 2 elements, just as nvarchar does. + columnSize: variableLengthDbType is SqlDbType.NVarChar or SqlDbType.VarBinary + ? int.MaxValue / ADP.CharSize + : int.MaxValue, + isLong: false, isFixedLength: false, + isSearchable: true, + literalPrefix: literalPrefix, literalSuffix: literalSuffix); + + void AddRowVersionType() => + AddStringOrBinaryType(SqlDbType.Timestamp, + columnSize: TdsEnums.TEXT_TIME_STAMP_LEN, isLong: false, isFixedLength: true, + isSearchable: true, + literalPrefix: "0x"); + + void AddStringOrBinaryType(SqlDbType sqlDbType, int columnSize, bool isLong, + bool isFixedLength, bool isSearchable, + string? literalPrefix = null, string? literalSuffix = null, + string? minimumVersion = null) + { + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(sqlDbType, isMultiValued: false); + DataRow typeRow = dataTypesDataTable.NewRow(); + bool hasLengthSpecifier = sqlDbType is SqlDbType.Char or SqlDbType.NChar or SqlDbType.Binary + or SqlDbType.VarChar or SqlDbType.NVarChar or SqlDbType.VarBinary; + + typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; + typeRow[DbMetaDataColumnNames.ColumnSize] = columnSize; + // Several string or binary data types do not include the length in their type declaration. + // Of the data types which do, fixed-length types use the length parameter to decide the full length, + // while the rest use it to decide the maximum length. + if (hasLengthSpecifier) + { + typeRow[DbMetaDataColumnNames.CreateFormat] = $"{metaType.TypeName}({{0}})"; + typeRow[DbMetaDataColumnNames.CreateParameters] = + isFixedLength ? "length" : "max length"; + } + else + { + typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; + } + typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; + typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; + // The DataType for XML is string, which is not the best match for an XML type. + // Similarly, although timestamp/rowversion is represented as a byte array, it's not best + // represented as such. + typeRow[DbMetaDataColumnNames.IsBestMatch] = sqlDbType is not SqlDbType.Xml and not SqlDbType.Timestamp and not SqlDbTypeExtensions.Json; + typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; + typeRow[DbMetaDataColumnNames.IsConcurrencyType] = sqlDbType is SqlDbType.Timestamp; + typeRow[DbMetaDataColumnNames.IsFixedLength] = isFixedLength; + typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; + typeRow[DbMetaDataColumnNames.IsLong] = isLong; + typeRow[DbMetaDataColumnNames.IsNullable] = sqlDbType is not SqlDbType.Timestamp; + typeRow[DbMetaDataColumnNames.IsSearchable] = isSearchable; + // String types are searchable with LIKE; binary types are not. SQL Server considers XML and JSON a binary type for this purpose. + typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = + metaType.IsCharType && sqlDbType is not SqlDbType.Xml and not SqlDbTypeExtensions.Json; + + if (literalPrefix is null && literalSuffix is null) + { + // If no literal prefix or suffix is specified, then literal support is not available. + typeRow[DbMetaDataColumnNames.IsLiteralSupported] = false; + } + else + { + if (literalPrefix is not null) + { + typeRow[DbMetaDataColumnNames.LiteralPrefix] = literalPrefix; + } + if (literalSuffix is not null) + { + typeRow[DbMetaDataColumnNames.LiteralSuffix] = literalSuffix; + } + } + + if (minimumVersion is not null) + { + typeRow[MinimumVersionKey] = minimumVersion; + } + + dataTypesDataTable.Rows.Add(typeRow); + } + + void AddSqlVariantType() + { + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(SqlDbType.Variant, isMultiValued: false); + DataRow typeRow = dataTypesDataTable.NewRow(); + + typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; + typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; + typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; + typeRow[DbMetaDataColumnNames.IsBestMatch] = true; + typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; + typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; + typeRow[DbMetaDataColumnNames.IsFixedLength] = false; + typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; + typeRow[DbMetaDataColumnNames.IsLong] = false; + typeRow[DbMetaDataColumnNames.IsNullable] = true; + typeRow[DbMetaDataColumnNames.IsSearchable] = true; + typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = false; + typeRow[DbMetaDataColumnNames.IsLiteralSupported] = false; + + dataTypesDataTable.Rows.Add(typeRow); + } + + void AddUniqueIdentifierType() + { + MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(SqlDbType.UniqueIdentifier, isMultiValued: false); + DataRow typeRow = dataTypesDataTable.NewRow(); + + typeRow[DbMetaDataColumnNames.TypeName] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.ProviderDbType] = (int)metaType.SqlDbType; + typeRow[DbMetaDataColumnNames.ColumnSize] = metaType.FixedLength; + typeRow[DbMetaDataColumnNames.CreateFormat] = metaType.TypeName; + typeRow[DbMetaDataColumnNames.DataType] = metaType.ClassType.FullName; + typeRow[DbMetaDataColumnNames.IsAutoIncrementable] = false; + typeRow[DbMetaDataColumnNames.IsBestMatch] = true; + typeRow[DbMetaDataColumnNames.IsCaseSensitive] = false; + typeRow[DbMetaDataColumnNames.IsConcurrencyType] = false; + typeRow[DbMetaDataColumnNames.IsFixedLength] = true; + typeRow[DbMetaDataColumnNames.IsFixedPrecisionScale] = false; + typeRow[DbMetaDataColumnNames.IsLong] = false; + typeRow[DbMetaDataColumnNames.IsNullable] = true; + typeRow[DbMetaDataColumnNames.IsSearchable] = true; + typeRow[DbMetaDataColumnNames.IsSearchableWithLike] = false; + typeRow[DbMetaDataColumnNames.LiteralPrefix] = "'"; + typeRow[DbMetaDataColumnNames.LiteralSuffix] = "'"; + + dataTypesDataTable.Rows.Add(typeRow); + } + } + + private static DataTable CreateDataTypesDataTable() + => new(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)) + } + }; +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs index 0c659579e7..1d9ceebafd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -17,7 +17,7 @@ namespace Microsoft.Data.SqlClient { - internal sealed class SqlMetaDataFactory : IDisposable + internal sealed partial class SqlMetaDataFactory : IDisposable { // Well-known column names private const string CollectionNameKey = "CollectionName"; @@ -713,6 +713,9 @@ private DataSet LoadDataSetFromXml(Stream XmlStream) { Locale = CultureInfo.InvariantCulture }; + + LoadDataTypesDataTables(metaDataCollectionsDataSet); + XmlReaderSettings settings = new() { XmlResolver = null, @@ -762,9 +765,6 @@ private DataSet LoadDataSetFromXml(Stream XmlStream) dataTable = CreateDataSourceInformationDataTable(); rowFixup = FixUpDataSourceInformationRow; break; - case "DataTypesTable": - dataTable = CreateDataTypesDataTable(); - break; case "ReservedWordsTable": dataTable = CreateReservedWordsDataTable(); break; @@ -898,38 +898,6 @@ private static DataTable CreateDataSourceInformationDataTable() } }; - private static DataTable CreateDataTypesDataTable() - => new(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(DbMetaDataCollectionNames.ReservedWords) { 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 index 15090edf8e..494e9a9106 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml +++ b/src/Microsoft.Data.SqlClient/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml @@ -580,695 +580,6 @@ 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 diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index b66c72774e..1e5458e130 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -94,6 +94,7 @@ public static class DataTestUtility // SQL Server capabilities private static bool? s_isDataClassificationSupported; + private static bool? s_isJsonSupported; private static bool? s_isVectorSupported; private static bool? s_isVectorFloat16Supported; @@ -146,6 +147,17 @@ public static string SQLServerVersion s_isDataClassificationSupported ??= IsTCPConnStringSetup() && IsObjectPresent("SYS.SENSITIVITY_CLASSIFICATIONS"); + /// + /// Determines whether the SQL Server supports the 'json' data type. + /// + /// + /// This method attempts to connect to the SQL Server and check for the existence of the + /// 'json' data type. + /// + public static bool IsJsonSupported => + s_isJsonSupported ??= IsTCPConnStringSetup() && + IsTypePresent("json"); + /// /// Determines whether the SQL Server supports the 'vector' data type. /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs index e765f56ce8..028447251c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Threading.Tasks; using Xunit; @@ -108,36 +109,94 @@ public static async Task GetStructuredTypeMembersFromSchema() await VerifySchemaTable(SqlClientMetaDataCollectionNames.StructuredTypeMembers, new string[] { "TYPE_CATALOG", "TYPE_SCHEMA", "TYPE_NAME", "MEMBER_NAME", "ORDINAL_POSITION" }); } - private static async Task VerifySchemaTable(string schemaItemName, string[] testColumnNames) - { - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + [ConditionalFact(nameof(CanRunSchemaTests))] + public static async Task GetDataTypesFromSchema() + { + (DataTable syncSchemaTable, DataTable asyncSchemaTable) = await VerifySchemaTable(DbMetaDataCollectionNames.DataTypes, [ + DbMetaDataColumnNames.TypeName, + DbMetaDataColumnNames.ProviderDbType, + DbMetaDataColumnNames.ColumnSize, + DbMetaDataColumnNames.CreateFormat, + DbMetaDataColumnNames.CreateParameters, + DbMetaDataColumnNames.DataType, + DbMetaDataColumnNames.IsAutoIncrementable, + DbMetaDataColumnNames.IsBestMatch, + DbMetaDataColumnNames.IsCaseSensitive, + DbMetaDataColumnNames.IsFixedLength, + DbMetaDataColumnNames.IsFixedPrecisionScale, + DbMetaDataColumnNames.IsLong, + DbMetaDataColumnNames.IsNullable, + DbMetaDataColumnNames.IsSearchable, + DbMetaDataColumnNames.IsSearchableWithLike, + DbMetaDataColumnNames.IsUnsigned, + DbMetaDataColumnNames.MaximumScale, + DbMetaDataColumnNames.MinimumScale, + DbMetaDataColumnNames.IsConcurrencyType, + DbMetaDataColumnNames.IsLiteralSupported, + DbMetaDataColumnNames.LiteralPrefix, + DbMetaDataColumnNames.LiteralSuffix + ]); + + VerifyDataTypesTable(syncSchemaTable); + VerifyDataTypesTable(asyncSchemaTable); + } + + private static async Task<(DataTable SyncSchema, DataTable AsyncSchema)> VerifySchemaTable(string schemaItemName, string[] testColumnNames) + { + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) { InitialCatalog = "master" }; + using SqlConnection connection = new(builder.ConnectionString); + HashSet syncColumnNames = []; + HashSet asyncColumnNames = []; + + // Connect to the database then retrieve the schema information + connection.Open(); + + DataTable syncTable = connection.GetSchema(schemaItemName); + DataTable asyncTable = await connection.GetSchemaAsync(schemaItemName); + + // Get all table columns + foreach (DataColumn column in syncTable.Columns) + { + syncColumnNames.Add(column.ColumnName); + } + foreach (DataColumn column in asyncTable.Columns) + { + asyncColumnNames.Add(column.ColumnName); + } + + Assert.All(testColumnNames, column => Assert.Contains(column, syncColumnNames)); + Assert.All(testColumnNames, column => Assert.Contains(column, asyncColumnNames)); + return (syncTable, asyncTable); + } + + private static void VerifyDataTypesTable(DataTable dataTypesTable) + { + string[] expectedTypes = [ + "smallint", "int", "real", "float", "money", "smallmoney", "bit", "tinyint", "bigint", "timestamp", + "binary", "image", "text", "ntext", "decimal", "numeric", "datetime", "smalldatetime", "sql_variant", "xml", + "varchar", "char", "nchar", "nvarchar", "varbinary", "uniqueidentifier", "date", "time", "datetime2", "datetimeoffset" + ]; + HashSet actualTypes = []; - using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) + // Get every type name, asserting that it is a unique string. + foreach (DataRow row in dataTypesTable.Rows) { - // Connect to the database then retrieve the schema information - connection.Open(); - DataTable syncTable = connection.GetSchema(schemaItemName); - DataTable asyncTable = await connection.GetSchemaAsync(schemaItemName); - - // Get all table columns - HashSet syncColumnNames = new HashSet(); - HashSet asyncColumnNames = new HashSet(); - - foreach (DataColumn column in syncTable.Columns) - { - syncColumnNames.Add(column.ColumnName); - } - foreach (DataColumn column in asyncTable.Columns) - { - asyncColumnNames.Add(column.ColumnName); - } - - Assert.All(testColumnNames, column => Assert.Contains(column, syncColumnNames)); - Assert.All(testColumnNames, column => Assert.Contains(column, asyncColumnNames)); + string typeName = row[DbMetaDataColumnNames.TypeName] as string; + + Assert.False(string.IsNullOrEmpty(typeName)); + Assert.True(actualTypes.Add(typeName)); } + + // Every expected type should be present. There will often be additional types present - user-defined table types + // and CLR types (such as geography and geometry.) + Assert.All(expectedTypes, type => Assert.Contains(type, actualTypes)); + + // The "json" type should only be present when running against a SQL Server version which supports it. + // SQL Azure reports a version of 12.x but supports JSON, so SqlClient doesn't include it in the list of types. + Assert.Equal(DataTestUtility.IsJsonSupported && DataTestUtility.IsNotAzureServer(), actualTypes.Contains("json")); } } } 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 03c5f794c0..06b76c7ed4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs @@ -269,7 +269,7 @@ private async Task BulkCopyDataAsync(CommandBehavior cb, bool enableStraming, in } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsJsonSupported))] [MemberData( nameof(JsonBulkCopyTestData) #if NETFRAMEWORK @@ -293,7 +293,7 @@ public void TestJsonBulkCopy(CommandBehavior cb, bool enableStraming, int jsonAr } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsJsonSupported))] [MemberData( nameof(JsonBulkCopyTestData) #if NETFRAMEWORK diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs index cbba92153a..3cb0f1b534 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs @@ -162,7 +162,7 @@ private void DeleteFile(string filename) } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsJsonSupported))] public void TestJsonStreaming() { try @@ -184,7 +184,7 @@ public void TestJsonStreaming() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsJsonSupported))] public async Task TestJsonStreamingAsync() { try 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 0652251447..2b2fd2c8db 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs @@ -74,7 +74,7 @@ private void ValidateNullJson(SqlDataReader reader) } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestJsonWrite() { using SqlConnection connection = new(DataTestUtility.TCPConnectionString); @@ -120,7 +120,7 @@ public void TestJsonWrite() ValidateRowsAffected(rowsAffected4); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public async Task TestJsonWriteAsync() { using SqlConnection connection = new(DataTestUtility.TCPConnectionString); @@ -165,7 +165,7 @@ public async Task TestJsonWriteAsync() ValidateRowsAffected(rowsAffected4); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestJsonRead() { using SqlConnection connection = new(DataTestUtility.TCPConnectionString); @@ -205,7 +205,7 @@ public void TestJsonRead() ValidateRows(reader2); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public async Task TestJsonReadAsync() { using SqlConnection connection = new(DataTestUtility.TCPConnectionString); @@ -245,7 +245,7 @@ public async Task TestJsonReadAsync() await ValidateRowsAsync(reader2); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestNullJson() { using SqlConnection connection = new(DataTestUtility.TCPConnectionString); @@ -268,7 +268,7 @@ public void TestNullJson() ValidateNullJson(reader); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestJsonAPIs() { using SqlConnection connection = new(DataTestUtility.TCPConnectionString); @@ -299,10 +299,11 @@ public void TestJsonAPIs() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestJsonWithMARS() { - using SqlConnection connection = new(DataTestUtility.TCPConnectionString + "MultipleActiveResultSets=True;"); + SqlConnectionStringBuilder csbMarsEnabledTcp = new(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = true }; + using SqlConnection connection = new(csbMarsEnabledTcp.ConnectionString); connection.Open(); using Table jsonTable1 = new(connection, nameof(TestJsonWithMARS), "(Data json)"); @@ -339,7 +340,7 @@ public void TestJsonWithMARS() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAzureServer), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestJsonSPParams() { using SqlConnection connection = new(DataTestUtility.TCPConnectionString); From 0b53d9fc989d3ce10b6548a7ac0b4aaa576cd9c8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 15:05:11 +0530 Subject: [PATCH 102/298] Prepend internal/ to schedule branch filter in sqlclient-kerberos pipeline (#4285) Agent-Logs-Url: https://github.com/dotnet/SqlClient/sessions/05fdb01b-de95-4747-8891-ca640d6bdc46 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: paulmedynski <31868385+paulmedynski@users.noreply.github.com> --- eng/pipelines/kerberos/sqlclient-kerberos.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/kerberos/sqlclient-kerberos.yml b/eng/pipelines/kerberos/sqlclient-kerberos.yml index a480ee8fb7..eb0016f9fc 100644 --- a/eng/pipelines/kerberos/sqlclient-kerberos.yml +++ b/eng/pipelines/kerberos/sqlclient-kerberos.yml @@ -10,7 +10,7 @@ # Daily Kerberos authentication test pipeline for Microsoft.Data.SqlClient. # Replaces the Classic "Test-SqlClient-Kerberos-Azure" pipeline. # -# Schedule: daily at 07:00 UTC on main. +# Schedule: daily at 07:00 UTC on internal/main. # # Job breakdown (10 total): # Stage: windows → 7 jobs (net462 + net8/9/10 × NativeSNI/ManagedSNI) @@ -42,7 +42,7 @@ schedules: displayName: Daily run (07:00 UTC) branches: include: - - main + - internal/main always: true name: $(date:yyyyMMdd)$(rev:.r) From bf9c17d56068523d3ccf84ed55815c5eb0e27980 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 14 May 2026 06:42:14 -0300 Subject: [PATCH 103/298] Add audit-variable-groups prompt for ADO variable group lifecycle management (#4181) --- .../prompts/audit-variable-groups.prompt.md | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 .github/prompts/audit-variable-groups.prompt.md diff --git a/.github/prompts/audit-variable-groups.prompt.md b/.github/prompts/audit-variable-groups.prompt.md new file mode 100644 index 0000000000..075e12d99a --- /dev/null +++ b/.github/prompts/audit-variable-groups.prompt.md @@ -0,0 +1,164 @@ +--- +name: audit-variable-groups +description: Audit Azure DevOps variable groups by searching repos/branches for usage and updating descriptions accordingly. +argument-hint: +tools: ['execute/runInTerminal', 'execute/getTerminalOutput', 'edit/createFile', 'read/readFile'] +--- + +Audit Azure DevOps variable groups in the **sqlclientdrivers** organization, **ADO.Net** project. Use the `az` CLI where possible; fall back to direct REST API calls where `az` doesn't provide sufficient coverage (e.g., repo file scanning, AzureKeyVault group updates). + +## Safety constraints + +- **Read-only by default.** Steps 1–4 are purely read-only (listing, searching, summarizing). Do NOT issue any write operations (`PUT`, `PATCH`, `POST`, `az pipelines variable-group update`, or any command that modifies state) until the user has explicitly approved changes in step 5. +- **No implicit approval.** Silence, ambiguous replies, or partial acknowledgements do NOT count as approval. You must receive an unambiguous "go" (or equivalent affirmative) from the user before proceeding to step 6. +- **Scope lock.** Only update variable group descriptions. Never delete variable groups, modify variables within a group, or change any pipeline definitions. +- **Dry-run first.** When presenting the summary in step 4, show the exact before/after description text for every group that would be modified so the user can verify the changes. +- **Abort on doubt.** If any step produces unexpected errors, ambiguous results, or data that contradicts expectations, stop and ask the user for guidance rather than proceeding. + +## Inputs + +If the user provided arguments, parse `${input:scope}` for overrides — it may contain specific repos, branches, or variable group names to scope the audit. Apply any recognized values as overrides to the defaults below; ignore unrecognized tokens. + +The user may override any of the following defaults: + +- **Organization**: `https://sqlclientdrivers.visualstudio.com` +- **Project**: `ADO.Net` +- **Repos & branches to search**: + - `dotnet-sqlclient`: `internal/main`, `internal/release/7.0`, `internal/release/6.1` + - `Microsoft.Data.SqlClient`: `ConfigFuzz` + - `Microsoft.Data.SqlClient.Ctaip`: `certAuth` + - `Microsoft.Data.SqlClient.sni`: `master`, `release/6.0` +- **Unused marker text**: `UNUSED - WILL BE DELETED SHORTLY` + +## Workflow + +### 1. List all variable groups + +``` +az pipelines variable-group list --org --project -o json +``` + +- Save the output. +- Identify which groups are already marked with the unused marker text and which are active. +- Use **fuzzy matching** when detecting the unused marker: check for the presence of both "unused" and "delete" (case-insensitive) in the description, since the actual marker text may vary (e.g. a person's name inserted before "WILL DELETE"). +- In later steps, ignore the current group description when determining usage to avoid biasing the search results. + +### 2. Search repos for variable group references + +For each repo/branch combination, use the Azure DevOps REST API (Items endpoint) to: + +1. List all files recursively in the repo at the given branch. +2. Filter to `.yml` and `.yaml` files. +3. Fetch the content of each YAML file. +4. Search for each variable group name using **exact-match** patterns that prevent prefix false positives (e.g., searching for `Foo` must not match `FooBar`). Match against these forms, ensuring the name is delimited by quotes or end-of-value (whitespace/newline/comment): + - `group: ''` (single-quoted — name bounded by quotes) + - `group: ""` (double-quoted — name bounded by quotes) + - `group: ` followed by end-of-line, whitespace, or `#` (unquoted — no trailing alphanumeric characters) + +Use a Bearer token from `az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" --query accessToken -o tsv`. + +REST API endpoints: +- **List items**: `{org}/{project}/_apis/git/repositories/{repo}/items?recursionLevel=Full&versionDescriptor.version={branch}&versionDescriptor.versionType=branch&api-version=7.1` +- **Get file content**: Same endpoint with `path={URL-encoded filePath}&$format=text` (URL-encode the `path` value; use `$format=text` to retrieve raw file content instead of JSON metadata) + +Avoid cloning repos. Only use the REST API to fetch file listings and content. + +**Performance & rate-limiting guidance**: +- Filter the file listing to paths likely to contain pipelines (e.g., `eng/`, `pipelines/`, or root-level YAML files) before fetching content, to reduce API calls. +- Add a short delay (e.g., 200ms) between file-content fetches to avoid hitting Azure DevOps rate limits. +- If a `429 Too Many Requests` or `503` response is received, back off exponentially (1s, 2s, 4s, …) and retry up to 3 times before logging a warning and moving on. + +### 3. Search Classic pipelines for variable group references + +Query all **enabled** Classic build and release pipeline definitions for variable group usage. + +#### Classic Build pipelines + +``` +GET {org}/{project}/_apis/build/definitions?api-version=7.1 +``` + +- Page through all results (`$top` / `continuationToken` if needed). +- Keep only definitions where `queueStatus` is **`enabled`**. +- For each enabled definition, fetch its full JSON: + ``` + GET {org}/{project}/_apis/build/definitions/{id}?api-version=7.1 + ``` +- Inspect the `variableGroups` array; each element has an `id` that maps to a variable group ID. + +#### Classic Release pipelines + +The Release API lives on the `vsrm.` sub-domain, but direct REST calls to that sub-domain may fail with SSL certificate errors for `.visualstudio.com` organizations. Use `az devops invoke` instead: + +``` +az devops invoke --area release --resource definitions \ + --org --route-parameters project= \ + --query-parameters '$expand=environments' '$top=200' \ + -o json +``` + +- Page through results using `continuation_token` if present in the response. +- Exclude definitions where `isDeleted` is `true`. +- Each definition can reference variable groups at two levels: + - **Definition level**: `variableGroups` array on the root object. + - **Stage/environment level**: each element in `environments` has its own `variableGroups` array. +- Collect all referenced variable group IDs from both levels. + +#### Recording results + +For every variable group ID found, record: +- The pipeline **name** and **type** (Build / Release). +- The stage name (for release-environment-level references). + +Merge these results with the repo/branch search results from step 2 so the summary in the next step covers both YAML and Classic usage. + +### 4. Summarize findings + +Present a clear summary table to the user **before making any changes**. The summary must include: + +- **Used groups**: group name, ID, which repos/branches and/or Classic pipelines reference it, and the proposed new description. +- **Unused groups**: group name, ID, current description, and confirmation it will be marked with the unused marker. +- **Surprise findings**: any group already marked unused that is actually still referenced (these should be un-marked). +- **No-change groups**: groups already marked unused and confirmed unused. + +### 5. Prompt for go/no-go + +**This is a mandatory gate — do NOT skip or auto-approve.** + +Ask the user to confirm before applying any changes and require an explicit selection from the options below (do not infer approval from ambiguous replies). Offer options: +- **Apply all** — apply every proposed change from step 4 +- **Apply subset** — let the user specify which groups to update (by name or ID) +- **Export script** — write all update commands to a shell script file for the user to inspect and run manually. Do NOT execute any updates. Save the script to a path the user specifies (default: `./audit-variable-group-updates.sh`). The script must include the full `az` CLI commands and REST API `curl` fallbacks (for AzureKeyVault-type groups) with comments identifying each group by name and ID. Mark the file executable. +- **Abort** — make no changes at all + +Do NOT proceed to step 6 unless the user selects "Apply all" or "Apply subset" and, for the subset case, clearly identifies which groups to update. If the user selects "Export script", generate the file and stop — do NOT execute step 6. If the user says "abort" or does not respond, stop here. + +### 6. Apply description updates + +**Prerequisites**: Step 5 must have completed with explicit user approval. If you have not received approval, do NOT execute this step. + +For each group that needs updating, try: + +``` +az pipelines variable-group update --group-id --description "" --org --project --detect false +``` + +**Fallback for AzureKeyVault-type groups**: The `az pipelines variable-group update` command may fail with 500 errors on groups whose `type` is `AzureKeyVault` (it tries to refresh the vault connection). When this happens, fall back to the REST API: + +1. `GET {org}/{project}/_apis/distributedtask/variablegroups/{id}?api-version=7.1` +2. Update **both** the top-level `description` field **and** every entry in `variableGroupProjectReferences[].description` in the JSON. +3. `PUT` the modified JSON back to the same URL with `Content-Type: application/json`. + +**Description rules**: +- **Used groups**: Prepend `[Used by: : , ; : ; Classic/: ] ` to the existing description (after stripping any previous `[Used by: ...]` prefix). `` is `Build` or `Release`. +- **Unused groups**: Set description to the unused marker text. +- **Incorrectly marked unused**: Replace the unused marker with `[Used by: ...]`. +- **Already correct**: Skip groups whose description would not change. + +Report the outcome of each update (success/failure) and a final tally. + +## Error handling + +- If a repo or branch does not exist or returns an error, log a warning and continue with the remaining repos/branches. +- If a variable group update fails, log the error and continue with the remaining updates. +- At the end, report any failures so the user can address them manually. From ab5324d9ce62eb974128c13ba56cff008811214a Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 14 May 2026 19:47:26 +1000 Subject: [PATCH 104/298] avoid Lock acquisition on ErrorCount/WarningCount when no errors exist (#4157) * avoid Lock acquisition on ErrorCount/WarningCount when no errors exist 1. _hasErrorOrWarning marked volatile (line 278) -- the field was already read without a lock at line 1212, so this makes the existing pattern and the new fast paths correct across all memory models. 2. ErrorCount fast path -- returns 0 immediately when _hasErrorOrWarning is false, skipping the lock. Safe because _hasErrorOrWarning is only set to false under the lock when _errors is also nulled. 3. WarningCount fast path -- same treatment. * Update src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update TdsParserStateObject.cs --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Microsoft/Data/SqlClient/TdsParserStateObject.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 b7fa5c865b..ad8651cebd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -275,7 +275,7 @@ private enum SnapshotStatus internal SqlErrorCollection _errors; internal SqlErrorCollection _warnings; internal object _errorAndWarningsLock = new object(); - private bool _hasErrorOrWarning; + private volatile bool _hasErrorOrWarning; // local exceptions to cache warnings and errors that occurred prior to sending attention internal SqlErrorCollection _preAttentionErrors; @@ -2672,6 +2672,12 @@ internal int ErrorCount { get { + if (!_hasErrorOrWarning && + Volatile.Read(ref _errors) == null && + Volatile.Read(ref _warnings) == null) + { + return 0; + } int count = 0; lock (_errorAndWarningsLock) { @@ -2713,6 +2719,10 @@ internal int WarningCount { get { + if (!_hasErrorOrWarning && _warnings == null) + { + return 0; + } int count = 0; lock (_errorAndWarningsLock) { From a561c787b23cd741b6be41a0b680ce2742415665 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Fri, 15 May 2026 06:06:32 -0700 Subject: [PATCH 105/298] Tests | Attempt to fix enable file stream failures + fix flaky tests (#4220) Co-authored-by: Copilot --- .../steps/configure-sql-server-win-step.yml | 117 ++++++++++++------ .../ParameterTest/SqlVariantParameterTests.cs | 1 + .../ManualTests/TracingTests/MetricsTest.cs | 1 + .../ConnectionFailoverTests.cs | 1 + 4 files changed, 79 insertions(+), 41 deletions(-) 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 50925d00d5..2e58e5013c 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 @@ -62,8 +62,21 @@ parameters: type: string default: $(LocalDbSharedInstanceName) +# Step execution order: +# 1. Enable TCP, NP & Firewall — Enable TCP and Named Pipes protocols, open firewall ports +# 2. Create SQL user — Create test login/user, grant sysadmin, enable SA; restarts SQL if needed +# 3. Enable FileStream [Win] — (conditional) Enable FileStream via WMI; sp_configure deferred to step 8 +# 4. Create FileStreamFolder — (conditional) Create the FileStream data directory +# 5. Setup SQL Alias — Register TCP aliases in x86/x64 registry paths +# 6. Add SQL Certificate — Generate self-signed cert, trust it, bind to SQL Server, grant key access +# 7. Restart SQL Server — Restart to pick up protocol, cert, and FileStream WMI changes; wait for readiness +# 8. Configure FileStream Access — (conditional) Run sp_configure filestream_access_level after restart +# 9. Start SQL Server Browser — Start the Browser service for named-instance discovery +# 10. Enable LocalDB — (conditional) Start and share the LocalDB instance + steps: + # Step 1: Enable TCP, Named Pipes protocols and configure Windows Firewall rules. # GOTCHA: We must use the Windows-only powershell task here instead of the cross-platform pwsh # task because we call some Windows-specific cmdlets. - powershell: | @@ -108,6 +121,9 @@ steps: displayName: 'Enable TCP, NP & Firewall [Win]' retryCountOnTaskFailure: 2 + # Step 2: Create test login and user with sysadmin privileges, enable and set the SA password. + # If SQL Server is unresponsive (e.g. after protocol changes), this step will restart it once + # and retry before failing. - powershell: | $password = "${{ parameters.saPassword }}" @@ -119,11 +135,19 @@ steps: Write-Host $machineName Import-Module "sqlps" + + # Determine the Windows service name for this SQL instance. + $serviceName = "${{parameters.instanceName }}" + if ("${{parameters.instanceName }}" -ne "MSSQLSERVER") { + $serviceName = "MSSQL`$${{parameters.instanceName }}" + } + + $hasRestarted = $false $tries = 0 while ($true) { $tries++ try { - Invoke-Sqlcmd -ServerInstance "$machineName" @" + Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @" CREATE LOGIN [${{parameters.user }}] WITH PASSWORD=N'$password', DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF; CREATE USER [${{parameters.user }}] FROM LOGIN [${{parameters.user }}]; @@ -133,11 +157,20 @@ steps: "@ break } catch { - if ($tries -ge 5) { - Write-Host "##[error]Failed to create database user after $tries tries." - break + if (-not $hasRestarted -and $tries -ge 5) { + # SQL Server may need a restart after protocol changes (TCP/NP). + Write-Host "Connection failed after $tries attempts. Restarting SQL Server ($serviceName) and retrying..." + Restart-Service -Name $serviceName -Force -ErrorAction Stop + $hasRestarted = $true + $tries = 0 + Start-Sleep -Seconds 5 + continue + } + if ($tries -ge 10) { + Write-Host "##[error]Failed to create database user after $tries tries (including a restart)." + throw } - Write-Host "Failed to connect to server. Retrying in 5 seconds..." + Write-Host "Failed to connect to server (attempt $tries). Retrying in 5 seconds..." Start-Sleep -Seconds 5 } } @@ -146,49 +179,21 @@ steps: SQL_USER: ${{parameters.user }} SQL_PASSWD: ${{ parameters.saPassword }} + # Step 3: Enable FileStream at the OS/WMI level (conditional on SQLRootPath being set). + # Only the WMI flag is toggled here. The T-SQL sp_configure call happens in step 8, + # after the full SQL Server restart in step 7, to avoid needing a double restart. - ${{ if ne(parameters.SQLRootPath, '') }}: - powershell: | - #Enable FileStream + #Enable FileStream via WMI. + # The sp_configure call is deferred to after the "Restart SQL Server [Win]" + # step so we don't need a separate restart here. $instance = "${{parameters.instanceName }}" $wmi = Get-WmiObject -Namespace "${{parameters.SQLRootPath }}" -Class FilestreamSettings | where {$_.InstanceName -eq $instance} $wmi.EnableFilestream(3, $instance) - - $machineName = $env:COMPUTERNAME - - if ("${{parameters.instanceName }}" -ne "MSSQLSERVER"){ - $machineName += "\${{parameters.instanceName }}" - } - - #Change the access level for FileStream for SQLServer - Set-ExecutionPolicy Unrestricted - Import-Module "sqlps" - - # Retry loop: SQL Server may be temporarily unavailable after enabling FileStream via WMI. - # Worst-case budget: 10 attempts x (5s connection timeout + 5s sleep) = ~100s - $tries = 0 - while ($true) { - $tries++ - try { - Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @" - EXEC sp_configure filestream_access_level, 2; - RECONFIGURE; - "@ - Write-Host "FileStream access level configured successfully." - break - } catch { - if ($tries -ge 10) { - Write-Host "##[error]Failed to configure FileStream access level after $tries tries." - throw - } - Write-Host "Failed to connect to SQL Server (attempt $tries/10). Retrying in 5 seconds..." - Start-Sleep -Seconds 5 - } - } + Write-Host "FileStream enabled via WMI for instance '$instance'." displayName: 'Enable FileStream [Win]' - env: - SQL_USER: ${{parameters.user }} - SQL_PASSWD: ${{ parameters.saPassword }} + # Step 4: Create the FileStream data directory (conditional on fileStreamDirectory being set). - ${{ if ne(parameters.FileStreamDirectory, '') }}: - powershell: | New-Item -Path ${{ parameters.fileStreamDirectory }} -ItemType Directory @@ -196,6 +201,8 @@ steps: retryCountOnTaskFailure: 1 continueOnError: true + # Step 5: Register TCP-based SQL Server aliases in both x86 and x64 registry hives + # so test connections using the alias name resolve to the correct host and port. - powershell: | $SQLServerName = ("{0}" -f [System.Net.Dns]::GetHostByName($env:computerName).HostName) Write-Host FQDN is: $SQLServerName @@ -214,6 +221,9 @@ steps: New-ItemProperty -Path ${{parameters.x64AliasRegistryPath }} -Name ${{parameters.SQLAliasName }} -PropertyType string -Value $TCPAliasName displayName: 'Setup SQL Alias [Win]' + # Step 6: Generate a self-signed TLS certificate, add it to the trusted root store, + # bind it to all SQL Server instances, and grant the SQL service account read access + # to the private key. - powershell: | # Create Certificate $computerDnsName = [System.Net.Dns]::Resolve($null).HostName @@ -251,6 +261,9 @@ steps: } displayName: 'Add SQL Certificate [Win]' + # Step 7: Restart SQL Server to apply all preceding configuration changes (protocols, + # certificate, FileStream WMI). Waits for SQL Server to accept connections before + # proceeding (up to ~160s). - powershell: | # You need to restart SQL Server for the change to persist # -Force takes care of any dependent services, like SQL Agent. @@ -295,6 +308,27 @@ steps: displayName: 'Restart SQL Server [Win]' + # Step 8: Configure FileStream access level via T-SQL after the restart so we don't + # need a separate restart in the "Enable FileStream [Win]" step. + - ${{ if ne(parameters.SQLRootPath, '') }}: + - powershell: | + $machineName = $env:COMPUTERNAME + if ("${{parameters.instanceName }}" -ne "MSSQLSERVER") { + $machineName += "\${{parameters.instanceName }}" + } + + Set-ExecutionPolicy Unrestricted + Import-Module "sqlps" + + Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @" + EXEC sp_configure filestream_access_level, 2; + RECONFIGURE; + "@ + Write-Host "FileStream access level configured successfully." + displayName: 'Configure FileStream Access Level [Win]' + + # Step 9: Start the SQL Server Browser service so that named instances can be + # discovered by clients. - powershell: | $arrService = Get-Service -Name "SQLBrowser" $arrService @@ -314,6 +348,7 @@ steps: } displayName: 'Start Sql Server Browser [Win]' + # Step 10: Start and share the LocalDB instance (conditional on enableLocalDB). - ${{ if parameters.enableLocalDB }}: - powershell: | #script to enable local db diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index d6d9791738..5f47576baa 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -210,6 +210,7 @@ public void SqlType_BulkCopyFromDataTable_RoundTripsCorrectly(object paramValue, /// [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(BulkCopySqlTypeTestData), DisableDiscoveryEnumeration = true)] + [Trait("Category", "flaky")] public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) { // Arrange diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs index af10a5824e..6f9abd12cb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs @@ -177,6 +177,7 @@ public void StasisCounters_Functional() Assert.Equal(0, SqlClientEventSourceProps.StasisConnections); } + [Trait("Category", "flaky")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public void TransactedConnectionPool_VerifyActiveConnectionCounters() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index d3a0bcd07b..33a7d7c600 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -527,6 +527,7 @@ public void TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail(uint } [Fact] + [Trait("Category", "flaky")] public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() { // Arrange From 57b65818ca3933a3a6765b5e8ad463441876013f Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Fri, 15 May 2026 18:53:10 +0100 Subject: [PATCH 106/298] Feature | Support SQL Graph column aliases in SqlBulkCopy (#3677) --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 93 +++++++++++- .../SqlClient/SqlBulkCopyColumnMapping.cs | 7 + .../SQL/SqlBulkCopyTest/CopyAllFromReader.cs | 12 +- .../SQL/SqlBulkCopyTest/SqlGraphTables.cs | 133 ++++++++++++++++++ 4 files changed, 236 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 3ebf10e7fc..414be57eb0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -154,12 +154,17 @@ public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed) // 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 + // Column aliases has 3 columns and n rows private const int MetaDataResultId = 1; private const int CollationResultId = 2; private const int CollationId = 3; + private const int ColumnAliasesResultId = 3; + private const int ColumnCanonicalNameColumnId = 0; + private const int ColumnAliasColumnId = 1; + private const int MAX_LENGTH = 0x7FFFFFFF; private const int DefaultCommandTimeout = 30; @@ -495,6 +500,15 @@ private string CreateInitialQuery() // // See: https://learn.microsoft.com/sql/relational-databases/graphs/sql-graph-architecture#syscolumns // + // Other columns have aliases assigned to them. The SQL Graph columns $node_id, $edge_id, + // $to_id and $from_id are actually aliases for columns with different canonical names. + // SqlBulkCopy generates these mappings by searching for columns with the below graph_type + // values. + // + // 2 = GRAPH_ID_COMPUTED = $node_id, $edge_id + // 5 = GRAPH_FROM_ID_COMPUTED = $from_id + // 8 = GRAPH_TO_ID_COMPUTED = $to_id + // // The column-name query is built as dynamic SQL and executed via sp_executesql so // that it is not compiled (and rejected) on SQL Server versions that lack the // graph_type column (e.g. SQL 2016). CatalogName and escapedObjectName are @@ -522,6 +536,13 @@ private string CreateInitialQuery() DECLARE @Column_Name_Query NVARCHAR(MAX); DECLARE @Column_Names NVARCHAR(MAX) = NULL; +CREATE TABLE #Column_Aliases +( + [Canonical_Column_Name] SYSNAME, + [Canonical_Column_Id] INT, + [Aliased_Column_Name] SYSNAME +) + IF CAST(SERVERPROPERTY('EngineEdition') AS INT) = 6 BEGIN SET @Column_Name_Query_SELECT = N'SELECT @Column_Names = STRING_AGG(CAST(QUOTENAME([name]) AS NVARCHAR(MAX)), '', '') WITHIN GROUP (ORDER BY [column_id] ASC)'; @@ -536,6 +557,17 @@ IF CAST(SERVERPROPERTY('EngineEdition') AS INT) = 6 IF EXISTS (SELECT TOP 1 * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.all_columns') AND [name] = 'graph_type') BEGIN SET @Column_Name_Query_FILTER = N'WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) NOT IN (1, 3, 4, 6, 7)'; + + EXEC sp_executesql N' + INSERT INTO #Column_Aliases ([Canonical_Column_Name], [Canonical_Column_Id], [Aliased_Column_Name]) + SELECT [name], [column_id], ''$to_id'' FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 8 + UNION ALL + SELECT [name], [column_id], ''$from_id'' FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 5 + UNION ALL + SELECT [name], [column_id], ''$edge_id'' FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 2 AND [name] LIKE ''$edge[_]id[_]%'' + UNION ALL + SELECT [name], [column_id], ''$node_id'' FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 2 AND [name] LIKE ''$node[_]id[_]%''', + N'@Object_ID INT', @Object_ID = @Object_ID END ELSE BEGIN @@ -551,6 +583,13 @@ IF EXISTS (SELECT TOP 1 * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sy SET FMTONLY OFF; EXEC {CatalogName}..{TableCollationsStoredProc} N'{SchemaName}.{TableName}'; + +SELECT [Canonical_Column_Name], [Aliased_Column_Name] +FROM #Column_Aliases +WHERE [Aliased_Column_Name] NOT IN (SELECT [name] FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID) +ORDER BY [Canonical_Column_Id] ASC + +DROP TABLE #Column_Aliases """; } @@ -647,9 +686,9 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i // Keep track of any result columns that we don't have a local // mapping for. #if NETFRAMEWORK - HashSet unmatchedColumns = new(); + HashSet unmatchedColumns = new(StringComparer.OrdinalIgnoreCase); #else - HashSet unmatchedColumns = new(_localColumnMappings.Count); + HashSet unmatchedColumns = new(_localColumnMappings.Count, StringComparer.OrdinalIgnoreCase); #endif // Start by assuming all locally mapped Destination columns will be @@ -659,6 +698,50 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i unmatchedColumns.Add(_localColumnMappings[i].DestinationColumn); } + // Apply any necessary column aliases. If an aliased name exists in the + // local column mappings but the canonical name does not, update them. + Result columnAliasResults = internalResults[ColumnAliasesResultId]; + for (int i = 0; i < columnAliasResults.Count; i++) + { + Row aliasRow = columnAliasResults[i]; + SqlString canonicalName = (SqlString)aliasRow[ColumnCanonicalNameColumnId]; + SqlString aliasedName = (SqlString)aliasRow[ColumnAliasColumnId]; + + if (canonicalName.IsNull || aliasedName.IsNull) + { + continue; + } + + string canonical = canonicalName.Value; + bool canonicalNameExists = unmatchedColumns.Contains(canonical) + // The destination columns might be escaped. If so, search for those instead + || unmatchedColumns.Contains(SqlServerEscapeHelper.EscapeIdentifier(canonical)); + + if (canonicalNameExists) + { + continue; + } + + // The canonical name does not exist. Look for a local column mapping which matches + // the alias (or its escaped variant) and replace its name with its canonical name. + string alias = aliasedName.Value; + string escapedAlias = SqlServerEscapeHelper.EscapeIdentifier(alias); + + for (int j = 0; j < _localColumnMappings.Count; j++) + { + if (unmatchedColumns.Comparer.Equals(_localColumnMappings[j].DestinationColumn, alias) + || unmatchedColumns.Comparer.Equals(_localColumnMappings[j].DestinationColumn, escapedAlias)) + { + unmatchedColumns.Remove(_localColumnMappings[j].DestinationColumn); + + unmatchedColumns.Add(canonical); + _localColumnMappings[j].MappedDestinationColumn = canonical; + + break; + } + } + } + // Flag to remember whether or not we need to append a comma before // the next column in the command text. bool appendComma = false; @@ -682,7 +765,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i // 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) + && UnquotedName(localColumn.MappedDestinationColumn) != metadata.column) { // Yes, so move on to the next local column. continue; @@ -692,8 +775,8 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i matched = true; // Remove it from our unmatched set. - unmatchedColumns.Remove(localColumn.DestinationColumn); - + unmatchedColumns.Remove(localColumn.MappedDestinationColumn); + // Check for column types that we refuse to bulk load, even // though we found a match. // diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnMapping.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnMapping.cs index 734e0615a7..295fa56d0e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnMapping.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnMapping.cs @@ -19,6 +19,13 @@ public sealed class SqlBulkCopyColumnMapping internal int _internalDestinationColumnOrdinal; internal int _internalSourceColumnOrdinal; // -1 indicates an undetermined value + // Used by SqlBulkCopy to generate the correct column name after mapping alternate names. + internal string MappedDestinationColumn + { + get => field ?? DestinationColumn; + set => field = value; + } + /// public string DestinationColumn { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs index a0ccf69301..7dcb0a8111 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs @@ -40,6 +40,10 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) using (DbDataReader reader = srcCmd.ExecuteReader()) { IDictionary stats; + long expectedIduCount = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 1 : 0; + long expectedSelectCount = DataTestUtility.IsAzureSynapse ? 4 : 12; + long expectedSelectRows = DataTestUtility.IsAzureSynapse ? 4 : 14; + long expectedTransactions = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 1 : 0; using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { bulkcopy.DestinationTableName = dstTable; @@ -60,12 +64,12 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) DataTestUtility.AssertEqualsWithDescription((long)3, stats["BuffersReceived"], "Unexpected BuffersReceived value."); DataTestUtility.AssertEqualsWithDescription((long)3, stats["BuffersSent"], "Unexpected BuffersSent value."); - DataTestUtility.AssertEqualsWithDescription((long)0, stats["IduCount"], "Unexpected IduCount value."); - DataTestUtility.AssertEqualsWithDescription((long)11, stats["SelectCount"], "Unexpected SelectCount value."); + DataTestUtility.AssertEqualsWithDescription(expectedIduCount, stats["IduCount"], "Unexpected IduCount value."); + DataTestUtility.AssertEqualsWithDescription(expectedSelectCount, stats["SelectCount"], "Unexpected SelectCount value."); DataTestUtility.AssertEqualsWithDescription((long)3, stats["ServerRoundtrips"], "Unexpected ServerRoundtrips value."); - DataTestUtility.AssertEqualsWithDescription((long)14, stats["SelectRows"], "Unexpected SelectRows value."); + DataTestUtility.AssertEqualsWithDescription(expectedSelectRows, stats["SelectRows"], "Unexpected SelectRows value."); DataTestUtility.AssertEqualsWithDescription((long)2, stats["SumResultSets"], "Unexpected SumResultSets value."); - DataTestUtility.AssertEqualsWithDescription((long)0, stats["Transactions"], "Unexpected Transactions value."); + DataTestUtility.AssertEqualsWithDescription(expectedTransactions, stats["Transactions"], "Unexpected Transactions value."); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs index 621784bafa..a5b702f6b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs @@ -35,5 +35,138 @@ public void WriteToServer_CopyToSqlGraphNodeTable_Succeeds() nodeCopy.ColumnMappings.Add("Name", "Name"); nodeCopy.WriteToServer(nodes); } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsAtLeastSQL2017))] + public void WriteToServer_CopyToAliasedColumnName_Succeeds() + { + string connectionString = DataTestUtility.TCPConnectionString; + + using SqlConnection dstConn = new SqlConnection(connectionString); + using DataTable edges = new DataTable() + { + Columns = { new DataColumn("To_ID", typeof(string)), new DataColumn("From_ID", typeof(string)), new DataColumn("Description", typeof(string)) } + }; + + dstConn.Open(); + + using Table srcNodeTable = new(dstConn, "SqlGraph_NodeByAlias", "(Id INT PRIMARY KEY IDENTITY(1,1), [Name] VARCHAR(100)) AS NODE"); + using Table dstEdgeTable = new(dstConn, "SqlGraph_EdgeByAlias", "([Description] VARCHAR(100)) AS EDGE"); + + string sampleNodeDataCommand = @$"INSERT INTO {srcNodeTable.Name} ([Name]) SELECT LEFT([name], 100) FROM sys.sysobjects"; + using (SqlCommand insertSampleNodes = new(sampleNodeDataCommand, dstConn)) + { + insertSampleNodes.ExecuteNonQuery(); + } + + using (SqlCommand nodeQuery = new SqlCommand($"SELECT $node_id FROM {srcNodeTable.Name}", dstConn)) + using (SqlDataReader reader = nodeQuery.ExecuteReader()) + { + bool firstRead = reader.Read(); + string toId; + string fromId; + + Assert.True(firstRead); + toId = reader.GetString(0); + + while (reader.Read()) + { + fromId = reader.GetString(0); + + edges.Rows.Add(toId, fromId, "Test Description"); + toId = fromId; + } + } + + using (SqlBulkCopy edgeCopy = new(dstConn)) + { + edgeCopy.DestinationTableName = dstEdgeTable.Name; + edgeCopy.ColumnMappings.Add("To_ID", "$to_id"); + edgeCopy.ColumnMappings.Add("From_ID", "$from_id"); + edgeCopy.ColumnMappings.Add("Description", "Description"); + + edgeCopy.WriteToServer(edges); + } + + // Read the values back, comparing to the source DataTable + using SqlCommand dstVerificationCommand = new($"SELECT $to_id, $from_id, [Description] FROM {dstEdgeTable.Name} ORDER BY $to_id ASC", dstConn); + using SqlDataReader dstVerificationReader = dstVerificationCommand.ExecuteReader(); + int currentRow = 0; + DataRow[] sortedRows = edges.Select(filterExpression: null, sort: "To_ID ASC"); + + while (dstVerificationReader.Read()) + { + string toId = dstVerificationReader.GetString(0); + string fromId = dstVerificationReader.GetString(1); + string description = dstVerificationReader.GetString(2); + DataRow currSourceRow = sortedRows[currentRow]; + + Assert.Equal(currSourceRow["To_ID"], toId); + Assert.Equal(currSourceRow["From_ID"], fromId); + Assert.Equal(currSourceRow["Description"], description); + + currentRow++; + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsAtLeastSQL2017))] + public void WriteToServer_CopyToTableWithSameNameAsColumnAlias_Succeeds() + { + string connectionString = DataTestUtility.TCPConnectionString; + + using SqlConnection dstConn = new SqlConnection(connectionString); + using DataTable nodes = new DataTable() + { + Columns = { new DataColumn("Name", typeof(string)) } + }; + + dstConn.Open(); + + for (int i = 0; i < 5; i++) + { + nodes.Rows.Add($"Name {i}"); + } + + using Table dstGraphTable = new(dstConn, "SqlGraph_NodeWithAlias", "(Id INT PRIMARY KEY IDENTITY(1,1), [Name] VARCHAR(100), [$node_id] VARCHAR(100)) AS NODE"); + using Table dstNormalTable = new(dstConn, "NonGraph_NodeWithAlias", "(Id INT PRIMARY KEY IDENTITY(1,1), [Name] VARCHAR(100), [$node_id] VARCHAR(100))"); + + using (SqlBulkCopy nodeCopy = new SqlBulkCopy(dstConn)) + { + nodeCopy.DestinationTableName = dstGraphTable.Name; + nodeCopy.ColumnMappings.Add("Name", "Name"); + nodeCopy.ColumnMappings.Add("Name", "$node_id"); + nodeCopy.WriteToServer(nodes); + + nodeCopy.DestinationTableName = dstNormalTable.Name; + nodeCopy.WriteToServer(nodes); + } + + // Read the values back, ensuring that we haven't overwritten the $node_id alias with the contents of the [$node_id] column. + // SELECTing $node_id will read the SQL Graph's node ID, SELECTing [$node_id] will read the column named $node_id. + using (SqlCommand graphVerificationCommand = new SqlCommand($"SELECT Id, $node_id, [$node_id], Name FROM {dstGraphTable.Name}", dstConn)) + using (SqlDataReader reader = graphVerificationCommand.ExecuteReader()) + { + while (reader.Read()) + { + string aliasNodeId = reader.GetString(1); + string physicalNodeId = reader.GetString(2); + string name = reader.GetString(3); + + Assert.NotEqual(physicalNodeId, aliasNodeId); + Assert.Equal(name, physicalNodeId); + } + } + + using (SqlCommand normalVerificationCommand = new SqlCommand($"SELECT [$node_id], Name FROM {dstNormalTable.Name}", dstConn)) + using (SqlDataReader reader = normalVerificationCommand.ExecuteReader()) + { + while (reader.Read()) + { + string physicalNodeId = reader.GetString(0); + string name = reader.GetString(1); + + Assert.Equal(name, physicalNodeId); + } + } + } } } From b37896e1c78da282529a1583bdcd4756af18acd9 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 18 May 2026 17:14:10 +0100 Subject: [PATCH 107/298] Feature | Use hardcoded LCID mappings when decoding strings (#4212) * Use hardcoded LCID mappings when decoding strings * Reformat LCID/codepage mapping table, adding LCID descriptions * Correct debug assertions, remove unused using * Add unit tests for LocalesHelper --- .../Microsoft/Data/SqlClient/LocalesHelper.cs | 210 ++++++++++++++ .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 261 ------------------ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 87 +----- .../Data/SqlClient/LocalesHelperTest.cs | 105 +++++++ 4 files changed, 319 insertions(+), 344 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalesHelper.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalesHelperTest.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalesHelper.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalesHelper.cs new file mode 100644 index 0000000000..a0881453d8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalesHelper.cs @@ -0,0 +1,210 @@ +// 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; + +#nullable enable + +namespace Microsoft.Data.SqlClient; + +internal static class LocalesHelper +{ + private const int LocaleMappingCount = 207; + + /// + /// Array copied directly from tdssort.h from luxor. + /// Use the sort ID as an index into this array to retrieve the code page. + /// If the value is zero, the index is not a valid sort ID. + /// + private static ReadOnlySpan SortIdToCodePageMappings => [ + // 0-29: reserved + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // 30-35 + 437, 437, 437, 437, 437, 437, + // 36-39: reserved + 0, 0, 0, 0, + // 40-45 + 850, 850, 850, 850, 850, 850, + // 46-48: reserved + 0, 0, 0, + // 49-61 + 850, 1252, 1252, 1252, 1252, 1252, 850, 850, 850, 850, 850, 850, 850, + // 62-70: reserved + 0, 0, 0, 0, 0, 0, 0, 0, 0, + // 71-75 + 1252, 1252, 1252, 1252, 1252, + // 76-79: reserved + 0, 0, 0, 0, + // 80-98 + 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, + // 99-103: reserved + 0, 0, 0, 0, 0, + // 104-108 + 1251, 1251, 1251, 1251, 1251, + // 109-111: reserved + 0, 0, 0, + // 112-114 + 1253, 1253, 1253, + // 115-119: reserved + 0, 0, 0, 0, 0, + // 120-122 + 1253, 1253, 1253, + // 123: reserved + 0, + // 124 + 1253, + // 125-127: reserved + 0, 0, 0, + // 128-130 + 1254, 1254, 1254, + // 131-135: reserved + 0, 0, 0, 0, 0, + // 136-138 + 1255, 1255, 1255, + // 139-143: reserved + 0, 0, 0, 0, 0, + // 144-146 + 1256, 1256, 1256, + // 147-151: reserved + 0, 0, 0, 0, 0, + // 152-160 + 1257, 1257, 1257, 1257, 1257, 1257, 1257, 1257, 1257, + // 161-182: reserved + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // 183-186 + 1252, 1252, 1252, 1252, + // 187-191: reserved + 0, 0, 0, 0, 0, + // 192-206 + 932, 932, 949, 949, 950, 950, 936, 936, 932, 949, 950, 936, 874, 874, 874, + // 207-209: reserved + 0, 0, 0, + // 210-217 + 1252, 1252, 1252, 1252, 1252, 1252, 1252, 1252, + // 218-255 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]; + + /// + /// Maps LCIDs to code pages. Each pair of values in the array represents an LCID and its corresponding code page. + /// This means that even indexes in the array are LCIDs, and the following odd index is the code page for that LCID. + /// + /// + /// This is aligned with the mssql-jdbc driver. See the following file for the original data: + /// https://github.com/microsoft/mssql-jdbc/blob/23e935b846af5f112f445289e89d54a379e8b8ed/src/main/java/com/microsoft/sqlserver/jdbc/SQLCollation.java#L141-L361 + /// + private static ReadOnlySpan LcidToCodePageMappings => [ + // ar_SA bg_BG ca_ES zh_TW cs_CZ da_DK de_DE el_GR + 0x0401, 1256, 0x0402, 1251, 0x0403, 1252, 0x0404, 950, 0x0405, 1250, 0x0406, 1252, 0x0407, 1252, 0x0408, 1253, + + // en_US es_ES_tradnl fi_FI fr_FR he_IL hu_HU is_IS it_IT + 0x0409, 1252, 0x040a, 1252, 0x040b, 1252, 0x040c, 1252, 0x040d, 1255, 0x040e, 1250, 0x040f, 1252, 0x0410, 1252, + + // ja_JP ko_KR nl_NL nb_NO pl_PL pt_BR rm_CH ro_RO + 0x0411, 932, 0x0412, 949, 0x0413, 1252, 0x0414, 1252, 0x0415, 1250, 0x0416, 1252, 0x0417, 1252, 0x0418, 1250, + + // ru_RU hr_HR sk_SK sq_AL sv_SE th_TH tr_TR ur_PK + 0x0419, 1251, 0x041a, 1250, 0x041b, 1250, 0x041c, 1250, 0x041d, 1252, 0x041e, 874, 0x041f, 1254, 0x0420, 1256, + + // id_ID uk_UA be_BY sl_SI et_EE lv_LV lt_LT tg_Cyrl_TJ + 0x0421, 1252, 0x0422, 1251, 0x0423, 1251, 0x0424, 1250, 0x0425, 1257, 0x0426, 1257, 0x0427, 1257, 0x0428, 1251, + + // fa_IR vi_VN hy_AM az_Latn_AZ eu_ES wen_DE mk_MK tn_ZA + 0x0429, 1256, 0x042a, 1258, 0x042b, 1252, 0x042c, 1254, 0x042d, 1252, 0x042e, 1252, 0x042f, 1251, 0x0432, 1252, + + // xh_ZA zu_ZA af_ZA ka_GE fo_FO hi_IN mt_MT se_NO + 0x0434, 1252, 0x0435, 1252, 0x0436, 1252, 0x0437, 1252, 0x0438, 1252, 0x0439, 1200, 0x043a, 1200, 0x043b, 1252, + + // ms_MY kk_KZ ky_KG sw_KE tk_TM uz_Latn_UZ tt_RU bn_IN + 0x043e, 1252, 0x043f, 1251, 0x0440, 1251, 0x0441, 1252, 0x0442, 1250, 0x0443, 1254, 0x0444, 1251, 0x0445, 1200, + + // pa_IN gu_IN or_IN ta_IN te_IN kn_IN ml_IN as_IN + 0x0446, 1200, 0x0447, 1200, 0x0448, 1200, 0x0449, 1200, 0x044a, 1200, 0x044b, 1200, 0x044c, 1200, 0x044d, 1200, + + // mr_IN sa_IN mn_MN bo_CN cy_GB km_KH lo_LA gl_ES + 0x044e, 1200, 0x044f, 1200, 0x0450, 1251, 0x0451, 1200, 0x0452, 1252, 0x0453, 1200, 0x0454, 1200, 0x0456, 1252, + + // kok_IN syr_SY si_LK iu_Cans_CA am_ET ne_NP fy_NL ps_AF + 0x0457, 1200, 0x045a, 1200, 0x045b, 1200, 0x045d, 1252, 0x045e, 1252, 0x0461, 1200, 0x0462, 1252, 0x0463, 1200, + + // fil_PH dv_MV ha_Latn_NG yo_NG quz_BO nso_ZA ba_RU lb_LU + 0x0464, 1252, 0x0465, 1200, 0x0468, 1252, 0x046a, 1252, 0x046b, 1252, 0x046c, 1252, 0x046d, 1251, 0x046e, 1252, + + // kl_GL ig_NG ii_CN arn_CL moh_CA br_FR ug_CN mi_NZ + 0x046f, 1252, 0x0470, 1252, 0x0478, 1252, 0x047a, 1252, 0x047c, 1252, 0x047e, 1252, 0x0480, 1256, 0x0481, 1200, + + // oc_FR co_FR gsw_FR sah_RU qut_GT rw_RW wo_SN prs_AF + 0x0482, 1252, 0x0483, 1252, 0x0484, 1252, 0x0485, 1251, 0x0486, 1252, 0x0487, 1252, 0x0488, 1252, 0x048c, 1256, + + // ar_IQ zh_CN de_CH en_GB es_MX fr_BE it_CH nl_BE + 0x0801, 1256, 0x0804, 936, 0x0807, 1252, 0x0809, 1252, 0x080a, 1252, 0x080c, 1252, 0x0810, 1252, 0x0813, 1252, + + // nn_NO pt_PT sr_Latn_CS sv_FI Lithuanian_Classic az_Cyrl_AZ dsb_DE se_SE + 0x0814, 1252, 0x0816, 1252, 0x081a, 1250, 0x081d, 1252, 0x0827, 1257, 0x082c, 1251, 0x082e, 1252, 0x083b, 1252, + + // ga_IE ms_BN uz_Cyrl_UZ bn_BD mn_Mong_CN iu_Latn_CA tzm_Latn_DZ quz_EC + 0x083c, 1252, 0x083e, 1252, 0x0843, 1251, 0x0845, 1200, 0x0850, 1251, 0x085d, 1252, 0x085f, 1252, 0x086b, 1252, + + // ar_EG zh_HK de_AT en_AU es_ES fr_CA sr_Cyrl_CS se_FI + 0x0c01, 1256, 0x0c04, 950, 0x0c07, 1252, 0x0c09, 1252, 0x0c0a, 1252, 0x0c0c, 1252, 0x0c1a, 1251, 0x0c3b, 1252, + + // quz_PE ar_LY zh_SG de_LU en_CA es_GT fr_CH hr_BA + 0x0c6b, 1252, 0x1001, 1256, 0x1004, 936, 0x1007, 1252, 0x1009, 1252, 0x100a, 1252, 0x100c, 1252, 0x101a, 1250, + + // smj_NO ar_DZ zh_MO de_LI en_NZ es_CR fr_LU bs_Latn_BA + 0x103b, 1252, 0x1401, 1256, 0x1404, 950, 0x1407, 1252, 0x1409, 1252, 0x140a, 1252, 0x140c, 1252, 0x141a, 1250, + + // smj_SE ar_MA en_IE es_PA fr_MC sr_Latn_BA sma_NO ar_TN + 0x143b, 1252, 0x1801, 1256, 0x1809, 1252, 0x180a, 1252, 0x180c, 1252, 0x181a, 1250, 0x183b, 1252, 0x1c01, 1256, + + // en_ZA es_DO sr_Cyrl_BA sma_SB ar_OM en_JM es_VE bs_Cyrl_BA + 0x1c09, 1252, 0x1c0a, 1252, 0x1c1a, 1251, 0x1c3b, 1252, 0x2001, 1256, 0x2009, 1252, 0x200a, 1252, 0x201a, 1251, + + // sms_FI ar_YE en_CB es_CO smn_FI ar_SY en_BZ es_PE + 0x203b, 1252, 0x2401, 1256, 0x2409, 1252, 0x240a, 1252, 0x243b, 1252, 0x2801, 1256, 0x2809, 1252, 0x280a, 1252, + + // ar_JO en_TT es_AR ar_LB en_ZW es_EC ar_KW en_PH + 0x2c01, 1256, 0x2c09, 1252, 0x2c0a, 1252, 0x3001, 1256, 0x3009, 1252, 0x300a, 1252, 0x3401, 1256, 0x3409, 1252, + + // es_CL ar_AE es_UY ar_BH es_PY ar_QA en_IN es_BO + 0x340a, 1252, 0x3801, 1256, 0x380a, 1252, 0x3c01, 1256, 0x3c0a, 1252, 0x4001, 1256, 0x4009, 1252, 0x400a, 1252, + + // en_MY es_SV en_SG es_HN es_NI es_PR es_US + 0x4409, 1252, 0x440a, 1252, 0x4809, 1252, 0x480a, 1252, 0x4c0a, 1252, 0x500a, 1252, 0x540a, 1252, + ]; + + public static bool TryGetCodePage(int lcid, int sortId, out int codePage) + { + if (sortId != 0) + { + codePage = (uint)sortId < SortIdToCodePageMappings.Length + ? SortIdToCodePageMappings[sortId] + : 0; + } + else + { + codePage = GetCodePageByLcid(lcid & 0xFFFF); + } + return codePage != 0; + } + + private static int GetCodePageByLcid(int lcid) + { + Debug.Assert(LcidToCodePageMappings.Length == LocaleMappingCount * 2); + + ReadOnlySpan lcidMappings = LcidToCodePageMappings; + int mappingIndex = lcidMappings.IndexOf(lcid); + + // If LCID is not found, or if it's found at an odd index (which would be a code page, not an LCID), return zero + // to indicate that the code page could not be found. + // Also include an explicit bounds check to ensure that the method doesn't contain any exception paths. + if (mappingIndex == -1 || (mappingIndex % 2) != 0 || ((uint)mappingIndex + 1) >= lcidMappings.Length) + { + return 0; + } + + return lcidMappings[mappingIndex + 1]; + } +} 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 cf8f4b3893..25a489d91d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -630,267 +630,6 @@ public enum ActiveDirectoryWorkflow : byte internal const ushort MAXLEN_ATTACHDBFILE = 260; // the filename for a database that is to be attached during the connection process internal const ushort MAXLEN_NEWPASSWORD = 128; // new password for the specified login. - - // array copied directly from tdssort.h from luxor - public static readonly ushort[] CODE_PAGE_FROM_SORT_ID = { - 0, /* 0 */ - 0, /* 1 */ - 0, /* 2 */ - 0, /* 3 */ - 0, /* 4 */ - 0, /* 5 */ - 0, /* 6 */ - 0, /* 7 */ - 0, /* 8 */ - 0, /* 9 */ - 0, /* 10 */ - 0, /* 11 */ - 0, /* 12 */ - 0, /* 13 */ - 0, /* 14 */ - 0, /* 15 */ - 0, /* 16 */ - 0, /* 17 */ - 0, /* 18 */ - 0, /* 19 */ - 0, /* 20 */ - 0, /* 21 */ - 0, /* 22 */ - 0, /* 23 */ - 0, /* 24 */ - 0, /* 25 */ - 0, /* 26 */ - 0, /* 27 */ - 0, /* 28 */ - 0, /* 29 */ - 437, /* 30 */ - 437, /* 31 */ - 437, /* 32 */ - 437, /* 33 */ - 437, /* 34 */ - 0, /* 35 */ - 0, /* 36 */ - 0, /* 37 */ - 0, /* 38 */ - 0, /* 39 */ - 850, /* 40 */ - 850, /* 41 */ - 850, /* 42 */ - 850, /* 43 */ - 850, /* 44 */ - 0, /* 45 */ - 0, /* 46 */ - 0, /* 47 */ - 0, /* 48 */ - 850, /* 49 */ - 1252, /* 50 */ - 1252, /* 51 */ - 1252, /* 52 */ - 1252, /* 53 */ - 1252, /* 54 */ - 850, /* 55 */ - 850, /* 56 */ - 850, /* 57 */ - 850, /* 58 */ - 850, /* 59 */ - 850, /* 60 */ - 850, /* 61 */ - 0, /* 62 */ - 0, /* 63 */ - 0, /* 64 */ - 0, /* 65 */ - 0, /* 66 */ - 0, /* 67 */ - 0, /* 68 */ - 0, /* 69 */ - 0, /* 70 */ - 1252, /* 71 */ - 1252, /* 72 */ - 1252, /* 73 */ - 1252, /* 74 */ - 1252, /* 75 */ - 0, /* 76 */ - 0, /* 77 */ - 0, /* 78 */ - 0, /* 79 */ - 1250, /* 80 */ - 1250, /* 81 */ - 1250, /* 82 */ - 1250, /* 83 */ - 1250, /* 84 */ - 1250, /* 85 */ - 1250, /* 86 */ - 1250, /* 87 */ - 1250, /* 88 */ - 1250, /* 89 */ - 1250, /* 90 */ - 1250, /* 91 */ - 1250, /* 92 */ - 1250, /* 93 */ - 1250, /* 94 */ - 1250, /* 95 */ - 1250, /* 96 */ - 1250, /* 97 */ - 1250, /* 98 */ - 0, /* 99 */ - 0, /* 100 */ - 0, /* 101 */ - 0, /* 102 */ - 0, /* 103 */ - 1251, /* 104 */ - 1251, /* 105 */ - 1251, /* 106 */ - 1251, /* 107 */ - 1251, /* 108 */ - 0, /* 109 */ - 0, /* 110 */ - 0, /* 111 */ - 1253, /* 112 */ - 1253, /* 113 */ - 1253, /* 114 */ - 0, /* 115 */ - 0, /* 116 */ - 0, /* 117 */ - 0, /* 118 */ - 0, /* 119 */ - 1253, /* 120 */ - 1253, /* 121 */ - 1253, /* 122 */ - 0, /* 123 */ - 1253, /* 124 */ - 0, /* 125 */ - 0, /* 126 */ - 0, /* 127 */ - 1254, /* 128 */ - 1254, /* 129 */ - 1254, /* 130 */ - 0, /* 131 */ - 0, /* 132 */ - 0, /* 133 */ - 0, /* 134 */ - 0, /* 135 */ - 1255, /* 136 */ - 1255, /* 137 */ - 1255, /* 138 */ - 0, /* 139 */ - 0, /* 140 */ - 0, /* 141 */ - 0, /* 142 */ - 0, /* 143 */ - 1256, /* 144 */ - 1256, /* 145 */ - 1256, /* 146 */ - 0, /* 147 */ - 0, /* 148 */ - 0, /* 149 */ - 0, /* 150 */ - 0, /* 151 */ - 1257, /* 152 */ - 1257, /* 153 */ - 1257, /* 154 */ - 1257, /* 155 */ - 1257, /* 156 */ - 1257, /* 157 */ - 1257, /* 158 */ - 1257, /* 159 */ - 1257, /* 160 */ - 0, /* 161 */ - 0, /* 162 */ - 0, /* 163 */ - 0, /* 164 */ - 0, /* 165 */ - 0, /* 166 */ - 0, /* 167 */ - 0, /* 168 */ - 0, /* 169 */ - 0, /* 170 */ - 0, /* 171 */ - 0, /* 172 */ - 0, /* 173 */ - 0, /* 174 */ - 0, /* 175 */ - 0, /* 176 */ - 0, /* 177 */ - 0, /* 178 */ - 0, /* 179 */ - 0, /* 180 */ - 0, /* 181 */ - 0, /* 182 */ - 1252, /* 183 */ - 1252, /* 184 */ - 1252, /* 185 */ - 1252, /* 186 */ - 0, /* 187 */ - 0, /* 188 */ - 0, /* 189 */ - 0, /* 190 */ - 0, /* 191 */ - 932, /* 192 */ - 932, /* 193 */ - 949, /* 194 */ - 949, /* 195 */ - 950, /* 196 */ - 950, /* 197 */ - 936, /* 198 */ - 936, /* 199 */ - 932, /* 200 */ - 949, /* 201 */ - 950, /* 202 */ - 936, /* 203 */ - 874, /* 204 */ - 874, /* 205 */ - 874, /* 206 */ - 0, /* 207 */ - 0, /* 208 */ - 0, /* 209 */ - 1252, /* 210 */ - 1252, /* 211 */ - 1252, /* 212 */ - 1252, /* 213 */ - 1252, /* 214 */ - 1252, /* 215 */ - 1252, /* 216 */ - 1252, /* 217 */ - 0, /* 218 */ - 0, /* 219 */ - 0, /* 220 */ - 0, /* 221 */ - 0, /* 222 */ - 0, /* 223 */ - 0, /* 224 */ - 0, /* 225 */ - 0, /* 226 */ - 0, /* 227 */ - 0, /* 228 */ - 0, /* 229 */ - 0, /* 230 */ - 0, /* 231 */ - 0, /* 232 */ - 0, /* 233 */ - 0, /* 234 */ - 0, /* 235 */ - 0, /* 236 */ - 0, /* 237 */ - 0, /* 238 */ - 0, /* 239 */ - 0, /* 240 */ - 0, /* 241 */ - 0, /* 242 */ - 0, /* 243 */ - 0, /* 244 */ - 0, /* 245 */ - 0, /* 246 */ - 0, /* 247 */ - 0, /* 248 */ - 0, /* 249 */ - 0, /* 250 */ - 0, /* 251 */ - 0, /* 252 */ - 0, /* 253 */ - 0, /* 254 */ - 0, /* 255 */ - }; - #if NETFRAMEWORK internal enum UDTFormatType { 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 53b89fbc90..8bfedc3539 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -5103,93 +5103,14 @@ private void WriteCollation(SqlCollation collation, TdsParserStateObject stateOb internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) { - int codePage = 0; + bool success = LocalesHelper.TryGetCodePage(collation.LCID, collation._sortId, out int codePage); - if (0 != collation._sortId) + if (!success) { - codePage = TdsEnums.CODE_PAGE_FROM_SORT_ID[collation._sortId]; - Debug.Assert(0 != codePage, "GetCodePage accessed codepage array and produced 0!, sortID =" + ((Byte)(collation._sortId)).ToString((IFormatProvider)null)); + ThrowUnsupportedCollationEncountered(stateObj); } - else - { - int cultureId = collation.LCID; - bool success = false; - - try - { - codePage = CultureInfo.GetCultureInfo(cultureId).TextInfo.ANSICodePage; - - // SqlHot 50001398: CodePage can be zero, but we should defer such errors until - // we actually MUST use the code page (i.e. don't error if no ANSI data is sent). - success = true; - } - catch (ArgumentException e) - { - ADP.TraceExceptionWithoutRethrow(e); - } - - // If we failed, it is quite possible this is because certain culture id's - // were removed in Win2k and beyond, however Sql Server still supports them. - // In this case we will mask off the sort id (the leading 1). If that fails, - // or we have a culture id other than the cases below, we throw an error and - // throw away the rest of the results. For additional info, see MDAC 65963. - // SqlHot 50001398: Sometimes GetCultureInfo will return CodePage 0 instead of throwing. - // This should be treated as an error and functionality switches into the following logic. - if (!success || codePage == 0) - { - switch (cultureId) - { - case 0x10404: // zh-TW - case 0x10804: // zh-CN - case 0x10c04: // zh-HK - case 0x11004: // zh-SG - case 0x11404: // zh-MO - case 0x10411: // ja-JP - case 0x10412: // ko-KR - // If one of the following special cases, mask out sortId and - // retry. - cultureId = cultureId & 0x03fff; - - try - { - codePage = new CultureInfo(cultureId).TextInfo.ANSICodePage; - success = true; - } - catch (ArgumentException e) - { - ADP.TraceExceptionWithoutRethrow(e); - } - break; - case 0x827: // Mapping Non-supported Lithuanian code page to supported Lithuanian. - try - { - codePage = new CultureInfo(0x427).TextInfo.ANSICodePage; - success = true; - } - catch (ArgumentException e) - { - ADP.TraceExceptionWithoutRethrow(e); - } - break; - case 0x43f: - codePage = 1251; // Kazakh code page based on SQL Server - break; - case 0x10437: - codePage = 1252; // Georgian code page based on SQL Server - break; - default: - break; - } - - if (!success) - { - ThrowUnsupportedCollationEncountered(stateObj); - } - - Debug.Assert(codePage >= 0, $"Invalid code page. codePage: {codePage}. cultureId: {cultureId}"); - } - } + Debug.Assert(codePage != 0, $"Invalid code page. codePage: {codePage}. cultureId: {collation.LCID}"); return codePage; } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalesHelperTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalesHelperTest.cs new file mode 100644 index 0000000000..0c79ebc92f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalesHelperTest.cs @@ -0,0 +1,105 @@ +// 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 Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests; + +/// +/// Provides unit tests which validate the behaviour of LocalesHelper. +/// +public class LocalesHelperTest +{ + /// + /// Verifies that a sort ID which would be out of the array's range is provided, + /// the method returns false rather than throwing an IndexOutOfRangeException. + /// + /// The sort ID. + [Theory] + [InlineData(int.MinValue)] + [InlineData(-1)] + [InlineData(256)] + [InlineData(int.MaxValue)] + public void OutOfRangeSortId_ReturnsFalse(int sortId) => + AssertFailingLookup(lcid: 0, sortId); + + /// + /// Verifies that if an invalid sort ID is provided, the method returns false. + /// + [Fact] + public void InvalidSortId_ReturnsFalse() => + AssertFailingLookup(lcid: 0, sortId: 37); + + /// + /// Verifies that if a valid sort ID which is known to map to code page 437 is + /// provided, a code page is returned. + /// + [Fact] + public void ValidSortId_ReturnsCodePage() => + AssertSuccessfulLookup(lcid: 0, sortId: 30, expectedCodePage: 437); + + /// + /// Verifies that if both a valid sort ID and a valid LCID are provided, the + /// code page associated with the sort ID takes priority. + /// + [Fact] + public void ValidSortIdCodePage_PrioritisedOverLcidCodePage() + { + bool sortIdCodePageRetrieved = LocalesHelper.TryGetCodePage(lcid: 0, sortId: 30, out int sortIdCodePage); + bool lcidCodePageRetrieved = LocalesHelper.TryGetCodePage(lcid: 0x0409, sortId: 0, out int lcidCodePage); + bool lcidAndSortIdCodePageRetrieved = LocalesHelper.TryGetCodePage(lcid: 0x0409, sortId: 30, out int sortIdAndLcidCodePage); + + Assert.True(sortIdCodePageRetrieved); + Assert.True(lcidCodePageRetrieved); + Assert.True(lcidAndSortIdCodePageRetrieved); + + Assert.Equal(437, sortIdCodePage); + Assert.Equal(1252, lcidCodePage); + + Assert.Equal(437, sortIdAndLcidCodePage); + } + + /// + /// Verifies that if an invalid LCID is provided, the method returns false. + /// + [Fact] + public void MissingLcid_ReturnsFalse() => + AssertFailingLookup(lcid: 0xFFFF, sortId: 0); + + /// + /// Verifies that if a valid LCID which is known to map to code page 1252 is provided, + /// this code page is provided. + /// + /// The LCID which maps to code page 1252 + [Theory] + // en_US + [InlineData(0x0409)] + // en_US, but with other bits in the collation identifier set. + [InlineData(0x0001_0409)] + public void ValidLcid_ReturnsCodePage(int lcid) => + AssertSuccessfulLookup(lcid, sortId: 0, expectedCodePage: 1252); + + /// + /// Verifies that if an invalid LCID which overlaps with a code page ID is provided, + /// the method returns false. + /// + [Fact] + public void InvalidLcidOverlappingCodePageId_ReturnsFalse() => + AssertFailingLookup(lcid: 1200, sortId: 0); + + private static void AssertFailingLookup(int lcid, int sortId) + { + bool codePageRetrieved = LocalesHelper.TryGetCodePage(lcid, sortId, out _); + + Assert.False(codePageRetrieved); + } + + private static void AssertSuccessfulLookup(int lcid, int sortId, int expectedCodePage) + { + bool codePageRetrieved = LocalesHelper.TryGetCodePage(lcid, sortId, out int codePage); + + Assert.True(codePageRetrieved); + Assert.Equal(expectedCodePage, codePage); + } +} From 58d784eff74282096d1122bfff7d656ecf1d0015 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 18 May 2026 21:15:46 +0100 Subject: [PATCH 108/298] Tests | Flakiness improvements to XEventsTracingTest (#4262) * Flakiness improvements to XEventsTracingTest * Refactor one test method into three distinct test cases. * Add reasons for the tests being marked as flaky. * Switch call to 'sp_help' to a new, simpler SP. * Switch execution of 'SELECT @@VERSION' to a simpler 'SELECT 1' statement. * Use new FlushResultSet helper. * Simplify XEvent session name generation. * Add test case to verify that an activity ID is recorded in the extended event when the SQL statement throws an error. * Cut MAX_MEMORY on XEvents session to 4MB * Add explanatory comment to MAX_MEMORY * Update comments and remove Flaky trait --- .../ManualTests/DataCommon/XEventScope.cs | 5 +- .../TracingTests/XEventsTracingTest.cs | 94 +++++++++++++++---- 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/XEventScope.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/XEventScope.cs index 906efdce8e..ab89a97b10 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/XEventScope.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/XEventScope.cs @@ -94,13 +94,16 @@ public XEventScope( ? $"MAX_DURATION={_durationInMinutes} MINUTES," : string.Empty; + // MAX_MEMORY = 4 MB is a compromise which is designed to allow extended events to be recorded, + // while also preventing a large number of simultaneous sessions from consuming all of the 128 MB + // memory which Azure SQL allocates to extended events sessions. string xEventCreateAndStartCommandText = $"CREATE EVENT SESSION [{SessionName}] ON {sessionLocation}" + $" {eventSpecification} " + $" {targetSpecification} " + $"WITH (" + $" {duration} " + - $" MAX_MEMORY=16 MB," + + $" MAX_MEMORY=4 MB," + $" EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS, " + $" MAX_DISPATCH_LATENCY={MaxDispatchLatencySeconds} SECONDS, " + $" MAX_EVENT_SIZE=0 KB, " + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs index c97435b427..b03fc8b224 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs @@ -1,11 +1,14 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Runtime.CompilerServices; using System.Xml; using System.Xml.XPath; +using Microsoft.Data.SqlClient.Tests.Common; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; using Xunit.Abstractions; @@ -89,23 +92,63 @@ public class XEventCleanerCollection : ICollectionFixture { } + /// + /// These tests validate that activity IDs are properly transferred to the server and recorded in XEvent sessions, + /// even in error scenarios. This is important to ensure that customers can rely on activity IDs being present in + /// XEvent sessions for troubleshooting and correlation purposes. + /// [Collection("XEventCleaner")] public class XEventsTracingTest { - private readonly string _testName; - - public XEventsTracingTest(ITestOutputHelper outputHelper) + private const int CustomErrorNumber = 50001; + + /// + /// Verifies that the attach_activity_id_xfer in the 'rpc_starting' extended event is consistent with the tracing + /// context when executing a stored procedure. + /// + /// + /// This test has historically been marked as flaky following occasional failures due to deadlocks on the remote + /// SQL Server instance. This first became apparent when running 'sp_help' on an Azure SQL instance under load. + /// This appears to have been resolved by switching to a temporary stored procedure which consists of a simple + /// 'SELECT 1' statement. + /// + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + public void XEventActivityIDConsistentWithTracing_RpcStarting() { - _testName = DataTestUtility.CurrentTestName(outputHelper); + using SqlConnection managementConnection = new(DataTestUtility.TCPConnectionString); + using StoredProcedure sp = new(managementConnection, nameof(XEventActivityIDConsistentWithTracing_RpcStarting), " AS SELECT 1 AS [Field1];"); + + // Our stored procedure name is an escaped SQL Server object name. This will not match the object_name data + // in the XEvent XML, which records it as an unescaped name. + string unescapedProcedureName = sp.Name.Substring(1, sp.Name.Length - 2).Replace("]]", "]"); + + VerifyXEventActivityIDConsistentWithTracing(unescapedProcedureName, System.Data.CommandType.StoredProcedure, "rpc_starting"); } - [Trait("Category", "flaky")] - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] - [InlineData("SELECT @@VERSION", System.Data.CommandType.Text, "sql_statement_starting")] - [InlineData("sp_help", System.Data.CommandType.StoredProcedure, "rpc_starting")] - public void XEventActivityIDConsistentWithTracing(string query, System.Data.CommandType commandType, string xEvent) + /// + /// Verifies that the attach_activity_id_xfer in the 'sql_statement_starting' extended event is consistent with the tracing + /// context when executing a SQL statement which is not a stored procedure. + /// + /// + /// This test has historically been marked as flaky following occasional failures due to deadlocks on the remote + /// SQL Server instance. This first became apparent when running 'SELECT @@VERSION' on an Azure SQL instance under load. + /// This appears to have been resolved by switching to a simpler 'SELECT 1' statement. + /// + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + public void XEventActivityIDConsistentWithTracing_SqlStatementStarting() => + VerifyXEventActivityIDConsistentWithTracing("SELECT 1 AS [Field1]", System.Data.CommandType.Text, "sql_statement_starting"); + + /// + /// Validates that the activity ID is consistently recorded in an XEvent session even when a command generates an error. + /// + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + public void XEventActivityIDConsistentWithTracing_ActivityIDTransferOnError() => + VerifyXEventActivityIDConsistentWithTracing($"THROW {CustomErrorNumber}, 'Sample message', 0", System.Data.CommandType.Text, "sql_statement_starting"); + + private void VerifyXEventActivityIDConsistentWithTracing(string query, System.Data.CommandType commandType, string xEvent, + [CallerMemberName] string testName = "") { - // This test validates that the activity ID recorded in the client-side trace is passed through to the server, + // This method 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 @@ -119,7 +162,7 @@ public void XEventActivityIDConsistentWithTracing(string query, System.Data.Comm xEventManagementConnection.Open(); using XEventScope xEventSession = new( - _testName, + testName, xEventManagementConnection, $@"ADD EVENT SQL_STATEMENT_STARTING (ACTION (client_connection_id) WHERE (client_connection_id='{connectionId}')), ADD EVENT RPC_STARTING (ACTION (client_connection_id) WHERE (client_connection_id='{connectionId}'))", @@ -127,12 +170,19 @@ ADD EVENT RPC_STARTING (ACTION (client_connection_id) WHERE (client_connection_i using (DataTestUtility.MDSEventListener TraceListener = new()) { - using SqlCommand command = new(query, activityConnection) { CommandType = commandType }; - using SqlDataReader reader = command.ExecuteReader(); - while (reader.Read()) + try { - // Flush data + using SqlCommand command = new(query, activityConnection) { CommandType = commandType }; + using SqlDataReader reader = command.ExecuteReader(); + + reader.FlushResultSet(); } + // We only want to catch and swallow our custom user error number. Error number 1205 should continue to cause the + // test to fail, as it indicates a deadlock. + // @TODO: If deadlocks continue to cause test failures, we should record the xml_deadlock_report extended event to + // capture more information about the deadlock and output it to the test logs for analysis. + catch (SqlException sqlEx) when (sqlEx.Number == CustomErrorNumber) + { } ids = TraceListener.ActivityIDs; } @@ -146,22 +196,26 @@ ADD EVENT RPC_STARTING (ACTION (client_connection_id) WHERE (client_connection_i private static string GetCommandActivityId(string commandText, string eventName, Guid connectionId, XmlDocument xEvents) { + // We manually build the XPath query and cannot escape quotes, so disallow them from the command text to simplify + // the test. + Assert.DoesNotContain("\"", commandText); + XPathNavigator? xPathRoot = xEvents.CreateNavigator(); Assert.NotNull(xPathRoot); // The transferred activity ID is attached to the "attach_activity_id_xfer" action within // the "sql_statement_starting" and the "rpc_starting" events. XPathNodeIterator statementStartingQuery = xPathRoot.Select( - $"/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}')]"); + $"/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? current = statementStartingQuery.Current; Assert.NotNull(current); - XPathNavigator? activityIdElement = current.SelectSingleNode("action[@name='attach_activity_id_xfer']/value"); + XPathNavigator? activityIdElement = current.SelectSingleNode("action[@name=\"attach_activity_id_xfer\"]/value"); Assert.NotNull(activityIdElement); Assert.NotNull(activityIdElement.Value); From eb9fcc3e709629e53ecca333c19cfe0233d82b00 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Mon, 18 May 2026 13:16:43 -0700 Subject: [PATCH 109/298] Update .NET 10 to 10.0.300 (#4287) Co-authored-by: Malcolm Daigle --- global.json | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/global.json b/global.json index 48cbbf1a80..5d6e4f2f39 100644 --- a/global.json +++ b/global.json @@ -2,14 +2,9 @@ "sdk": { // We currently require the .NET 10 SDK to build and test the project. - // - // GOTCHA: Our CI infrastructure for Windows uses VS 2022 which comes with MSBuild 17.x. The - // .NET 10 SDK versions in the 10.0.2xx series require MSBuild 18.x, so we specify the most - // recent 10.0.1xx series release which is compatible with MSBuild 17.x. - // - "version": "10.0.107", + "version": "10.0.300", - // We cannot allow any roll forward due to the above MSBuild compatibility issues. + // Lock to the above version so that we can control when we take breaking sdk changes. "rollForward": "disable", // Do not allow pre-release versions. From c92d901238b129c88341da94c7daa37ae9bf5304 Mon Sep 17 00:00:00 2001 From: priyankatiwari08 Date: Tue, 19 May 2026 13:16:56 +0530 Subject: [PATCH 110/298] Remove labeling from auto-triage workflow (#4289) - Remove add-labels safe-output from frontmatter - Remove add_labels action instructions from workflow body - Remove Required Context section (full .github/ scan) - Update triage comment template (area from table, not label) - Move AI disclaimer before Next Steps - Recompile lock.yml Labels will be managed via GitHub Projects and human triage instead. --- .github/workflows/issue-triage.lock.yml | 54 ++++++++----------------- .github/workflows/issue-triage.md | 22 +++------- 2 files changed, 22 insertions(+), 54 deletions(-) diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 08a2e176b1..54d5661b0c 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ddda208c8014c64e57436e95b5e1c92431c6815b5d0f782bb6a01ee1e84a63d9","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"20b8fbbfaae4d08b3206fb6b2f4000e43b8915e37f5afc9a534056f8ce9a563b","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -189,20 +189,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_bef3a8b6a480c883_EOF' + cat << 'GH_AW_PROMPT_b5f7a25dd7272e5f_EOF' - GH_AW_PROMPT_bef3a8b6a480c883_EOF + GH_AW_PROMPT_b5f7a25dd7272e5f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_bef3a8b6a480c883_EOF' + cat << 'GH_AW_PROMPT_b5f7a25dd7272e5f_EOF' - Tools: add_comment, add_labels(max:5), missing_tool, missing_data, noop + Tools: add_comment, missing_tool, missing_data, noop - GH_AW_PROMPT_bef3a8b6a480c883_EOF + GH_AW_PROMPT_b5f7a25dd7272e5f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_bef3a8b6a480c883_EOF' + cat << 'GH_AW_PROMPT_b5f7a25dd7272e5f_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -231,12 +231,12 @@ jobs: {{/if}} - GH_AW_PROMPT_bef3a8b6a480c883_EOF + GH_AW_PROMPT_b5f7a25dd7272e5f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_bef3a8b6a480c883_EOF' + cat << 'GH_AW_PROMPT_b5f7a25dd7272e5f_EOF' {{#runtime-import .github/workflows/issue-triage.md}} - GH_AW_PROMPT_bef3a8b6a480c883_EOF + GH_AW_PROMPT_b5f7a25dd7272e5f_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -430,16 +430,15 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_6f7dd6f136148d14_EOF' - {"add_comment":{"hide_older_comments":true,"max":1},"add_labels":{"max":5},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_6f7dd6f136148d14_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_421fab10d521b1cd_EOF' + {"add_comment":{"hide_older_comments":true,"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_421fab10d521b1cd_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Supports reply_to_id for discussion threading.", - "add_labels": " CONSTRAINTS: Maximum 5 label(s) can be added." + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Supports reply_to_id for discussion threading." }, "repo_params": {}, "dynamic_tools": [] @@ -468,25 +467,6 @@ jobs: } } }, - "add_labels": { - "defaultMax": 5, - "fields": { - "item_number": { - "issueNumberOrTemporaryId": true - }, - "labels": { - "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 - } - } - }, "missing_data": { "defaultMax": 20, "fields": { @@ -639,7 +619,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_47425e2eb31c9c9e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_95017dc8ef4cee83_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -683,7 +663,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_47425e2eb31c9c9e_EOF + GH_AW_MCP_CONFIG_95017dc8ef4cee83_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1314,7 +1294,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"max\":5},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index 8974ec6a38..793dfc81c3 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -19,8 +19,6 @@ safe-outputs: add-comment: max: 1 hide-older-comments: true - add-labels: - max: 5 --- # SqlClient Issue Auto-Triage @@ -29,10 +27,10 @@ You are a triage specialist for **Microsoft.Data.SqlClient**. A new issue has just been opened. Your job is to: 1. Read the issue silently using GitHub read tools -2. Apply labels silently using `add_labels` -3. Post **one** triage summary comment using `add_comment` +2. Post **one** triage summary comment using `add_comment` That is the entire workflow. Do NOT call `add_comment` more than once. +Do NOT call `add_labels`. Do NOT apply any labels. Do NOT post intermediate findings. Do NOT post separate comments for area detection, duplicate checking, or environment validation. Everything goes into the single triage summary at the end. @@ -90,19 +88,9 @@ Proceed with all remaining triage steps regardless of missing environment detail --- -## Actions (execute in this order) +## Actions -**First**: Call `add_labels` with at most 5 labels total (safe-outputs limit): -- `Triage Needed :new:` (always) -- Exactly **one** `Area\*` label from the table above (pick the single best match) -- `Needs More Info :information_source:` if critical environment details are missing (bugs only) -- `Repro Available :heavy_check_mark:` if repro steps are provided -- `Regression :boom:` if this appears to be a regression - -If the issue spans multiple areas, pick the primary area as the label and mention -other relevant areas in the triage summary comment instead. - -**Then**: Call `add_comment` exactly **once** with this markdown: +Call `add_comment` exactly **once** with this markdown: ``` ## 🔍 Triage Summary @@ -111,7 +99,7 @@ other relevant areas in the triage summary comment instead. |-------|--------| | Issue type | | | Environment | | -| Area | | +| Area | | | Duplicates | | | Regression | | From 9c0d240aa8b18993cdec2d9ae9ba99f2b46f384a Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 20 May 2026 19:44:50 +0100 Subject: [PATCH 111/298] Always Encrypted | Align reads of CekMdVersion and EkValueCount with TDS specification (#4240) * Align read of keyMDVersion with TDS ULONGLONG datatype * Align read of EkValueCount in COLMETADATA with TDS USHORT datatype * Address test failures Test code uses reflection to call SqlTceCipherInfoEntry.Add --- .../SqlClient/AlwaysEncryptedHelperClasses.cs | 20 +++++++-------- .../Data/SqlClient/ColumnEncryptionKeyInfo.cs | 25 +++++++------------ .../Data/SqlClient/SqlCommand.Encryption.cs | 3 ++- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 15 +++++------ .../ExceptionsAlgorithmErrors.cs | 14 +++++------ .../AlwaysEncryptedTests/Utility.cs | 2 +- 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedHelperClasses.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedHelperClasses.cs index 659d2e9f0f..cb37486a42 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedHelperClasses.cs @@ -1,8 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; using Microsoft.Data.SqlClient.Server; @@ -20,7 +21,7 @@ internal class SqlEncryptionKeyInfo internal int databaseId; internal int cekId; internal int cekVersion; - internal byte[] cekMdVersion; + internal ulong cekMdVersion; internal string keyPath; internal string keyStoreName; internal string algorithmName; @@ -63,7 +64,7 @@ internal class SqlTceCipherInfoEntry /// /// Cek MD Version /// - private byte[] _cekMdVersion; + private ulong _cekMdVersion; /// /// Return the ordinal. @@ -112,7 +113,7 @@ internal int CekVersion /// /// Return the CEK MD Version. /// - internal byte[] CekMdVersion + internal ulong CekMdVersion { get { @@ -142,7 +143,7 @@ internal List ColumnEncryptionKeyValues /// /// /// - internal void Add(byte[] encryptedKey, int databaseId, int cekId, int cekVersion, byte[] cekMdVersion, string keyPath, string keyStoreName, string algorithmName) + internal void Add(byte[] encryptedKey, int databaseId, int cekId, int cekVersion, ulong cekMdVersion, string keyPath, string keyStoreName, string algorithmName) { Debug.Assert(_columnEncryptionKeyValues != null, "_columnEncryptionKeyValues should already be initialized."); @@ -172,7 +173,6 @@ internal void Add(byte[] encryptedKey, int databaseId, int cekId, int cekVersion Debug.Assert(_databaseId == databaseId); Debug.Assert(_cekId == cekId); Debug.Assert(_cekVersion == cekVersion); - Debug.Assert(_cekMdVersion != null && cekMdVersion != null && _cekMdVersion.Length == _cekMdVersion.Length); } } @@ -186,7 +186,7 @@ internal SqlTceCipherInfoEntry(int ordinal = 0) _databaseId = 0; _cekId = 0; _cekVersion = 0; - _cekMdVersion = null; + _cekMdVersion = 0; _columnEncryptionKeyValues = new List(); } } @@ -550,7 +550,7 @@ private byte[] SerializeToWriteFormat() totalLength += sizeof(int); // Metadata version of the encryption key. - totalLength += _cipherMetadata.EncryptionKeyInfo.cekMdVersion.Length; + totalLength += sizeof(ulong); // Normalization Rule Version. totalLength += sizeof(byte); @@ -576,8 +576,8 @@ private byte[] SerializeToWriteFormat() SerializeIntIntoBuffer(_cipherMetadata.EncryptionKeyInfo.cekVersion, serializedWireFormat, ref consumedBytes); // 6 - Write the metadata version of the encryption key. - Buffer.BlockCopy(_cipherMetadata.EncryptionKeyInfo.cekMdVersion, 0, serializedWireFormat, consumedBytes, _cipherMetadata.EncryptionKeyInfo.cekMdVersion.Length); - consumedBytes += _cipherMetadata.EncryptionKeyInfo.cekMdVersion.Length; + BinaryPrimitives.WriteUInt64LittleEndian(serializedWireFormat.AsSpan(consumedBytes), _cipherMetadata.EncryptionKeyInfo.cekMdVersion); + consumedBytes += sizeof(ulong); // 7 - Write Normalization Rule Version. serializedWireFormat[consumedBytes++] = _cipherMetadata.NormalizationRuleVersion; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ColumnEncryptionKeyInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ColumnEncryptionKeyInfo.cs index 6c99dd8432..f738a465c0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ColumnEncryptionKeyInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ColumnEncryptionKeyInfo.cs @@ -1,8 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; namespace Microsoft.Data.SqlClient { @@ -16,10 +17,9 @@ internal class ColumnEncryptionKeyInfo internal readonly byte[] DecryptedKeyBytes; internal readonly byte[] KeyIdBytes; internal readonly byte[] DatabaseIdBytes; - internal readonly byte[] KeyMetadataVersionBytes; + internal readonly ulong KeyMetadataVersion; private static readonly string _decryptedKeyName = "DecryptedKey"; - private static readonly string _keyMetadataVersionName = "KeyMetadataVersion"; private static readonly string _className = "ColumnEncryptionKeyInfo"; private static readonly string _bytePackageName = "BytePackage"; private static readonly string _serializeToBufferMethodName = "SerializeToBuffer"; @@ -32,7 +32,7 @@ internal class ColumnEncryptionKeyInfo /// database id for this column encryption key /// key metadata version for this column encryption key /// key id for this column encryption key - internal ColumnEncryptionKeyInfo(byte[] decryptedKey, int databaseId, byte[] keyMetadataVersion, int keyid) + internal ColumnEncryptionKeyInfo(byte[] decryptedKey, int databaseId, ulong keyMetadataVersion, int keyid) { if (decryptedKey == null) @@ -43,19 +43,11 @@ internal ColumnEncryptionKeyInfo(byte[] decryptedKey, int databaseId, byte[] key { throw SQL.EmptyArgumentInConstructorInternal(_decryptedKeyName, _className); } - if (keyMetadataVersion == null) - { - throw SQL.NullArgumentInConstructorInternal(_keyMetadataVersionName, _className); - } - if (0 == keyMetadataVersion.Length) - { - throw SQL.EmptyArgumentInConstructorInternal(_keyMetadataVersionName, _className); - } KeyId = keyid; DatabaseId = databaseId; DecryptedKeyBytes = decryptedKey; - KeyMetadataVersionBytes = keyMetadataVersion; + KeyMetadataVersion = keyMetadataVersion; //Covert keyId to Bytes ushort keyIdUShort; @@ -96,7 +88,8 @@ internal int GetLengthForSerialization() lengthForSerialization += DecryptedKeyBytes.Length; lengthForSerialization += KeyIdBytes.Length; lengthForSerialization += DatabaseIdBytes.Length; - lengthForSerialization += KeyMetadataVersionBytes.Length; + // KeyMetadataVersion is of type ulong which is 8 bytes + lengthForSerialization += sizeof(ulong); return lengthForSerialization; } @@ -131,8 +124,8 @@ internal int SerializeToBuffer(byte[] bytePackage, int startOffset) Buffer.BlockCopy(DatabaseIdBytes, 0, bytePackage, startOffset, DatabaseIdBytes.Length); startOffset += DatabaseIdBytes.Length; - Buffer.BlockCopy(KeyMetadataVersionBytes, 0, bytePackage, startOffset, KeyMetadataVersionBytes.Length); - startOffset += KeyMetadataVersionBytes.Length; + BinaryPrimitives.WriteUInt64LittleEndian(bytePackage.AsSpan(startOffset), KeyMetadataVersion); + startOffset += sizeof(ulong); Buffer.BlockCopy(KeyIdBytes, 0, bytePackage, startOffset, KeyIdBytes.Length); startOffset += KeyIdBytes.Length; Buffer.BlockCopy(DecryptedKeyBytes, 0, bytePackage, startOffset, DecryptedKeyBytes.Length); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs index 59a2fddadc..fa5cfb5ab2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.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.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -1016,7 +1017,7 @@ private bool ReadDescribeEncryptionParameterResultsKeys( databaseId: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.DbId), cekId: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyId), cekVersion: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyVersion), - cekMdVersion: keyMdVersion, + cekMdVersion: BinaryPrimitives.ReadUInt64LittleEndian(keyMdVersion), keyPath: keyPath, keyStoreName: providerName, algorithmName: ds.GetString((int)DescribeParameterEncryptionResultSet1.KeyEncryptionAlgorithm)); 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 8bfedc3539..66e9c3e52e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -5288,6 +5288,10 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb /// /// Parses the TDS message to read single CIPHER_INFO entry. /// + /// + /// The CIPHER_INFO structure is represented as an EK_INFO structure in the TDS protocol. + /// + /// . internal TdsOperationStatus TryReadCipherInfoEntry(TdsParserStateObject stateObj, out SqlTceCipherInfoEntry entry) { byte cekValueCount = 0; @@ -5318,8 +5322,7 @@ internal TdsOperationStatus TryReadCipherInfoEntry(TdsParserStateObject stateObj } // Read the key MD Version - byte[] keyMDVersion = new byte[8]; - result = stateObj.TryReadByteArray(keyMDVersion, 8); + result = stateObj.TryReadInt64(out long keyMDVersion); if (result != TdsOperationStatus.Done) { return result; @@ -5414,7 +5417,7 @@ internal TdsOperationStatus TryReadCipherInfoEntry(TdsParserStateObject stateObj databaseId: dbId, cekId: keyId, cekVersion: keyVersion, - cekMdVersion: keyMDVersion, + cekMdVersion: unchecked((ulong)keyMDVersion), keyPath: keyPath, keyStoreName: keyStoreName, algorithmName: algorithmName); @@ -5429,9 +5432,8 @@ internal TdsOperationStatus TryReadCipherInfoEntry(TdsParserStateObject stateObj internal TdsOperationStatus TryProcessCipherInfoTable(TdsParserStateObject stateObj, out SqlTceCipherInfoTable cipherTable) { // Read count - short tableSize = 0; cipherTable = null; - TdsOperationStatus result = stateObj.TryReadInt16(out tableSize); + TdsOperationStatus result = stateObj.TryReadUInt16(out ushort tableSize); if (result != TdsOperationStatus.Done) { return result; @@ -11445,8 +11447,7 @@ internal void WriteEncryptionEntries(ref SqlTceCipherInfoTable cekTable, TdsPars WriteInt(cekTable[i].CekVersion, stateObj); // Write 8 bytes of key MD Version - Debug.Assert(8 == cekTable[i].CekMdVersion.Length); - stateObj.WriteByteArray(cekTable[i].CekMdVersion, 8, 0); + WriteUnsignedLong(cekTable[i].CekMdVersion, stateObj); // We don't really need to send the keys stateObj.WriteByte(0x00); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs index caa98cc686..c12f73e29f 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs @@ -72,7 +72,7 @@ public void TestInvalidEncryptionType() { const byte invalidEncryptionType = 3; Object cipherMD = GetSqlCipherMetadata(0, 2, null, invalidEncryptionType, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); @@ -135,7 +135,7 @@ 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, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); @@ -153,7 +153,7 @@ public void TestUnknownEncryptionAlgorithmId() string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnknownColumnEncryptionAlgorithmId, unknownEncryptionAlgoId, "'1', '2'"); Object cipherMD = GetSqlCipherMetadata(0, unknownEncryptionAlgoId, null, 1, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); @@ -177,7 +177,7 @@ 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, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, invalidProviderName, "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, invalidProviderName, "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); @@ -199,7 +199,7 @@ 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, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); @@ -220,7 +220,7 @@ public void TestExceptionsFromCertStore() "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 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, corruptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); @@ -244,7 +244,7 @@ public void TestExceptionsFromCustomKeyStore() SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders); object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "DummyProvider", "DummyAlgo"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "DummyProvider", "DummyAlgo"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs index e53fa6491e..c51dcecf99 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs @@ -217,7 +217,7 @@ internal static void AddEncryptionKeyToCipherMD( int databaseId, int cekId, int cekVersion, - byte[] cekMdVersion, + ulong cekMdVersion, string keyPath, string keyStoreName, string algorithmName) From 8afb242e01860b3f41157cdd230a1ed731dd2b49 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 20 May 2026 16:32:42 -0300 Subject: [PATCH 112/298] Add SqlServer.Server as a sibling project reference (#4291) --- .gitignore | 3 + Directory.Packages.props | 53 ++++-- build.proj | 170 +++++++++++++++--- doc/Directory.Packages.props | 1 + .../Microsoft.Data.SqlClient.Samples.csproj | 13 +- .../templates/jobs/ci-build-nugets-job.yml | 20 +++ .../templates/jobs/ci-run-tests-job.yml | 18 ++ .../templates/stages/ci-run-tests-stage.yml | 14 ++ .../templates/steps/ci-project-build-step.yml | 7 + .../templates/steps/run-all-tests-step.yml | 15 ++ eng/pipelines/dotnet-sqlclient-ci-core.yml | 19 +- .../jobs/test-azure-package-ci-job.yml | 34 ++++ .../libraries/ci-build-variables.yml | 18 +- .../onebranch/stages/build-stages.yml | 6 + .../onebranch/steps/build-buildproj-step.yml | 1 + .../steps/roslyn-analyzers-buildproj-step.yml | 1 + .../stages/build-azure-package-ci-stage.yml | 49 +++++ .../build-sqlclient-package-ci-stage.yml | 12 ++ src/Directory.Build.props | 6 +- ...waysEncrypted.AzureKeyVaultProvider.csproj | 5 +- .../src/Versions.props | 12 +- .../Abstractions/src/Abstractions.csproj | 9 +- .../Abstractions/src/Versions.props | 12 +- .../Azure/src/Azure.csproj | 5 +- .../Azure/src/Versions.props | 12 +- .../Logging/src/Logging.csproj | 5 +- .../Logging/src/Versions.props | 12 +- src/Microsoft.Data.SqlClient/Versions.props | 10 ++ .../Microsoft.Data.SqlClient.csproj | 20 ++- .../ref/Microsoft.Data.SqlClient.csproj | 14 +- .../src/Microsoft.Data.SqlClient.csproj | 22 ++- .../src/Microsoft.Data.SqlClient.nuspec | 6 +- ...Microsoft.Data.SqlClient.TestCommon.csproj | 1 + .../FunctionalTests/SqlDataRecordTest.cs | 4 +- ...icrosoft.Data.SqlClient.ManualTests.csproj | 9 +- .../SQL/UdtTest/SqlServerTypesTest.cs | 12 ++ .../SQL/UdtTest/UDTs/Address/Address.csproj | 7 +- .../SQL/UdtTest/UDTs/Circle/Circle.csproj | 7 +- .../SQL/UdtTest/UDTs/Shapes/Shapes.csproj | 5 +- .../UdtTest/UDTs/Utf8String/Utf8String.csproj | 5 +- .../Microsoft.Data.SqlClient.UnitTests.csproj | 13 +- .../Microsoft.SqlServer.Server.csproj | 5 +- src/Microsoft.SqlServer.Server/Versions.props | 12 +- 43 files changed, 588 insertions(+), 96 deletions(-) diff --git a/.gitignore b/.gitignore index 0d8dfed6d5..da143d57ac 100644 --- a/.gitignore +++ b/.gitignore @@ -376,3 +376,6 @@ MigrationBackup/ # MDS "Not Supported" GenAPI code **/notsupported/*.cs + +# C# language server cache +*.lscache diff --git a/Directory.Packages.props b/Directory.Packages.props index 62918f7d65..37b72ca205 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,4 +1,33 @@ + + + + + + + + + + + + true - - - - - - - - + + - + diff --git a/build.proj b/build.proj index 971cfb12fa..b8ac7d44ed 100644 --- a/build.proj +++ b/build.proj @@ -1,5 +1,6 @@ + @@ -208,12 +209,15 @@ ReferenceType Applies to: BuildSqlClient, BuildSqlClientUnix, BuildSqlClientWindows Description: Determines how to build SqlClient. If set to "Project", any cross-project - references (eg, Abstractions from SqlClient) will be made as project references. If - set to "Package", package references will be made instead. When running in - package mode, target versions can be specified via PackageVersion* build - parameters. If these are not provided, the central feed version will be used - instead (see Directory.Packages.props). When running in project mode, the - dependencies will be built automatically. + references (eg, Abstractions from SqlClient) will be made as project + references. If set to "Package", package references will be made instead. When + running in package mode, target versions can be specified via PackageVersion* + build parameters. If these are not provided, the central feed version will be + used instead (see Directory.Packages.props). In package mode, all Pack* + targets also mirror their .nupkg/.snupkg output into the local NuGet feed + directory (packages/) so that downstream builds in the same invocation can + restore them without a separate feed publish step. When running in project + mode, the dependencies will be built automatically. Applies to: TestSqlClientFunctional, TestSqlClientManual Description: Determines whether to test against a project reference or a package version @@ -227,6 +231,19 @@ -p:ReferenceType=Package + + false + category!=failing&category!=flaky&category!=interactive + + $(TestFilters)&category!=signed --filter "$(TestFilters)" @@ -370,6 +393,7 @@ $(SqlClientSrcRoot)src/Microsoft.Data.SqlClient.csproj $(SqlClientSrcRoot)ref/Microsoft.Data.SqlClient.csproj $(SqlClientSrcRoot)notsupported/Microsoft.Data.SqlClient.csproj + $(SqlClientArtifactRoot)$(ReferenceType)-$(Configuration)/ $(SqlClientSrcRoot)tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -390,7 +414,10 @@ PlatformNotSupportedException. It generates the source for this using GenAPI which is built as the first step of this target. --> - + + PackLogging;PackAbstractions;PackSqlServer + + "$(DotnetPath)dotnet" build "$(GenApiProjectPath)" @@ -424,6 +451,7 @@ $(ReferenceTypeArgument) $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) @@ -434,7 +462,10 @@ - + + PackLogging;PackAbstractions;PackSqlServer + + "$(DotnetPath)dotnet" build $(SqlClientRefProjectPath) @@ -449,6 +480,7 @@ $(ReferenceTypeArgument) $(PackageVersionAbstractionsArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) @@ -459,7 +491,10 @@ - + + PackLogging;PackAbstractions;PackSqlServer + + "$(DotnetPath)dotnet" build $(SqlClientProjectPath) @@ -476,6 +511,7 @@ $(ReferenceTypeArgument) $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) @@ -486,7 +522,10 @@ - + + PackLogging;PackAbstractions;PackSqlServer + + "$(DotnetPath)dotnet" build $(SqlClientProjectPath) @@ -503,6 +542,7 @@ $(ReferenceTypeArgument) $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) @@ -545,15 +585,27 @@ $(ReferenceTypeArgument) $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) + $(PackageVersionSqlServerArgument) - -p:PackageOutputPath="$(SqlClientArtifactRoot)/$(ReferenceType)-$(Configuration)" + -p:PackageOutputPath="$(SqlClientPackageArtifactRoot)" $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + @@ -582,6 +634,7 @@ $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) @@ -613,6 +666,7 @@ $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) @@ -650,14 +704,18 @@ - + $(RepoRoot)src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/ $(AkvProviderSrcRoot)src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj + $(RepoRoot)artifacts/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/$(Configuration)/ - - + + + PackSqlClient;PackLogging + + "$(DotnetPath)dotnet" build "$(AkvProviderProjectPath)" @@ -674,12 +732,13 @@ $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + @@ -687,7 +746,7 @@ - "$(DotnetPath)dotnet" pack "$(AkvProviderProjectpath)" + "$(DotnetPath)dotnet" pack "$(AkvProviderProjectPath)" -p:Configuration=$(Configuration) $(PackBuildArgument) $(SigningKeyPathArgument) @@ -702,25 +761,42 @@ $(PackageVersionAbstractionsArgument) $(PackageVersionLoggingArgument) $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + + + + + + + + $(RepoRoot)src/Microsoft.Data.SqlClient.Extensions/Abstractions/ $(AbstractionsSrcRoot)src/Abstractions.csproj $(AbstractionsSrcRoot)test/Abstractions.Test.csproj + $(RepoRoot)artifacts/Microsoft.Data.SqlClient.Extensions.Abstractions/$(Configuration)/ - + + PackLogging + + "$(DotnetPath)dotnet" build "$(AbstractionsProjectPath)" @@ -768,6 +844,17 @@ + + + + + + + @@ -804,10 +891,14 @@ $(RepoRoot)src/Microsoft.Data.SqlClient.Extensions/Azure/ $(AzureSrcRoot)src/Azure.csproj $(AzureSrcRoot)test/Azure.Test.csproj + $(RepoRoot)artifacts/Microsoft.Data.SqlClient.Extensions.Azure/$(Configuration)/ - + + PackLogging;PackAbstractions + + "$(DotnetPath)dotnet" build "$(AzureProjectPath)" @@ -855,6 +946,17 @@ + + + + + + + @@ -875,14 +977,16 @@ $(ReferenceTypeArgument) - + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + @@ -891,6 +995,7 @@ $(RepoRoot)src/Microsoft.Data.SqlClient.Internal/Logging/src/ $(LoggingSrcRoot)Logging.csproj + $(RepoRoot)artifacts/Microsoft.Data.SqlClient.Internal.Logging/$(Configuration)/ @@ -934,6 +1039,17 @@ + + + + + + + @@ -941,6 +1057,7 @@ $(RepoRoot)src/Microsoft.SqlServer.Server/ $(SqlServerSrcRoot)Microsoft.SqlServer.Server.csproj + $(RepoRoot)artifacts/Microsoft.SqlServer.Server/$(Configuration)/ @@ -984,5 +1101,16 @@ + + + + + + + diff --git a/doc/Directory.Packages.props b/doc/Directory.Packages.props index 2ead6c1757..d47f7e01b9 100644 --- a/doc/Directory.Packages.props +++ b/doc/Directory.Packages.props @@ -6,5 +6,6 @@ + diff --git a/doc/samples/Microsoft.Data.SqlClient.Samples.csproj b/doc/samples/Microsoft.Data.SqlClient.Samples.csproj index c194fac4d0..44a153f6f2 100644 --- a/doc/samples/Microsoft.Data.SqlClient.Samples.csproj +++ b/doc/samples/Microsoft.Data.SqlClient.Samples.csproj @@ -10,14 +10,19 @@ False + - - - - + + + + + + + + diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index 8d051e5167..e9c3ad7c64 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -77,6 +77,16 @@ parameters: - name: akvPackageVersion type: string + # The version of the SqlServer package to depend on when referenceType is 'Package'. + - name: sqlServerPackageVersion + type: string + default: $(sqlServerPackageVersion) + + # The name of the SqlServer pipeline artifact to download when referenceType is 'Package'. + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + jobs: - job: build_mds_akv_packages_job displayName: Build MDS & AKV Packages @@ -115,6 +125,12 @@ jobs: artifactName: ${{ parameters.loggingArtifactsName }} targetPath: $(localFeedPath) + - task: DownloadPipelineArtifact@2 + displayName: Download SqlServer Package Artifacts + inputs: + artifactName: ${{ parameters.sqlServerArtifactsName }} + targetPath: $(localFeedPath) + # Install the .NET SDK. - template: /eng/pipelines/steps/install-dotnet.yml@self @@ -139,6 +155,7 @@ jobs: build: MDS abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} loggingPackageVersion: ${{ parameters.loggingPackageVersion }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} - task: DotNetCoreCLI@2 displayName: 'Create MDS NuGet Package' @@ -153,6 +170,7 @@ jobs: -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} # PackSqlClient outputs to artifacts/Microsoft.Data.SqlClient/-/. # Downstream steps (local feed copy, AKV pack, artifact publish) all expect packages at @@ -184,6 +202,7 @@ jobs: loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} akvPackageVersion: ${{ parameters.akvPackageVersion }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} - task: DotNetCoreCLI@2 displayName: 'Create AKV Provider NuGet Package' @@ -199,6 +218,7 @@ jobs: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:PackageVersionAkvProvider=${{ parameters.akvPackageVersion }} - task: CopyFiles@2 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 5c593e11e3..8b79bf7274 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -88,6 +88,16 @@ parameters: - name: mdsPackageVersion type: string + # The name of the SqlServer pipeline artifact to download when referenceType is 'Package'. + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + + # The version of the SqlServer package to depend on when referenceType is 'Package'. + - name: sqlServerPackageVersion + type: string + default: $(sqlServerPackageVersion) + # TODO: What is this for? - name: netcoreVersionTestUtils type: string @@ -194,6 +204,12 @@ jobs: artifactName: ${{ parameters.mdsArtifactsName }} targetPath: $(Build.SourcesDirectory)/packages + - task: DownloadPipelineArtifact@2 + displayName: Download SqlServer Package Artifacts + inputs: + artifactName: ${{ parameters.sqlServerArtifactsName }} + targetPath: $(Build.SourcesDirectory)/packages + # Install the .NET SDK and Runtimes. - template: /eng/pipelines/steps/install-dotnet.yml@self parameters: @@ -364,6 +380,7 @@ jobs: abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} - ${{ if and(eq(parameters.enableX86Test, true), eq(parameters.operatingSystem, 'Windows')) }}: - template: /eng/pipelines/common/templates/steps/run-all-tests-step.yml@self @@ -379,6 +396,7 @@ jobs: abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} - template: /eng/pipelines/common/templates/steps/publish-test-results-step.yml@self parameters: diff --git a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml index fd87d7d9ac..86ddc0ca45 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -50,6 +50,16 @@ parameters: - name: mdsPackageVersion type: string + # The name of the SqlServer pipeline artifacts to download. + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + + # The version of the SqlServer package to depend on when referenceType is 'Package'. + - name: sqlServerPackageVersion + type: string + default: $(sqlServerPackageVersion) + # Jobs to run after the test jobs complete, if any. - name: postTestJobs type: jobList @@ -114,6 +124,8 @@ stages: loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} targetFramework: ${{ targetFramework }} netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }} @@ -150,6 +162,8 @@ stages: loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} targetFramework: ${{ targetFramework }} netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }} 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 fe46ad66a2..e5fa6d4424 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -68,6 +68,11 @@ parameters: type: string default: $(akvPackageVersion) + # Necessary when referenceType is Package. Ignored when referenceType is Project. + - name: sqlServerPackageVersion + type: string + default: $(sqlServerPackageVersion) + steps: # Build MDS - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: @@ -85,6 +90,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} # Build AKV Provider - ${{ if or(eq(parameters.build, 'AkvProvider'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: @@ -103,3 +109,4 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} 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 cf3ce206a4..5730b8cc0d 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -22,6 +22,9 @@ parameters: - name: mdsPackageVersion type: string + - name: sqlServerPackageVersion + type: string + - name: platform type: string default: $(Platform) @@ -137,6 +140,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:TestResultsFolderPath=TestResults ${{ else }}: # x86 arguments: >- @@ -147,6 +151,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:TestResultsFolderPath=TestResults @@ -165,6 +170,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults -p:TestCodeCoverage=false @@ -177,6 +183,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults @@ -199,6 +206,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:TestResultsFolderPath=TestResults ${{ else }}: # x86 arguments: >- @@ -210,6 +218,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:TestResultsFolderPath=TestResults retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} @@ -230,6 +239,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults -p:TestCodeCoverage=false @@ -243,6 +253,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults @@ -292,6 +303,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:TestResultsFolderPath=TestResults - task: DotNetCoreCLI@2 @@ -308,6 +320,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults -p:TestCodeCoverage=false @@ -327,6 +340,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:TestResultsFolderPath=TestResults retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} @@ -345,6 +359,7 @@ steps: -p:PackageVersionAbstractions=${{ parameters.abstractionsPackageVersion }} -p:PackageVersionLogging=${{ parameters.loggingPackageVersion }} -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} -p:TestFilters="category=flaky" -p:TestResultsFolderPath=TestResults -p:TestCodeCoverage=false diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index c8181aff5a..f5f2269494 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -185,14 +185,17 @@ stages: mdsPackageVersion: $(mdsPackageVersion) akvPackageVersion: $(akvPackageVersion) referenceType: ${{ parameters.referenceType }} + sqlServerArtifactsName: $(sqlServerArtifactsName) + sqlServerPackageVersion: $(sqlServerPackageVersion) SNIVersion: ${{ parameters.SNIVersion }} SNIValidationFeed: ${{ parameters.SNIValidationFeed }} - # When building SqlClient via packages, we must depend on the Abstractions and Logging - # packages. + # When building SqlClient via packages, we must depend on the Abstractions, Logging, + # and SqlServer packages. ${{ if eq(parameters.referenceType, 'Package') }}: additionalDependsOn: - build_abstractions_package_stage - build_logging_package_stage + - build_sqlserver_package_stage # Build the Azure package, and publish it to the pipeline artifacts under the # given artifact name. @@ -206,16 +209,21 @@ stages: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} # When building via packages, we must depend on the Abstractions, Logging, - # and MDS packages. + # SqlServer, and MDS packages. ${{ if eq(parameters.referenceType, 'Package') }}: additionalDependsOn: - build_abstractions_package_stage - build_logging_package_stage + - build_sqlserver_package_stage - build_sqlclient_package_stage dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + loggingArtifactsName: $(loggingArtifactsName) + loggingPackageVersion: $(loggingPackageVersion) mdsArtifactsName: $(mdsArtifactsName) mdsPackageVersion: $(mdsPackageVersion) referenceType: ${{ parameters.referenceType }} + sqlServerArtifactsName: $(sqlServerArtifactsName) + sqlServerPackageVersion: $(sqlServerPackageVersion) # Verify that all NuGet packages comply with Microsoft metadata requirements. # This runs on a Windows agent after all packages have been built and @@ -240,14 +248,17 @@ stages: loggingPackageVersion: $(loggingPackageVersion) mdsArtifactsName: $(mdsArtifactsName) mdsPackageVersion: $(mdsPackageVersion) + sqlServerArtifactsName: $(sqlServerArtifactsName) + sqlServerPackageVersion: $(sqlServerPackageVersion) testJobTimeout: ${{ parameters.testJobTimeout }} # When testing MDS via packages, we must depend on the Abstractions, - # Logging, MDS, and Azure packages. + # Logging, SqlServer, MDS, and Azure packages. ${{ if eq(parameters.referenceType, 'Package') }}: additionalDependsOn: - build_abstractions_package_stage - build_logging_package_stage + - build_sqlserver_package_stage - build_sqlclient_package_stage - build_azure_package_stage diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index cd9b87e103..9c70433e33 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -26,6 +26,13 @@ parameters: type: string default: Logging.Artifacts + # The Logging package version to depend on. + # + # This is used when the referenceType is 'Package'. + - name: loggingPackageVersion + type: string + default: '' + # The Abstractions package verion to depend on. # # This is used when the referenceType is 'Package'. @@ -79,6 +86,21 @@ parameters: - name: mdsPackageVersion type: string + # The name of the SqlServer pipeline artifacts to download. + # + # This is used when the referenceType is 'Package'. MDS depends on + # SqlServer.Server, so the package must be available for transitive restore. + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + + # The SqlServer package version to depend on. + # + # This is used when the referenceType is 'Package'. + - name: sqlServerPackageVersion + type: string + default: '' + # The list of .NET Framework runtimes to test against. - name: netFrameworkRuntimes type: object @@ -153,7 +175,9 @@ jobs: --verbosity ${{ parameters.dotnetVerbosity }} -p:ReferenceType=${{ parameters.referenceType }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + -p:LoggingPackageVersion=${{ parameters.loggingPackageVersion }} -p:SqlClientPackageVersion=${{ parameters.mdsPackageVersion }} + -p:SqlServerPackageVersion=${{ parameters.sqlServerPackageVersion }} # Explicitly unset the $PLATFORM environment variable that is set by the # 'ADO Build properties' Library in the ADO SqlClientDrivers public @@ -208,6 +232,16 @@ jobs: artifactName: ${{ parameters.mdsArtifactsName }} targetPath: $(Build.SourcesDirectory)/packages + # Download the SqlServer package artifacts into packages/. + # + # MDS depends on SqlServer.Server, so the package must be available for + # transitive restore. + - task: DownloadPipelineArtifact@2 + displayName: Download SqlServer Package Artifacts + inputs: + artifactName: ${{ parameters.sqlServerArtifactsName }} + targetPath: $(Build.SourcesDirectory)/packages + # Install the .NET SDK and Runtimes. - template: /eng/pipelines/steps/install-dotnet.yml@self parameters: diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 142e5e21fe..438b011fab 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -24,35 +24,35 @@ variables: # Abstractions library assembly file version - name: abstractionsAssemblyFileVersion - value: 1.0.0.$(assemblyBuildNumber) + value: 1.1.0.$(assemblyBuildNumber) # Abstractions library NuGet package version - name: abstractionsPackageVersion - value: 1.0.0-ci$(Build.BuildNumber) + value: 1.1.0-preview1ci$(Build.BuildNumber) # AKV provider assembly file version - name: akvAssemblyFileVersion - value: 7.0.0.$(assemblyBuildNumber) + value: 7.1.0.$(assemblyBuildNumber) # AKV provider NuGet package version - name: akvPackageVersion - value: 7.0.0-ci$(Build.BuildNumber) + value: 7.1.0-preview1ci$(Build.BuildNumber) # Azure library assembly file version - name: azureAssemblyFileVersion - value: 1.0.0.$(assemblyBuildNumber) + value: 1.1.0.$(assemblyBuildNumber) # Azure library NuGet package version - name: azurePackageVersion - value: 1.0.0-ci$(Build.BuildNumber) + value: 1.1.0-preview1ci$(Build.BuildNumber) # Logging library assembly file version - name: loggingAssemblyFileVersion - value: 1.0.0.$(assemblyBuildNumber) + value: 1.1.0.$(assemblyBuildNumber) # Logging library NuGet package version - name: loggingPackageVersion - value: 1.0.0-ci$(Build.BuildNumber) + value: 1.1.0-preview1ci$(Build.BuildNumber) # MDS library NuGet package version # NOTE: This differs from the other structures! MdsVersions.props will deconstruct a provided @@ -66,7 +66,7 @@ variables: # assembly build number, so this must remain a three-part SemVer base with a prerelease # suffix, not a four-part numeric version. - name: sqlServerPackageVersion - value: 1.0.0-ci$(AssemblyBuildNumber) + value: 1.1.0-preview1ci$(AssemblyBuildNumber) # Local NuGet feed directory where downloaded pipeline artifacts are placed. # NuGet.config references this as a local package source for restore. diff --git a/eng/pipelines/onebranch/stages/build-stages.yml b/eng/pipelines/onebranch/stages/build-stages.yml index 096da7e949..62f26b7aa3 100644 --- a/eng/pipelines/onebranch/stages/build-stages.yml +++ b/eng/pipelines/onebranch/stages/build-stages.yml @@ -222,6 +222,9 @@ stages: - artifactName: '${{ parameters.loggingArtifactsName }}' shortName: 'Logging' version: '${{ parameters.loggingPackageVersion }}' + - artifactName: '${{ parameters.sqlServerArtifactsName }}' + shortName: 'SqlServer' + version: '${{ parameters.sqlServerPackageVersion }}' fileVersion: '${{ parameters.sqlClientFileVersion }}' packageFullName: 'Microsoft.Data.SqlClient' packageShortName: 'SqlClient' @@ -284,6 +287,9 @@ stages: - artifactName: '${{ parameters.sqlClientArtifactsName }}' shortName: 'SqlClient' version: '${{ parameters.sqlClientPackageVersion }}' + - artifactName: '${{ parameters.sqlServerArtifactsName }}' + shortName: 'SqlServer' + version: '${{ parameters.sqlServerPackageVersion }}' fileVersion: '${{ parameters.akvProviderFileVersion }}' packageFullName: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' packageShortName: 'AkvProvider' diff --git a/eng/pipelines/onebranch/steps/build-buildproj-step.yml b/eng/pipelines/onebranch/steps/build-buildproj-step.yml index 9e02243537..080896ee10 100644 --- a/eng/pipelines/onebranch/steps/build-buildproj-step.yml +++ b/eng/pipelines/onebranch/steps/build-buildproj-step.yml @@ -56,6 +56,7 @@ steps: -t:Build${{ parameters.packageShortName }} -p:Configuration=${{ parameters.buildConfiguration }} -p:ReferenceType=Package + -p:SkipDependencyPack=true -p:SigningKeyPath="$(keyFile.secureFilePath)" -p:BuildNumber="$(Build.BuildNumber)" -p:PackageVersion${{ parameters.packageShortName }}="${{ parameters.packageVersion }}" diff --git a/eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml b/eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml index 1f7c22a554..89177f1d88 100644 --- a/eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml +++ b/eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml @@ -50,6 +50,7 @@ steps: -t:Build${{ parameters.packageShortName }} -p:Configuration=Release -p:ReferenceType=Package + -p:SkipDependencyPack=true -p:BuildNumber="$(Build.BuildNumber)" -p:PackageVersion${{ parameters.packageShortName }}="${{ parameters.packageVersion }}" ${{ parameters.dependencyArguments }} diff --git a/eng/pipelines/stages/build-azure-package-ci-stage.yml b/eng/pipelines/stages/build-azure-package-ci-stage.yml index 3b57ce8c3b..1da56a18ef 100644 --- a/eng/pipelines/stages/build-azure-package-ci-stage.yml +++ b/eng/pipelines/stages/build-azure-package-ci-stage.yml @@ -101,6 +101,20 @@ parameters: - detailed - diagnostic + # The name of the Logging pipeline artifacts to download. + # + # This is used when the referenceType is 'Package'. + - name: loggingArtifactsName + type: string + default: Logging.Artifacts + + # The Logging package version to depend on. + # + # This is used when the referenceType is 'Package'. + - name: loggingPackageVersion + type: string + default: '' + # The name of the MDS pipeline artifacts to download. # # This is used when the referenceType is 'Package'. @@ -114,6 +128,21 @@ parameters: - name: mdsPackageVersion type: string + # The name of the SqlServer pipeline artifacts to download. + # + # This is used when the referenceType is 'Package'. MDS depends on + # SqlServer.Server, so the package must be available for transitive restore. + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + + # The SqlServer package version to depend on. + # + # This is used when the referenceType is 'Package'. + - name: sqlServerPackageVersion + type: string + default: '' + # The C# project reference type to use when building and packing the packages. - name: referenceType type: string @@ -152,8 +181,12 @@ stages: displayNamePrefix: Linux dotnetVerbosity: ${{ parameters.dotnetVerbosity }} jobNameSuffix: linux + loggingArtifactsName: ${{ parameters.loggingArtifactsName }} + loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} netFrameworkRuntimes: [] netRuntimes: [net8.0, net9.0, net10.0] poolName: ${{ parameters.azurePoolName }} @@ -170,8 +203,12 @@ stages: displayNamePrefix: Linux Integration dotnetVerbosity: ${{ parameters.dotnetVerbosity }} jobNameSuffix: linux_integration + loggingArtifactsName: ${{ parameters.loggingArtifactsName }} + loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} netFrameworkRuntimes: [] netRuntimes: [net8.0, net9.0, net10.0] poolName: ${{ parameters.adoPoolName }} @@ -197,8 +234,12 @@ stages: displayNamePrefix: Win dotnetVerbosity: ${{ parameters.dotnetVerbosity }} jobNameSuffix: windows + loggingArtifactsName: ${{ parameters.loggingArtifactsName }} + loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} netFrameworkRuntimes: [net462] netRuntimes: [net8.0, net9.0, net10.0] poolName: ${{ parameters.azurePoolName }} @@ -215,8 +256,12 @@ stages: displayNamePrefix: Win Integration dotnetVerbosity: ${{ parameters.dotnetVerbosity }} jobNameSuffix: windows_integration + loggingArtifactsName: ${{ parameters.loggingArtifactsName }} + loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} netFrameworkRuntimes: [net462] netRuntimes: [net8.0, net9.0, net10.0] poolName: ${{ parameters.adoPoolName }} @@ -251,8 +296,12 @@ stages: displayNamePrefix: macOS dotnetVerbosity: ${{ parameters.dotnetVerbosity }} jobNameSuffix: macos + loggingArtifactsName: ${{ parameters.loggingArtifactsName }} + loggingPackageVersion: ${{ parameters.loggingPackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} netFrameworkRuntimes: [] netRuntimes: [net8.0, net9.0, net10.0] poolName: ${{ parameters.azurePoolName }} diff --git a/eng/pipelines/stages/build-sqlclient-package-ci-stage.yml b/eng/pipelines/stages/build-sqlclient-package-ci-stage.yml index 57343a0006..858f1c8500 100644 --- a/eng/pipelines/stages/build-sqlclient-package-ci-stage.yml +++ b/eng/pipelines/stages/build-sqlclient-package-ci-stage.yml @@ -56,6 +56,16 @@ parameters: - name: akvPackageVersion type: string + # The name of the SqlServer pipeline artifacts to download. + - name: sqlServerArtifactsName + type: string + default: SqlServer.Artifacts + + # The SqlServer package version. + - name: sqlServerPackageVersion + type: string + default: $(sqlServerPackageVersion) + # The C# project reference type to use when building and packing the packages. - name: referenceType type: string @@ -97,6 +107,8 @@ stages: mdsPackageVersion: ${{ parameters.mdsPackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} akvPackageVersion: ${{ parameters.akvPackageVersion }} + sqlServerArtifactsName: ${{ parameters.sqlServerArtifactsName }} + sqlServerPackageVersion: ${{ parameters.sqlServerPackageVersion }} ${{ if ne(parameters.SNIVersion, '') }}: prebuildSteps: - template: /eng/pipelines/common/templates/steps/override-sni-version.yml@self diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e760ebe9f3..f8ac03e168 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -35,7 +35,11 @@ - + + $(AkvProviderAssemblyVersion) $(AkvProviderFileVersion) diff --git a/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props index 31f85b7da4..dd9ddc03d5 100644 --- a/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props @@ -13,13 +13,23 @@ NuGet package. It should be in the form: (Major).(Minor).(Patch)[-(Suffix)(BuildNumber)] --> + + + true + + - 7.0.0 + 7.1.0-preview1 diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj index 45acd55488..0a9d29aa1e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -22,7 +22,10 @@ - + + $(AbstractionsAssemblyVersion) $(AbstractionsFileVersion) @@ -71,9 +74,9 @@ + Condition="'$(ReferenceType)' != 'Package'" /> + Condition="'$(ReferenceType)' == 'Package'" /> diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props index 14daeda570..cc6d4ca14a 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props @@ -13,6 +13,16 @@ naming. It must be in the form: (Major).0.0.0 --> + + + true + + - 1.0.0 + 1.1.0-preview1 diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj index 423c55387f..dddf5673f9 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj @@ -22,7 +22,10 @@ - + + $(AzureAssemblyVersion) $(AzureFileVersion) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props index 3c4bb9dfac..636face4e6 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props @@ -13,13 +13,23 @@ form: (Major).(Minor).(Patch).(BuildNumber) --> + + + true + + - 1.0.0 + 1.1.0-preview1 diff --git a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj index f8461d8b92..d1c3e0fc5c 100644 --- a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj +++ b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj @@ -24,7 +24,10 @@ - + + $(LoggingAssemblyVersion) $(LoggingFileVersion) diff --git a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props index eeb25b93ca..9d031d21d0 100644 --- a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props @@ -13,6 +13,16 @@ naming. It must be in the form: (Major).0.0.0 --> + + + true + + - 1.0.0 + 1.1.0-preview1 diff --git a/src/Microsoft.Data.SqlClient/Versions.props b/src/Microsoft.Data.SqlClient/Versions.props index 2a915285b0..712263b73e 100644 --- a/src/Microsoft.Data.SqlClient/Versions.props +++ b/src/Microsoft.Data.SqlClient/Versions.props @@ -13,6 +13,16 @@ package. It should be in the form: (Major).(Minor).(Patch)[-(Suffix)(BuildNumber)] --> + + + true + + - + + $(SqlClientAssemblyVersion) $(SqlClientFileVersion) @@ -112,9 +115,9 @@ + + + + + @@ -156,7 +166,6 @@ - @@ -170,7 +179,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index 0b6282965d..fe871099e6 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -11,7 +11,10 @@ - + + $(SqlClientAssemblyVersion) $(SqlClientFileVersion) @@ -79,6 +82,13 @@ + + + + + @@ -112,7 +122,6 @@ - @@ -124,7 +133,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index cd7fdb4b79..d7a7ef060a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -50,7 +50,10 @@ - + + $(SqlClientAssemblyVersion) $(SqlClientFileVersion) @@ -87,6 +90,7 @@ $(RepoRoot)/src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj $(RepoRoot)/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props $(RepoRoot)/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props + $(RepoRoot)/src/Microsoft.SqlServer.Server/Versions.props @@ -129,13 +133,15 @@ + + <_AbstractionsPackageVersionTrimmed>$([System.String]::Copy('$(AbstractionsPackageVersion)').Trim()) <_LoggingPackageVersionTrimmed>$([System.String]::Copy('$(LoggingPackageVersion)').Trim()) + <_SqlServerPackageVersionTrimmed>$([System.String]::Copy('$(SqlServerPackageVersion)').Trim()) - + - <_SqlClientPackNuspecExpandedText>$([System.IO.File]::ReadAllText('$(SqlClientPackNuspecTemplatePath)').Replace('$AbstractionsPackageVersion$','$(AbstractionsPackageVersion)').Replace('$LoggingPackageVersion$','$(LoggingPackageVersion)')) + <_SqlClientPackNuspecExpandedText>$([System.IO.File]::ReadAllText('$(SqlClientPackNuspecTemplatePath)').Replace('$AbstractionsPackageVersion$','$(AbstractionsPackageVersion)').Replace('$LoggingPackageVersion$','$(LoggingPackageVersion)').Replace('$SqlServerPackageVersion$','$(SqlServerPackageVersion)')) - + @@ -73,7 +73,7 @@ - + @@ -85,7 +85,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj index 3f014f9fbe..71e4f305e1 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj +++ b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj @@ -3,6 +3,7 @@ Microsoft.Data.SqlClient.TestCommon Microsoft.Data.SqlClient.TestCommon enable + enable + + + + + @@ -367,7 +374,6 @@ - @@ -394,7 +400,6 @@ - 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 3719a09328..b590a7c451 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs @@ -35,6 +35,9 @@ public static class SqlServerTypesTest // Synapse: Parse error at line: 1, column: 48: Incorrect syntax near 'hierarchyid'. [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + #if NETFRAMEWORK + [Trait("Category", "signed")] // Requires strong-name signed Microsoft.SqlServer.Server + #endif public static void GetSchemaTableTest() { string db = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString).InitialCatalog; @@ -61,6 +64,9 @@ public static void GetSchemaTableTest() // Synapse: Parse error at line: 1, column: 48: Incorrect syntax near 'hierarchyid'. [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + #if NETFRAMEWORK + [Trait("Category", "signed")] // Requires strong-name signed Microsoft.SqlServer.Server + #endif public static void GetValueTest() { using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) @@ -219,6 +225,9 @@ void ActAndAssert(int index, string expectedHexString) // Synapse: Parse error at line: 1, column: 41: Incorrect syntax near 'hierarchyid'. [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + #if NETFRAMEWORK + [Trait("Category", "signed")] // Requires strong-name signed Microsoft.SqlServer.Server + #endif public static void TestUdtSchemaMetadata() { using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) @@ -373,6 +382,9 @@ private static string GetUdtName(Type udtClrType) // Synapse: Parse error at line: 1, column: 8: Incorrect syntax near 'geometry'. [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + #if NETFRAMEWORK + [Trait("Category", "signed")] // Requires strong-name signed Microsoft.SqlServer.Server + #endif public static void TestSqlServerTypesInsertAndRead() { string tableName = DataTestUtility.GetLongName("Type"); 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 d481e4cca6..c98b9b3fae 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 @@ -1,4 +1,4 @@ - + Address Address @@ -16,6 +16,9 @@ - + + 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 6066c1c751..db53171c6e 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 @@ -1,4 +1,4 @@ - + Circle Circle @@ -16,6 +16,9 @@ - + + 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 67f26c9ae9..e115355bfe 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 @@ -16,6 +16,9 @@ - + + 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 aa1dd19dd2..02e0193f82 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 @@ -16,6 +16,9 @@ - + + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index 8c59d27572..82e900e54c 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -39,16 +39,26 @@ + + + + + + - @@ -65,7 +75,6 @@ - diff --git a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj index ead90d9eac..a995fa13b9 100644 --- a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj +++ b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj @@ -6,7 +6,10 @@ - + + $(SqlServerAssemblyVersion) $(SqlServerFileVersion) diff --git a/src/Microsoft.SqlServer.Server/Versions.props b/src/Microsoft.SqlServer.Server/Versions.props index 0a342f9426..3615bd95a8 100644 --- a/src/Microsoft.SqlServer.Server/Versions.props +++ b/src/Microsoft.SqlServer.Server/Versions.props @@ -13,6 +13,16 @@ naming. It must be in the form: (Major).0.0.0 --> + + + true + + - 1.0.0 + 1.1.0-preview1 From 0759beef16ec4101f7b32a1745d517b68893c392 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Wed, 20 May 2026 14:44:11 -0700 Subject: [PATCH 113/298] Remove mcp.json (#4296) * Move from .vscode/mcp.json to .mcp.json * Clean up MCP servers * Delete .mcp.json --- .vscode/mcp.json | 75 ------------------------------------------------ 1 file changed, 75 deletions(-) delete mode 100644 .vscode/mcp.json diff --git a/.vscode/mcp.json b/.vscode/mcp.json deleted file mode 100644 index a3f5378943..0000000000 --- a/.vscode/mcp.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "servers": { - "ado": { - "args": [ - "-y", - "@azure-devops/mcp", - "SqlClientDrivers" - ], - "command": "npx", - "type": "stdio" - }, - "bluebird_ctaip": { - "headers": { - "x-mcp-ec-branch": "certAuth", - "x-mcp-ec-organization": "sqlclientdrivers", - "x-mcp-ec-project": "ADO.NET", - "x-mcp-ec-repository": "Microsoft.Data.SqlClient.Ctaip" - }, - "type": "http", - "url": "https://mcp.bluebird-ai.net/" - }, - "bluebird_onebranchwiki": { - "headers": { - "x-mcp-ec-branch": "main", - "x-mcp-ec-organization": "onebranch", - "x-mcp-ec-project": "OneBranch Customer Wiki", - "x-mcp-ec-repository": "OneBranch-Customer-Wiki.v2" - }, - "type": "http", - "url": "https://mcp.bluebird-ai.net/" - }, - "bluebird-mcp-1es-docs": { - "headers": { - "x-mcp-ec-branch": "main", - "x-mcp-ec-organization": "mseng", - "x-mcp-ec-project": "1ES", - "x-mcp-ec-repository": "1ES-on-EngHub" - }, - "type": "http", - "url": "https://mcp.bluebird-ai.net/" - }, - "bluebird-mcp-sni": { - "headers": { - "x-mcp-ec-branch": "master", - "x-mcp-ec-organization": "SqlClientDrivers", - "x-mcp-ec-project": "ADO.NET", - "x-mcp-ec-repository": "Microsoft.Data.SqlClient.SNI" - }, - "type": "http", - "url": "https://mcp.bluebird-ai.net/" - }, - "bluebird-mcp-sqlclient": { - "headers": { - "x-mcp-ec-branch": "internal/main", - "x-mcp-ec-organization": "SqlClientDrivers", - "x-mcp-ec-project": "ADO.NET", - "x-mcp-ec-repository": "dotnet-sqlclient" - }, - "type": "http", - "url": "https://mcp.bluebird-ai.net/" - }, - "github": { - "type": "http", - "url": "https://api.githubcopilot.com/mcp/" - }, - "icm": { - "type": "http", - "url": "https://icm-mcp-prod.azure-api.net/v1/" - }, - "microsoft-learn": { - "type": "http", - "url": "https://learn.microsoft.com/api/mcp" - } - } -} \ No newline at end of file From 8da13c8d8af6b6acfab20ba77b3062bf3776681c Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 21 May 2026 12:40:49 -0300 Subject: [PATCH 114/298] Fix LoginWithFailover missing parser state check (#4140) --- .github/copilot-instructions.md | 1 + .github/instructions/features.instructions.md | 1 + AGENTS.md | 9 +- .../Connection/SqlConnectionInternal.cs | 12 +- .../Data/SqlClient/LocalAppContextSwitches.cs | 25 ++ .../Common/LocalAppContextSwitchesHelper.cs | 15 + .../SqlClient/LocalAppContextSwitchesTest.cs | 1 + .../ConnectionFailoverTests.cs | 342 +++++++++++++++++- .../TDS.Servers/TransientTdsErrorTdsServer.cs | 2 +- .../TransientTdsErrorTdsServerArguments.cs | 8 + 10 files changed, 409 insertions(+), 7 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e024b30d6f..c0bbc25457 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -50,6 +50,7 @@ This project includes several key products and libraries that facilitate SQL Ser - **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. + - Compatibility switch: `Switch.Microsoft.Data.SqlClient.UseLegacyFailoverAlternationOnLoginSqlErrors` (default `false`) can restore legacy alternation behavior in `LoginWithFailover` for login-phase SQL errors. - **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. diff --git a/.github/instructions/features.instructions.md b/.github/instructions/features.instructions.md index 3ecba2cc4e..34262b8db6 100644 --- a/.github/instructions/features.instructions.md +++ b/.github/instructions/features.instructions.md @@ -246,6 +246,7 @@ AppContext switches allow runtime behavior changes without modifying connection | `Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault` | `false` | Sets `MultiSubnetFailover=true` as the default for all connections | | `Switch.Microsoft.Data.SqlClient.EnableUserAgent` | varies | Controls sending user agent information to SQL Server | | `Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner` | `false` | Ignores failover partner information sent by the server | +| `Switch.Microsoft.Data.SqlClient.UseLegacyFailoverAlternationOnLoginSqlErrors` | `false` | Restores legacy `LoginWithFailover` alternation for login-phase SQL errors when parser state is not `Closed` | | `Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior` | `false` | Restores legacy null handling for rowversion columns | | `Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour` | `false` | Restores legacy zero-scale behavior for time/datetime2/datetimeoffset | | `Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking` | `false` | Makes ReadAsync behave synchronously (legacy compat) | diff --git a/AGENTS.md b/AGENTS.md index 42f3a39399..06bc0edfd1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,10 +79,11 @@ Do **not** create branches directly under `main`, `dev/`, or any other top-level ### Bug Fix Workflow 1. Understand the issue from the bug report 2. Locate relevant code in `src/Microsoft.Data.SqlClient/src/` (do NOT modify legacy `netcore/src/` or `netfx/src/`) -3. Write a failing test that reproduces the issue -4. Implement the fix -5. Ensure all tests pass -6. Update documentation if behavior changes +3. Check `.github/instructions/features.instructions.md` for existing AppContext switches (including failover compatibility switches) before introducing behavior changes +4. Write a failing test that reproduces the issue +5. Implement the fix +6. Ensure all tests pass +7. Update documentation if behavior changes ### Feature Implementation 1. Review the feature specification diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index e62541137c..6d067bab7f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -3635,7 +3635,17 @@ private void LoginWithFailover( continue; } - if (IsDoNotRetryConnectError(sqlex) || timeout.IsExpired) + bool isLoginPhaseSqlError = _parser?.State is not TdsParserState.Closed; + + // If state != closed, indicates that the parser encountered an error while + // processing the login response (e.g. an explicit error token). Transient + // network errors that impact connectivity will result in parser state being + // closed. Only network-level errors should trigger failover alternation; + // login-phase errors (like transient errors) should be thrown so they can + // be handled by the outer ConnectRetryCount loop. + if ((isLoginPhaseSqlError && !LocalAppContextSwitches.UseLegacyFailoverAlternationOnLoginSqlErrors) || + IsDoNotRetryConnectError(sqlex) || + timeout.IsExpired) { // No more time to try again. // Caller will call LoginFailure() 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 16e0abb7ec..0177338bf2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -58,6 +58,13 @@ internal static class LocalAppContextSwitches private const string IgnoreServerProvidedFailoverPartnerString = "Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; + /// + /// The name of the app context switch that controls whether failover + /// alternation should use legacy behavior for login-phase SQL errors. + /// + private const string UseLegacyFailoverAlternationOnLoginSqlErrorsString = + "Switch.Microsoft.Data.SqlClient.UseLegacyFailoverAlternationOnLoginSqlErrors"; + /// /// The name of the app context switch that controls whether to preserve /// legacy behavior where Timestamp/RowVersion fields return empty byte @@ -182,6 +189,11 @@ private enum SwitchValue : byte /// private static SwitchValue s_ignoreServerProvidedFailoverPartner = SwitchValue.None; + /// + /// The cached value of the UseLegacyFailoverAlternationOnLoginSqlErrors switch. + /// + private static SwitchValue s_useLegacyFailoverAlternationOnLoginSqlErrors = SwitchValue.None; + /// /// The cached value of the LegacyRowVersionNullBehavior switch. /// @@ -409,6 +421,19 @@ public static bool GlobalizationInvariantMode defaultValue: false, ref s_ignoreServerProvidedFailoverPartner); + /// + /// When set to true, LoginWithFailover preserves legacy behavior and may + /// alternate to the failover partner on login-phase SQL errors where the + /// parser state is not Closed. + /// + /// The default value of this switch is false. + /// + public static bool UseLegacyFailoverAlternationOnLoginSqlErrors => + AcquireAndReturn( + UseLegacyFailoverAlternationOnLoginSqlErrorsString, + defaultValue: false, + ref s_useLegacyFailoverAlternationOnLoginSqlErrors); + /// /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a /// field with type Timestamp/RowVersion would return an empty byte array. diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 8102f47d51..11523b3f83 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -47,6 +47,7 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly bool? _globalizationInvariantModeOriginal; #endif private readonly bool? _ignoreServerProvidedFailoverPartnerOriginal; + private readonly bool? _useLegacyFailoverAlternationOnLoginSqlErrorsOriginal; private readonly bool? _legacyRowVersionNullBehaviorOriginal; private readonly bool? _legacyVarTimeZeroScaleBehaviourOriginal; private readonly bool? _makeReadAsyncBlockingOriginal; @@ -98,6 +99,8 @@ public LocalAppContextSwitchesHelper() #endif _ignoreServerProvidedFailoverPartnerOriginal = GetSwitchValue("s_ignoreServerProvidedFailoverPartner"); + _useLegacyFailoverAlternationOnLoginSqlErrorsOriginal = + GetSwitchValue("s_useLegacyFailoverAlternationOnLoginSqlErrors"); _legacyRowVersionNullBehaviorOriginal = GetSwitchValue("s_legacyRowVersionNullBehavior"); _legacyVarTimeZeroScaleBehaviourOriginal = @@ -154,6 +157,9 @@ public void Dispose() SetSwitchValue( "s_ignoreServerProvidedFailoverPartner", _ignoreServerProvidedFailoverPartnerOriginal); + SetSwitchValue( + "s_useLegacyFailoverAlternationOnLoginSqlErrors", + _useLegacyFailoverAlternationOnLoginSqlErrorsOriginal); SetSwitchValue( "s_legacyRowVersionNullBehavior", _legacyRowVersionNullBehaviorOriginal); @@ -242,6 +248,15 @@ public bool? IgnoreServerProvidedFailoverPartner set => SetSwitchValue("s_ignoreServerProvidedFailoverPartner", value); } + /// + /// Get or set the UseLegacyFailoverAlternationOnLoginSqlErrors switch value. + /// + public bool? UseLegacyFailoverAlternationOnLoginSqlErrors + { + get => GetSwitchValue("s_useLegacyFailoverAlternationOnLoginSqlErrors"); + set => SetSwitchValue("s_useLegacyFailoverAlternationOnLoginSqlErrors", value); + } + /// /// Get or set the LegacyRowVersionNullBehavior switch value. /// diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index a5db02faa0..a468d8fd37 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -28,6 +28,7 @@ public void TestDefaultAppContextSwitchValues() Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); Assert.False(LocalAppContextSwitches.TruncateScaledDecimal); Assert.False(LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner); + Assert.False(LocalAppContextSwitches.UseLegacyFailoverAlternationOnLoginSqlErrors); Assert.False(LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault); #if NET Assert.False(LocalAppContextSwitches.GlobalizationInvariantMode); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index 33a7d7c600..f2c50285ed 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -4,6 +4,7 @@ using System; using System.Data; +using System.Threading.Tasks; using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.Tests.Common; using Microsoft.SqlServer.TDS.Servers; @@ -172,7 +173,7 @@ public void NetworkTimeout_ShouldFail() InitialCatalog = "master",// Required for failover partner to work ConnectTimeout = 1, ConnectRetryInterval = 1, - ConnectRetryCount = 0, // Disable retry + ConnectRetryCount = 0, // Disable retry Encrypt = false, MultiSubnetFailover = false, #if NETFRAMEWORK @@ -336,6 +337,10 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); } + /// + /// Verifies login-phase transient SQL errors are retried on the primary endpoint and + /// do not trigger failover-partner alternation. + /// [Theory] [InlineData(40613)] [InlineData(42108)] @@ -372,6 +377,8 @@ public void TransientFault_ShouldConnectToPrimary(uint errorCode) using SqlConnection connection = new(builder.ConnectionString); // Act + // First login receives the transient token; outer connect retry opens a fresh parser + // and retries against the same primary endpoint. connection.Open(); // Assert @@ -380,6 +387,8 @@ public void TransientFault_ShouldConnectToPrimary(uint errorCode) // Failures should prompt the client to return to the original server, resulting in a login count of 2 Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); + // Login-phase errors must NOT trigger failover alternation + Assert.Equal(0, failoverServer.PreLoginCount); } [Theory] @@ -430,6 +439,10 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) Assert.Fail(); } + /// + /// Verifies user-provided failover partner does not change behavior for login-phase + /// transient SQL errors; retries stay on primary. + /// [Theory] [InlineData(40613)] [InlineData(42108)] @@ -467,6 +480,8 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e using SqlConnection connection = new(builder.ConnectionString); // Act + // Even with a configured partner, this path should use outer connect retry + // against primary rather than alternation inside LoginWithFailover. connection.Open(); // Assert @@ -475,6 +490,8 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e // Failures should prompt the client to return to the original server, resulting in a login count of 2 Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); + // Login-phase errors must NOT trigger failover alternation + Assert.Equal(0, failoverServer.PreLoginCount); } [Theory] @@ -592,5 +609,328 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // 1 for the failover connection Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); } + + /// + /// Async parity for primary-only retry behavior on login-phase transient SQL errors. + /// + [Theory] + [InlineData(40613)] + [InlineData(42108)] + [InlineData(42109)] + public async Task TransientFault_Async_ShouldConnectToPrimary_NotFailover(uint errorCode) + { + // Async parity for TransientFault_ShouldConnectToPrimary. + // A transient login-token error must be retried against the primary; + // the failover partner must never be contacted. + + using TdsServer failoverServer = new( + new TdsServerArguments + { + FailoverPartner = "localhost,1234", + }); + failoverServer.Start(); + + using TransientTdsErrorTdsServer server = new( + new TransientTdsErrorTdsServerArguments() + { + IsEnabledTransientError = true, + Number = errorCode, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + InitialCatalog = "master", + ConnectTimeout = 30, + ConnectRetryInterval = 1, + Encrypt = false, + Pooling = false, + }; + using SqlConnection connection = new(builder.ConnectionString); + + // Asserts async open follows the same retry and failover-selection rules as sync. + await connection.OpenAsync(); + + Assert.Equal(ConnectionState.Open, connection.State); + Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); + // Login-phase errors must NOT trigger failover alternation + Assert.Equal(0, failoverServer.PreLoginCount); + } + + /// + /// Async parity with user-provided partner: login-phase transient SQL errors should + /// still retry on primary without failover alternation. + /// + [Theory] + [InlineData(40613)] + [InlineData(42108)] + [InlineData(42109)] + public async Task TransientFault_WithUserProvidedPartner_Async_ShouldConnectToPrimary_NotFailover(uint errorCode) + { + // Async parity for TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary. + // Even with a user-provided failover partner, a login-token error must not + // cause alternation to the failover server. + + using TdsServer failoverServer = new( + new TdsServerArguments + { + FailoverPartner = "localhost,1234", + }); + failoverServer.Start(); + + using TransientTdsErrorTdsServer server = new( + new TransientTdsErrorTdsServerArguments() + { + IsEnabledTransientError = true, + Number = errorCode, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + InitialCatalog = "master", + ConnectTimeout = 30, + ConnectRetryInterval = 1, + Encrypt = false, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }; + using SqlConnection connection = new(builder.ConnectionString); + + // Asserts async open with explicit partner still avoids failover alternation. + await connection.OpenAsync(); + + Assert.Equal(ConnectionState.Open, connection.State); + Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); + // Login-phase errors must NOT trigger failover alternation + Assert.Equal(0, failoverServer.PreLoginCount); + } + + /// + /// Verifies pooled connections are not cleared and failover is not attempted when a + /// login-phase transient SQL error occurs with a user-provided failover partner. + /// + [Theory] + [InlineData(40613)] + [InlineData(42108)] + [InlineData(42109)] + public void TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(uint errorCode) + { + // With pooling enabled and a user-provided failover partner, a transient + // login-token error must not clear the pool and must not contact the failover server. + + using TdsServer failoverServer = new( + new TdsServerArguments + { + FailoverPartner = "localhost,1234", + }); + failoverServer.Start(); + + // Start with errors disabled so the pool warms up successfully. + using TransientTdsErrorTdsServer server = new( + new TransientTdsErrorTdsServerArguments() + { + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + InitialCatalog = "master", + ConnectTimeout = 30, + ConnectRetryInterval = 1, + Encrypt = SqlConnectionEncryptOption.Optional, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + Pooling = true, + }; + + // Keep one connection open so the next Open() cannot reuse it and must perform login. + using SqlConnection warmup = new(builder.ConnectionString); + warmup.Open(); + + // Enable the transient error for the next login attempt. + server.SetErrorBehavior(true, errorCode); + + // ConnectRetryCount > 0 (default 1) so the client retries and succeeds. + using SqlConnection connection = new(builder.ConnectionString); + connection.Open(); + Assert.Equal(ConnectionState.Open, connection.State); + Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); + + connection.Close(); + warmup.Close(); + + // If the pool is not cleared, this open should reuse a pooled connection without a new login. + using SqlConnection pooledConnection = new(builder.ConnectionString); + pooledConnection.Open(); + + Assert.Equal(ConnectionState.Open, pooledConnection.State); + Assert.Equal($"localhost,{server.EndPoint.Port}", pooledConnection.DataSource); + + // 1 warmup login + 1 failed login + 1 retry login. + Assert.Equal(3, server.PreLoginCount - server.AbandonedPreLoginCount); + Assert.Equal(3, server.Login7Count); + // Failover server must never have been contacted. + Assert.Equal(0, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); + Assert.Equal(0, failoverServer.Login7Count); + } + + /// + /// Verifies ConnectRetryCount=0 propagates login-phase transient SQL errors immediately + /// and never attempts failover alternation. + /// + [Theory] + [InlineData(40613)] + [InlineData(42108)] + [InlineData(42109)] + public void TransientFault_RetryDisabled_WithUserProvidedPartner_ShouldFail_NotFailover(uint errorCode) + { + // When ConnectRetryCount = 0 and the server returns a login-phase token error, + // the exception must propagate immediately and the failover partner must not be + // contacted (parser state is not Closed, so the new guard must kick in). + + using TdsServer failoverServer = new( + new TdsServerArguments + { + FailoverPartner = "localhost,1234", + }); + failoverServer.Start(); + + using TransientTdsErrorTdsServer server = new( + new TransientTdsErrorTdsServerArguments() + { + IsEnabledTransientError = true, + Number = errorCode, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + InitialCatalog = "master", + ConnectTimeout = 30, + ConnectRetryInterval = 1, + ConnectRetryCount = 0, + Encrypt = false, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }; + using SqlConnection connection = new(builder.ConnectionString); + + // No outer connect retry is allowed, so the first transient error should surface. + SqlException ex = Assert.Throws(() => connection.Open()); + + Assert.Equal((int)errorCode, ex.Number); + Assert.Equal(ConnectionState.Closed, connection.State); + // The parser was not closed (login-phase error), so the failover alternation branch + // must not have been entered. + Assert.Equal(0, failoverServer.PreLoginCount); + } + + /// + /// Isolates the parser-state guard by using a non-fatal login error token: without the + /// guard, LoginWithFailover alternates to partner; with the guard, retry stays on primary. + /// + [Fact] + public void NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrimary_NotFailover() + { + // This test isolates the parser-state guard added to LoginWithFailover. + // We emit a transient login error with non-fatal severity so the connection + // is not automatically doomed/broken by existing breakConnection logic. + + using TdsServer failoverServer = new( + new TdsServerArguments + { + FailoverPartner = "localhost,1234", + }); + failoverServer.Start(); + + using TransientTdsErrorTdsServer server = new( + new TransientTdsErrorTdsServerArguments() + { + IsEnabledTransientError = true, + Number = 40613, + // Use non-fatal severity so break/doom logic does not short-circuit the path. + ErrorClass = 16, + RepeatCount = 1, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + InitialCatalog = "master", + ConnectTimeout = 30, + ConnectRetryInterval = 1, + Encrypt = false, + Pooling = false, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }; + + using SqlConnection connection = new(builder.ConnectionString); + connection.Open(); + + Assert.Equal(ConnectionState.Open, connection.State); + Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); + Assert.Equal(0, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); + } + + /// + /// Verifies opt-in legacy behavior: login-phase SQL errors can alternate to the + /// failover partner when UseLegacyFailoverAlternationOnLoginSqlErrors is enabled. + /// + [Fact] + public void NonFatalTransientLoginError_WithLegacySwitch_ShouldAlternateToFailoverPartner() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyFailoverAlternationOnLoginSqlErrors = true; + + using TdsServer failoverServer = new( + new TdsServerArguments + { + FailoverPartner = "localhost,1234", + }); + failoverServer.Start(); + + using TransientTdsErrorTdsServer server = new( + new TransientTdsErrorTdsServerArguments() + { + IsEnabledTransientError = true, + Number = 40613, + // Keep the login token non-fatal so parser state, not break/doom behavior, + // drives this branch decision. + ErrorClass = 16, + RepeatCount = 1, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + InitialCatalog = "master", + ConnectTimeout = 30, + ConnectRetryInterval = 1, + Encrypt = false, + Pooling = false, + FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", + }; + + using SqlConnection connection = new(builder.ConnectionString); + connection.Open(); + + Assert.Equal(ConnectionState.Open, connection.State); + Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", connection.DataSource); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); + Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs index 6ec7ac2528..ba20fb4d02 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs @@ -89,7 +89,7 @@ private TDSMessageCollection GenerateErrorMessage(TDSMessage request) TDSUtilities.Log(Arguments.Log, "Request", request); // Prepare ERROR token with the denial details - TDSErrorToken errorToken = new TDSErrorToken(errorNumber, 1, 20, errorMessage); + TDSErrorToken errorToken = new TDSErrorToken(errorNumber, 1, Arguments.ErrorClass, errorMessage); // Log response TDSUtilities.Log(Arguments.Log, "Response", errorToken); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServerArguments.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServerArguments.cs index 6a61d9cc6f..5f1adacd61 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServerArguments.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServerArguments.cs @@ -25,5 +25,13 @@ public class TransientTdsErrorTdsServerArguments : TdsServerArguments /// The number of times the transient error should be raised. /// public int RepeatCount { get; set; } = 1; + + /// + /// Error class (severity) to emit in ERROR token. + /// The default is 20 to preserve existing fatal-error behavior. + /// Fatal starts at 20 (TdsEnums.FATAL_ERROR_CLASS), so set values below 20 + /// when a test needs to avoid automatic break/doom behavior in the client. + /// + public byte ErrorClass { get; set; } = 20; } } From ed896eb910107aa7edd6e454198b88231b97ee06 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 21 May 2026 11:45:40 -0500 Subject: [PATCH 115/298] Add CodeQL suppression "random number" dictionary key (#4276) --- .../src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs index b990270924..23fa1c9359 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs @@ -89,7 +89,7 @@ private SqlDependencyPerAppDomainDispatcher() #if NETFRAMEWORK _timeoutTimer = new Timer(new TimerCallback(TimeoutTimerCallback), null, Timeout.Infinite, Timeout.Infinite); - // If rude abort - we'll leak. This is acceptable for now. + // If rude abort - we'll leak. This is acceptable for now. AppDomain.CurrentDomain.DomainUnload += new EventHandler(UnloadEventHandler); #else _timeoutTimer = ADP.UnsafeCreateTimer( @@ -131,7 +131,7 @@ private void UnloadEventHandler(object sender, EventArgs e) } } - // When remoted across appdomains, MarshalByRefObject links by default time out if there is no activity + // When remoted across appdomains, MarshalByRefObject links by default time out if there is no activity // within a few minutes. Add this override to prevent marshaled links from timing out. #if NET [Obsolete("InitializeLifetimeService() is not supported after .Net5.0 and throws PlatformNotSupportedException.")] @@ -216,7 +216,7 @@ internal string AddCommandEntry(string commandHash, SqlDependency dep) dependencyList.Add(dep); // map command hash to notification we just created to reuse it for the next client - _commandHashToNotificationId.Add(commandHash, notificationId); + _commandHashToNotificationId.Add(commandHash, notificationId); // CodeQL [SM04207] This value is an opaque query-notification correlation identifier, not a secret or security token. It is used only for uniqueness and exact dictionary lookup after SQL Server round-trips the user data. Guid.NewGuid provides sufficient collision resistance for the expected in-process notification cardinality, and changing the generator would not materially improve security. _notificationIdToDependenciesHash.Add(notificationId, dependencyList); } From c7245542d2307db07835eb7fae35857ac8fcd6a8 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 22 May 2026 04:49:12 -0700 Subject: [PATCH 116/298] pwsh via dotnet tool (#4134) Co-authored-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 +++ BUILDGUIDE.md | 17 ++++++++---- dotnet-tools.json | 7 +++++ .../templates/jobs/ci-build-nugets-job.yml | 3 +++ .../templates/jobs/ci-run-tests-job.yml | 3 +++ eng/pipelines/steps/restore-dotnet-tools.yml | 14 ++++++++++ .../ref/Microsoft.Data.SqlClient.csproj | 18 ++++++++++--- .../SQL/AdapterTest/AdapterTest.cs | 2 ++ tools/targets/CompareMdsRefAssemblies.targets | 27 ++++++++++++------- 9 files changed, 77 insertions(+), 18 deletions(-) create mode 100644 eng/pipelines/steps/restore-dotnet-tools.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 75e4411e4b..45890dfdf4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -72,6 +72,10 @@ jobs: uses: actions/setup-dotnet@v5.2.0 with: global-json-file: global.json + + - name: Restore dotnet tools + shell: bash + run: dotnet tool restore # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index a927945c05..073f6afc68 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -19,15 +19,22 @@ on operating systems that do not support .NET Framework. As such, it is not nece ### Miscellaneous -**PowerShell** is required to run several miscellaneous tasks as part of building and packaging. On -Windows systems, either the built-in `powershell.exe` will be used, or if installed, the modern -`pwsh` will be used. On Linux and macOS systems, the `pwsh` command is required to be in the `$PATH` -environment variable. For specific instructions see: [Install -PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell) +**PowerShell** is included as a .NET local tool in this repository. Running `dotnet tool restore` +(see below) will make it available via `dotnet tool run pwsh -- `. Note that `pwsh` is not +added to PATH — it must be invoked through `dotnet tool run`. Build targets handle this +automatically; manual invocation is only needed for ad-hoc scripting. The **NuGet** binary is optional for inspection and feed-management workflows, but build and packaging flows in this repository are run through `dotnet build` against `build.proj`. +### .NET Tools + +This repository uses .NET local tools (e.g. PowerShell) that must be restored before building. Run the following from the repository root: + +```bash +dotnet tool restore +``` + ## Developer Workflow Once you've cloned the repository and made your changes to the codebase, it is time to build, test, and optionally diff --git a/dotnet-tools.json b/dotnet-tools.json index 1f59e06063..11e3494512 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -15,6 +15,13 @@ "apicompat" ], "rollForward": false + }, + "powershell": { + "version": "7.6.0", + "commands": [ + "pwsh" + ], + "rollForward": false } } } \ No newline at end of file diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index e9c3ad7c64..de9b4d284f 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -134,6 +134,9 @@ jobs: # Install the .NET SDK. - template: /eng/pipelines/steps/install-dotnet.yml@self + # Restore dotnet CLI tools (e.g. pwsh, apicompat) before building. + - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + # When we're performing a Debug build, we still want to try _compiling_ the # code in Release mode to ensure downstream pipelines don't encounter # compilation errors. We won't use the Release artifacts for anything else 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 8b79bf7274..4c42af8dc1 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -222,6 +222,9 @@ jobs: ${{ else }}: runtimes: [8.x, 9.x] + # Restore dotnet CLI tools (e.g. pwsh, apicompat) before building. + - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + - ${{ if ne(parameters.prebuildSteps, '') }}: - ${{ parameters.prebuildSteps }} # extra steps to run before the build like downloading sni and the required configuration diff --git a/eng/pipelines/steps/restore-dotnet-tools.yml b/eng/pipelines/steps/restore-dotnet-tools.yml new file mode 100644 index 0000000000..b213583e82 --- /dev/null +++ b/eng/pipelines/steps/restore-dotnet-tools.yml @@ -0,0 +1,14 @@ +################################################################################# +# 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. # +################################################################################# + +# Restores dotnet CLI tools defined in dotnet-tools.json. +# This step should be invoked after install-dotnet.yml and before any build +# steps that depend on the restored tools (e.g. pwsh, apicompat). + +steps: + - script: dotnet tool restore + displayName: Restore .NET Tools + workingDirectory: $(Build.SourcesDirectory) diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index fe871099e6..43aca3f1c1 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -44,6 +44,7 @@ $(ArtifactPath)$(AssemblyName).ref/$(ReferenceType)-$(Configuration)/ + + + + + + + + + - powershell.exe - pwsh - $(PowerShellCommand) + dotnet tool run pwsh -- -NonInteractive -ExecutionPolicy Unrestricted -Command "$(RepoRoot)tools\intellisense\TrimDocs.ps1 -inputFile '$(DocumentationFile)' -outputFile '$(DocumentationFile)'" diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs index 9bf5351a83..798890c3fa 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs @@ -107,6 +107,8 @@ public void SimpleFillTest() // TODO Synapse: Remove Northwind dependency by creating required tables in setup. [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + // https://github.com/dotnet/SqlClient/issues/4135 + [Trait("Category", "flaky")] public void FillShouldAllowRetryLogicProviderToBeInvoked() { int maxRetries = 3; diff --git a/tools/targets/CompareMdsRefAssemblies.targets b/tools/targets/CompareMdsRefAssemblies.targets index bccfa00930..47caaa7a56 100644 --- a/tools/targets/CompareMdsRefAssemblies.targets +++ b/tools/targets/CompareMdsRefAssemblies.targets @@ -59,10 +59,24 @@ Text="BaselinePackageVersion is required. Specify the published MDS NuGet package version to compare against, e.g.: /p:BaselinePackageVersion=6.1.4" /> + + + + + + + + + + - + - - - - - - - @@ -111,7 +118,7 @@ + DependsOnTargets="_DownloadBaselinePackage;_BuildRefProject"> $(_TranslateUrlPattern), $(_TranslateUrlReplacement))) - + From f873c1ac05e7736fef4a5e81347847a87babf67b Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 29 May 2026 11:42:47 -0300 Subject: [PATCH 122/298] Enable InternalsVisibleTo for UnitTests in Package mode (#4300) * Enable InternalsVisibleTo for test projects in Package mode * Pipeline: run unit tests in both Project and Package modes * Reverted some straggler leftover changes from previous attempts. * Addressed Copilot feedback. --- build.proj | 24 ++- .../templates/steps/run-all-tests-step.yml | 156 ++++++++++-------- .../src/Microsoft.Data.SqlClient.csproj | 15 +- ...Microsoft.Data.SqlClient.TestCommon.csproj | 12 +- .../Microsoft.Data.SqlClient.UnitTests.csproj | 51 ++++-- ...rosoft.Data.SqlClient.TestUtilities.csproj | 8 + .../TDS/TDS.EndPoint/TDS.EndPoint.csproj | 9 + .../tools/TDS/TDS.Servers/TDS.Servers.csproj | 6 + .../tests/tools/TDS/TDS/TDS.csproj | 6 + 9 files changed, 200 insertions(+), 87 deletions(-) diff --git a/build.proj b/build.proj index b8ac7d44ed..c13b40e246 100644 --- a/build.proj +++ b/build.proj @@ -259,6 +259,20 @@ -p:SigningKeyPath="$(SigningKeyPath)" + + + + -p:TestSigningKeyPath="$(TestSigningKeyPath)" + + - SqlClientUnit-$(OS) $(LogFilePrefix)-$(TestFramework) @@ -692,6 +702,10 @@ $(TestCodeCoverageArgument) $(TestFiltersArgument) $(TestFrameworkArgument) + $(ReferenceTypeArgument) + $(TestSigningKeyPathArgument) + $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) --results-directory "$(TestResultsFolderPath)" --logger:"trx;LogFilePrefix=$(LogFilePrefix)" @@ -862,7 +876,7 @@ AbstractionsTests-$(OS) $(LogFilePrefix)-$(TestFramework) 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 5730b8cc0d..e41c7d22b2 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -79,51 +79,62 @@ steps: condition: succeededOrFailed() - ${{if eq(parameters.operatingSystem, 'Windows')}}: - - ${{if eq(parameters.referenceType, 'Project')}}: - - task: DotNetCoreCLI@2 - displayName: 'Run Unit Tests ${{parameters.msbuildArchitecture }}' - condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) - inputs: - command: build - projects: build.proj - ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - arguments: >- - -t:TestSqlClientUnit - -p:TestFramework=${{ parameters.targetFramework }} - -p:Configuration=${{ parameters.buildConfiguration }} - -p:TestResultsFolderPath=TestResults - ${{ else }}: # x86 - arguments: >- - -t:TestSqlClientUnit - -p:TestFramework=${{ parameters.targetFramework }} - -p:Configuration=${{ parameters.buildConfiguration }} - -p:DotnetPath=${{ parameters.dotnetx86RootPath }} - -p:TestResultsFolderPath=TestResults + - task: DotNetCoreCLI@2 + displayName: 'Run Unit Tests ${{parameters.msbuildArchitecture }}' + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) + inputs: + command: build + projects: build.proj + ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: + arguments: >- + -t:TestSqlClientUnit + -p:TestFramework=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} + -p:TestResultsFolderPath=TestResults + ${{ else }}: # x86 + arguments: >- + -t:TestSqlClientUnit + -p:TestFramework=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} + -p:DotnetPath=${{ parameters.dotnetx86RootPath }} + -p:TestResultsFolderPath=TestResults - - task: DotNetCoreCLI@2 - displayName: 'Run Flaky Unit Tests ${{parameters.msbuildArchitecture }}' - condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) - inputs: - command: build - projects: build.proj - ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - arguments: >- - -t:TestSqlClientUnit - -p:TestFramework=${{ parameters.targetFramework }} - -p:Configuration=${{ parameters.buildConfiguration }} - -p:TestFilters="category=flaky" - -p:TestResultsFolderPath=TestResults - -p:TestCodeCoverage=false - ${{ else }}: # x86 - arguments: >- - -t:TestSqlClientUnit - -p:TestFramework=${{ parameters.targetFramework }} - -p:Configuration=${{ parameters.buildConfiguration }} - -p:DotnetPath=${{ parameters.dotnetx86RootPath }} - -p:TestFilters="category=flaky" - -p:TestResultsFolderPath=TestResults - -p:TestCodeCoverage=false - continueOnError: true + - task: DotNetCoreCLI@2 + displayName: 'Run Flaky Unit Tests ${{parameters.msbuildArchitecture }}' + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) + inputs: + command: build + projects: build.proj + ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: + arguments: >- + -t:TestSqlClientUnit + -p:TestFramework=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false + ${{ else }}: # x86 + arguments: >- + -t:TestSqlClientUnit + -p:TestFramework=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} + -p:DotnetPath=${{ parameters.dotnetx86RootPath }} + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false + continueOnError: true - task: DotNetCoreCLI@2 displayName: 'Run Functional Tests ${{parameters.msbuildArchitecture }}' @@ -261,33 +272,38 @@ steps: continueOnError: true - ${{ else }}: # Linux or macOS - - ${{if eq(parameters.referenceType, 'Project')}}: - - task: DotNetCoreCLI@2 - displayName: 'Run Unit Tests' - condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) - inputs: - command: build - projects: build.proj - arguments: >- - -t:TestSqlClientUnit - -p:TestFramework=${{ parameters.targetFramework }} - -p:Configuration=${{ parameters.buildConfiguration }} - -p:TestResultsFolderPath=TestResults + - task: DotNetCoreCLI@2 + displayName: 'Run Unit Tests' + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) + inputs: + command: build + projects: build.proj + arguments: >- + -t:TestSqlClientUnit + -p:TestFramework=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} + -p:TestResultsFolderPath=TestResults - - task: DotNetCoreCLI@2 - displayName: 'Run Flaky Unit Tests' - condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) - inputs: - command: build - projects: build.proj - arguments: >- - -t:TestSqlClientUnit - -p:TestFramework=${{ parameters.targetFramework }} - -p:Configuration=${{ parameters.buildConfiguration }} - -p:TestFilters="category=flaky" - -p:TestResultsFolderPath=TestResults - -p:TestCodeCoverage=false - continueOnError: true + - task: DotNetCoreCLI@2 + displayName: 'Run Flaky Unit Tests' + condition: and(eq(variables['setupSucceeded'], 'true'), succeededOrFailed()) + inputs: + command: build + projects: build.proj + arguments: >- + -t:TestSqlClientUnit + -p:TestFramework=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:PackageVersionSqlClient=${{ parameters.mdsPackageVersion }} + -p:PackageVersionSqlServer=${{ parameters.sqlServerPackageVersion }} + -p:TestFilters="category=flaky" + -p:TestResultsFolderPath=TestResults + -p:TestCodeCoverage=false + continueOnError: true - task: DotNetCoreCLI@2 displayName: 'Run Functional Tests' diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index d7a7ef060a..24c6a548f9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -62,13 +62,26 @@ - + + <_Parameter1>UnitTests + + + + <_Parameter1>UnitTests, PublicKey=00240000048000001401000006020000002400005253413100080000010001003D19684676DA365F331D00CE7BD4B8EF03E74102F39A5681B40622703D68F0298ECACECC723D3FFC1EA9365AF4958578550EA1EBEEC084B0B3757F3762449F5365E872802A4B548056760764FAD062BFEE81ED26183109AD46810E7E6E965419D0A10473680144D20C1BFE1027A5F586CA987523C06F5C126C44EA7D4F51EB023867A9F294315F95775ACEFD2D678186919458DFCCB4DE2E9F53AEFC766C7CBCEC474ED21C1616E5A9414D366D91D121C39F5FE6641295ADC058EF3FB10593BCDE2E82D9F217C2634909EEF496CD53AE78ABBEA572B871D72EBFC5378205950ABA97C7CCC2B9635D96933D5F9C9624D71FF53EE2094CF3A6BD38534D66E414B7 + + + $(RepoRoot)artifacts/ diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj index ef4ac391d6..67c5250748 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj +++ b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj @@ -14,6 +14,13 @@ false + + + + true + $(TestSigningKeyPath) + + net8.0;net9.0;net10.0 @@ -28,7 +35,10 @@ - + + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index 82e900e54c..4501d710a9 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -36,22 +36,53 @@ + + + + true + $(TestSigningKeyPath) + + - - + - - - + + + <_SqlClientRid Condition="'$(OS)' != 'Windows_NT'">unix + <_SqlClientRid Condition="'$(OS)' == 'Windows_NT'">win + + <_SqlClientPackageTfm Condition="'$(TargetFramework)' == 'net8.0'">net8.0 + <_SqlClientPackageTfm Condition="'$(TargetFramework)' == 'net462'">net462 + <_SqlClientPackageTfm Condition="'$(_SqlClientPackageTfm)' == ''">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 083cca8a61..c50a251e4f 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 @@ -2,6 +2,14 @@ netstandard2.0 + + + + + true + $(TestSigningKeyPath) + + 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 2e1a1b5c81..c9dce97ddb 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 @@ -3,6 +3,15 @@ Microsoft.SqlServer.TDS.EndPoint Microsoft.SqlServer.TDS.EndPoint netstandard2.0 + + + + + true + $(TestSigningKeyPath) + + + false 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 57d3dfadd2..b6f33b0977 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 @@ -4,6 +4,12 @@ Microsoft.SqlServer.TDS.Servers netstandard2.0 + + + + true + $(TestSigningKeyPath) + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj index a4da7c8b92..39d5aae2de 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj @@ -4,4 +4,10 @@ Microsoft.SqlServer.TDS netstandard2.0 + + + + true + $(TestSigningKeyPath) + From 61148688fff5742b5715c770e84dc7a17aea2230 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 29 May 2026 10:19:01 -0700 Subject: [PATCH 123/298] Add instructions file for determining dependency versions (#4318) * Add instructions file. * Add categorization instructions --- .../package-versions.instructions.md | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 .github/instructions/package-versions.instructions.md diff --git a/.github/instructions/package-versions.instructions.md b/.github/instructions/package-versions.instructions.md new file mode 100644 index 0000000000..9dba7acb8e --- /dev/null +++ b/.github/instructions/package-versions.instructions.md @@ -0,0 +1,206 @@ +--- +applyTo: "**/Directory.Packages.props,**/*.csproj,**/Directory.Build.props,**/*.nuspec" +--- +# Choosing Package Versions in Multi-Targeted Projects + +Guidance for choosing NuGet package versions in multi-targeted projects (e.g. `net462;net8.0;net9.0`). + +## Rule +For runtime-aligned packages, **the package major must match the target runtime major**: 8.x on `net8.0`, 9.x on `net9.0`, 10.x on `net10.0`, and so on. TFMs that aren't tied to a specific runtime major (`net462`, `netstandard2.0`) get the major of the floor LTS. Other categories are versioned as described below. + +Split package references into three categories: + +### 1. Runtime-aligned packages — **version per TFM, matching the runtime band** + +Packages whose major version ships with (or is tightly coupled to) a specific .NET runtime: + +- `Microsoft.Extensions.*` (Logging, DependencyInjection, Configuration, Hosting, Options, Caching, Http, Primitives, ...) +- `Microsoft.AspNetCore.*` +- `Microsoft.EntityFrameworkCore.*` +- `System.Text.Json`, `System.Memory`, `System.IO.Pipelines`, `System.Formats.Asn1`, `System.Security.Cryptography.Pkcs` +- `Microsoft.Bcl.*` + +Use the major version that matches the TFM. For TFMs without a corresponding runtime major (`net462`, `netstandard2.0`, etc.), use the major of the **lowest supported modern TFM** — typically the floor LTS (e.g. `8.x` while net8 is supported). This keeps the legacy targets on a long-lived, well-patched band and avoids dragging in transitive deps from a newer major: + +```xml + + + + + + + + + + +``` + +When the floor LTS drops out of support, bump the default block to the new floor LTS major and drop any conditional block that becomes redundant. + +### 2. Independent packages — **single version across all TFMs** + +Packages whose versioning is decoupled from the .NET runtime: + +- `Newtonsoft.Json`, `Polly.*`, `Serilog.*` +- `Azure.*`, `Microsoft.Identity.*`, `Microsoft.IdentityModel.*` +- `Microsoft.Data.SqlClient`, `Dapper`, `StackExchange.Redis` +- Most third-party packages + +Reference one (latest stable) version unconditionally: + +```xml + + +``` + +### 3. Polyfills — **conditional presence, single version** + +Packages that only exist (or are only needed) on older TFMs. The polyfill major doesn't have to match any runtime band (older TFMs have no in-box equivalent), so pick the latest stable available: + +```xml + + + +``` + +Condition the *presence* of the reference, not the version. + +## How to categorize a package + +The named lists above aren't exhaustive. To classify a package you don't recognize, work through these steps in order: + +### 1. Read the nuget.org description + +Pure polyfills almost always say so explicitly. For example, `Microsoft.Bcl.TimeProvider`'s page reads: *"For apps targeting .NET 8 and newer versions, referencing this package is unnecessary, as the types it contains are already included in the .NET 8 and higher platform versions."* + +If the description says "for apps targeting .NET X and earlier" or "unnecessary on .NET X+", treat as polyfill candidate and continue to step 2 to confirm. If it makes no such claim and the package owner is Microsoft + a major number tracks the .NET release train, treat as runtime-aligned candidate. + +### 2. Inspect the package's `lib/` layout + +Look at the "Frameworks" tab on nuget.org or open the `.nupkg`: + +| `lib/` layout | Category | +|---|---| +| Only older TFM folders (`netstandard2.0`, `net462`) — no modern TFMs | Pure polyfill | +| `lib/net8.0/_._`, `lib/net9.0/_._` placeholders + real DLL only on older TFMs | Pure polyfill (no-op on modern TFMs) | +| Real DLLs in `lib/net8.0/`, `lib/net9.0/`, `lib/net10.0/`, differing per band | Runtime-aligned | +| Real DLLs on every TFM including older ones, single major doesn't track .NET releases | Independent | + +### 3. Check the release cadence + +- Runtime-aligned: new major every November in lockstep with .NET (8.0, 9.0, 10.0, ...) plus monthly servicing patches. +- Independent: releases on its own schedule, major doesn't correlate with .NET versions. +- Pure polyfill: usually freezes at one major and rarely bumps; new majors only to ride the build train. + +### 4. Functional test — remove the reference and rebuild + +The decisive test for the polyfill-vs-runtime-aligned boundary: remove the `PackageReference` on a modern TFM (e.g. net8) and build. + +- Builds clean → package was acting as a polyfill on that TFM. Confirm category 3. +- Fails with `CS0246`/`CS1061` (missing type or method) → the package contributes API the in-box BCL doesn't have. Treat as runtime-aligned (category 1), even if the description sounds polyfill-ish. + +### Beware hybrids + +Some packages look like polyfills but add API beyond the in-box BCL even on modern TFMs. Treat these like runtime-aligned packages. + +### When in doubt + +Treat as **runtime-aligned** (category 1) and reference on every TFM with per-TFM majors. + +- Cost of mis-classifying a true polyfill this way: a redundant `_._` asset at restore. Harmless. +- Cost of mis-classifying a hybrid as a pure polyfill: a compile break on the TFMs where you dropped the reference. + +## Why + +### Why latest minor/patch always + +`PackageReference` versions are minimums (`[X, ∞)`), so writing a stale minor or patch buys nothing for downgrade-safety and only loses fixes: + +- **Security**: BCL and Extensions packages ship CVE patches in minor/patch bumps. Pinning to an older patch means a customer who doesn't transitively pull a newer version stays on the vulnerable floor. +- **Bug fixes**: Same logic for non-security fixes. We have no reason to anchor customers to an older `8.0.0` when `8.0.5` is available. +- **NU1605 risk is small and easy to fix**: NU1605 fires on *any* downgrade, including minor/patch within the same major (e.g. our `8.0.5` transitive vs. a customer's direct `8.0.0`). In practice this is rare and trivial to resolve — the customer bumps their direct reference to a current patch. The cost of *not* tracking latest (stale security/bug fixes for every consumer who doesn't override) is larger than the cost of an occasional one-line bump in a consumer project. +- **Reduces noise from automated bumps**: Dependabot/Renovate PRs disappear if we already track latest. + +This rule applies to all three categories. The category decides the major; "latest" decides the minor and patch. + +### NuGet `PackageReference` versions are minimums, not pins + +`Version="X"` means `[X, ∞)`. The resolver picks the highest version requested across the graph, with one critical exception: a **direct** reference wins over a transitive one (nearest-wins). + +### NU1605 fires when the customer's direct version is *lower* than your transitive version + +If your library transitively requires `Microsoft.Extensions.Logging 10.0.0` and the consuming app has a direct ``, NuGet detects a downgrade and emits **NU1605**. In modern SDKs this is an **error**, not a warning — the customer's build fails. + +The reverse (customer's direct version higher than your transitive) resolves cleanly with no warning. + +### The asymmetry drives the rule + +| Your transitive version | Customer's direct version | Result | +|---|---|---| +| 10.x | 8.x | **NU1605 error** | +| 10.x | 10.x or higher | Clean | +| 8.x | 8.x or higher | Clean | + +Pinning runtime-aligned packages to the **band matching each TFM** means a net8 consumer transitively gets 8.x (no friction with their own 8.x reference), and a net10 consumer transitively gets 10.x. + +Pinning everything to the latest major (e.g. `10.x` unconditionally) forces every net8 customer to roll their direct references forward or hit NU1605. + +### Independent packages don't have this problem + +`Newtonsoft.Json 13.x`, `Azure.Identity 1.17.x`, etc. aren't tied to a runtime version. Customers don't have a "matching" version in mind, and the package's own multi-targeted assets handle TFM selection internally. One version is simpler and avoids needless conditional blocks. + +### Framework-provided assemblies win at runtime anyway + +On .NET 8+, packages like `System.Text.Json` and `System.Memory` are part of the shared framework. Even if you reference `System.Text.Json 9.0.0`, a net8 app uses the in-box net8 copy at runtime. Referencing 10.x on net8 just creates restore-graph noise with no runtime benefit — another reason to match the band. + +## Tradeoffs and alternatives considered + +### Per-TFM (the rule) vs. single lowest-LTS version + +A "pin everything to the lowest supported LTS major (e.g. 8.x everywhere) and bump only when that LTS drops" policy is also downgrade-safe and simpler in `Directory.Packages.props`. We rejected it because: + +- Customers on newer runtimes lose access to perf/feature work that landed in later package majors for packages that *aren't* fully overridden by the in-box shared framework (e.g. `Microsoft.Extensions.Caching.Memory`, `System.Configuration.ConfigurationManager`). +- The simplification is small: one extra conditional `ItemGroup` per runtime-aligned package. +- A coordinated bump when the floor LTS drops is a larger, riskier change than incremental per-TFM updates. + +Per-TFM keeps each runtime on its matching band and confines change to one `Update` line at a time. + +### Why no upper bounds + +Customers can transitively pull in a *higher* major than your reference. NuGet resolves nearest-wins with no warning, so a major that broke API can produce `MissingMethodException`/`TypeLoadException` at runtime rather than a restore-time failure. + +An upper bound (e.g. `Version="[8.0.0, 10.0.0)"`) would convert that runtime failure into a restore-time `NU1107` and is the only way to guarantee compatibility. We still avoid it because: + +- It blocks customers from rolling forward to fix CVEs or take perf wins in the newer major. +- It propagates conflicts deep into customer dependency graphs that we can't see. +- Microsoft's [library guidance](https://learn.microsoft.com/dotnet/standard/library-guidance/dependencies#nuget-dependency-version-ranges) explicitly says **AVOID upper bounds**, and the foundational Microsoft libraries (EF Core, ASP.NET Core, Aspire, Orleans, the BCL itself, Azure SDK) follow it. +- Exact pins (`[X]`) in Microsoft repos are reserved for host-coupled scenarios: Roslyn analyzers (`Microsoft.CodeAnalysis.*`), MSBuild API consumers (`Microsoft.Build`), VS extensibility. None apply to runtime libraries like SqlClient. + +We accept the SemVer-break risk in exchange for not breaking customer rollforward. If a specific package is known to break compatibility at a future major, document it in code review and revisit — don't blanket-bound. + +### Downgrade direction matters + +| Your transitive | Customer's direct | Result | +|---|---|---| +| Higher | Lower | **NU1605 error** (we cause this) | +| Lower | Higher | Clean restore, customer's wins | +| Equal | Equal | Clean | + +The asymmetry is the entire reason per-TFM matching works: it makes us the *lower* or *equal* for any customer who has aligned their own references with their runtime. + +## Checklist before changing a package version + +1. Is the package in the runtime-aligned list above? → use per-TFM conditional `PackageVersion Update`, latest minor/patch in each band. +2. Is it independent? → single `PackageVersion`, latest stable. +3. Is it a polyfill? → conditional `PackageReference` only on TFMs that need it, latest stable. +4. Always pick the latest available minor/patch within the chosen major. Don't carry forward a stale minor when bumping or adding. +5. Prefer Central Package Management (`Directory.Packages.props`) over per-project versions. +6. Never use exact-version (`[X]`) or upper-bound (`[X, Y)`) ranges on `PackageReference` unless you have a documented compatibility reason. + +## Sources + +- [NU1605 — Detected package downgrade](https://learn.microsoft.com/nuget/reference/errors-and-warnings/nu1605) +- [NuGet Package versioning — version ranges](https://learn.microsoft.com/nuget/concepts/package-versioning#version-ranges) +- [NuGet dependency resolution](https://learn.microsoft.com/nuget/concepts/dependency-resolution) +- [Library guidance — Dependencies](https://learn.microsoft.com/dotnet/standard/library-guidance/dependencies) +- [Library guidance — Cross-platform targeting](https://learn.microsoft.com/dotnet/standard/library-guidance/cross-platform-targeting) From 045adc5e608f42ce98eff8c12b920f18218e6855 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 30 May 2026 04:12:08 +1000 Subject: [PATCH 124/298] avoid stack trace cost via exception when (#4099) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> --- .../Connection/SqlConnectionInternal.cs | 34 ++--- .../WaitHandleDbConnectionPool.cs | 59 ++++----- .../ManagedSni/SniTcpHandle.netcore.cs | 125 +++++++++--------- .../Data/SqlClient/Server/ValueUtilsSmi.cs | 6 +- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 12 +- .../Data/SqlClient/SqlCommand.NonQuery.cs | 11 +- .../Data/SqlClient/SqlCommand.Reader.cs | 45 +++---- .../Data/SqlClient/SqlCommand.Xml.cs | 19 +-- .../Microsoft/Data/SqlClient/SqlCommand.cs | 6 +- .../Microsoft/Data/SqlClient/SqlConnection.cs | 7 +- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 53 ++------ .../Microsoft/Data/SqlClient/SqlDependency.cs | 23 +--- .../Data/SqlClient/SqlDependencyListener.cs | 97 +++----------- .../Data/SqlClient/SqlDependencyUtils.cs | 19 +-- .../Data/SqlClient/SqlInternalTransaction.cs | 42 ++---- .../Data/SqlClient/SqlMetaDataFactory.cs | 6 +- .../Microsoft/Data/SqlClient/SqlParameter.cs | 7 +- .../Data/SqlClient/SqlQueryMetadataCache.cs | 22 +-- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 73 ++++------ .../Data/SqlClient/TdsParserStateObject.cs | 13 +- 20 files changed, 206 insertions(+), 473 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 6d067bab7f..ac9d36bdd7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -424,19 +424,14 @@ internal SqlConnectionInternal( break; } catch (SqlException sqlex) + when ( + connectionEstablishCount != i + 1 + && applyTransientFaultHandling + && !_timeout.IsExpired + && _timeout.MillisecondsRemaining >= transientRetryIntervalInMilliSeconds + && IsTransientError(sqlex)) { - if (connectionEstablishCount == i + 1 - || !applyTransientFaultHandling - || _timeout.IsExpired - || _timeout.MillisecondsRemaining < transientRetryIntervalInMilliSeconds - || !IsTransientError(sqlex)) - { - throw; - } - else - { - Thread.Sleep(transientRetryIntervalInMilliSeconds); - } + Thread.Sleep(transientRetryIntervalInMilliSeconds); } } } @@ -2073,13 +2068,8 @@ protected override void Deactivate() } } // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - // If an exception occurred, the inner connection will be marked as unusable and // destroyed upon returning to the pool DoomThisConnection(); @@ -3845,13 +3835,9 @@ private void OpenLoginEnlist( _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (ADP.IsCatchableExceptionType(e)) - { - LoginFailure(); - } - + LoginFailure(); throw; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 4243e777ba..fa704ab438 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -22,7 +22,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// /// 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. @@ -36,7 +36,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// 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 @@ -379,7 +379,7 @@ private void CleanupCallback(object state) // ONLY touch obj after lock release if shouldDestroy is false!!! Otherwise, it may be destroyed // by transaction-end thread! - // Note that there is a minor race condition between this task and the transaction end event, if the latter runs + // Note that there is a minor race condition between this task and the transaction end event, if the latter runs // between the lock above and the SetInStasis call below. The result is that the stasis counter may be // incremented without a corresponding decrement (the transaction end task is normally expected // to decrement, but will only do so if the stasis flag is set when it runs). I've minimized the size @@ -547,14 +547,8 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio // Reset the error wait: _errorWait = ERROR_WAIT_DEFAULT; } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - // UNDONE - should not be catching all exceptions!!! - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); if (!IsBlockingPeriodEnabled()) @@ -577,7 +571,7 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio Timer t = new Timer(new TimerCallback(this.ErrorCallback), null, Timeout.Infinite, Timeout.Infinite); bool timerIsNotDisposed; - + _waitHandles.ErrorEvent.Set(); _errorOccurred = true; @@ -624,16 +618,16 @@ private void DeactivateObject(DbConnectionInternal obj) // be returned to a different customer until the transaction // actually completes, so we send it into Stasis -- the SysTx // transaction object will ensure that it is owned (not lost), - // and it will be certain to put it back into the pool. + // and it will be certain to put it back into the pool. if (State is ShuttingDown) { if (obj.IsTransactionRoot) { - // SQLHotfix# 50003503 - connections that are affiliated with a - // root transaction and that also happen to be in a connection - // pool that is being shutdown need to be put in stasis so that - // the root transaction isn't effectively orphaned with no + // SQLHotfix# 50003503 - connections that are affiliated with a + // root transaction and that also happen to be in a connection + // pool that is being shutdown need to be put in stasis so that + // the root transaction isn't effectively orphaned with no // means to promote itself to a full delegated transaction or // Commit or Rollback obj.SetInStasis(); @@ -666,7 +660,7 @@ private void DeactivateObject(DbConnectionInternal obj) { // NOTE: we're not locking on _state, so it's possible that its // value could change between the conditional check and here. - // Although perhaps not ideal, this is OK because the + // Although perhaps not ideal, this is OK because the // DelegatedTransactionEnded event will clean up the // connection appropriately regardless of the pool state. Debug.Assert(_transactedConnectionPool != null, "Transacted connection pool was not expected to be null."); @@ -685,13 +679,13 @@ private void DeactivateObject(DbConnectionInternal obj) { // SQLHotfix# 50003503 - if the object cannot be pooled but is a transaction // root, then we must have hit one of two race conditions: - // 1) PruneConnectionPoolGroups shutdown the pool and marked this connection + // 1) PruneConnectionPoolGroups shutdown the pool and marked this connection // as non-poolable while we were processing within this lock // 2) The LoadBalancingTimeout expired on this connection and marked this // connection as DoNotPool. // // This connection needs to be put in stasis so that the root transaction isn't - // effectively orphaned with no means to promote itself to a full delegated + // effectively orphaned with no means to promote itself to a full delegated // transaction or Commit or Rollback obj.SetInStasis(); rootTxn = true; @@ -716,7 +710,7 @@ private void DeactivateObject(DbConnectionInternal obj) } else if (destroyObject) { - // Connections that have been marked as no longer + // Connections that have been marked as no longer // poolable (e.g. exceeded their connection lifetime) are not, in fact, // returned to the general pool DestroyObject(obj); @@ -783,7 +777,7 @@ private void ErrorCallback(object state) private Exception TryCloneCachedException() // Cached exception can be of any type, so is not always cloneable. - // This functions clones SqlException + // This functions clones SqlException // OleDb and Odbc connections are not passing throw this code => _resError is SqlException sqlEx ? sqlEx.InternalClone() : _resError; @@ -796,7 +790,7 @@ private void WaitForPendingOpen() do { bool started = false; - + try { started = Interlocked.CompareExchange(ref _pendingOpensWaiting, 1, 0) == 0; @@ -825,7 +819,7 @@ private void WaitForPendingOpen() DbConnectionInternal connection = null; bool timeout = false; Exception caughtException = null; - + try { ADP.SetCurrentTransaction(next.Completion.Task.AsyncState as Transaction); @@ -1165,8 +1159,8 @@ private DbConnectionInternal GetFromGeneralPool() Debug.Assert(obj != null, "null connection is not expected"); } - // When another thread is clearing this pool, - // it will remove all connections in this pool which causes the + // When another thread is clearing this pool, + // it will remove all connections in this pool which causes the // following assert to fire, which really mucks up stress against // checked bits. @@ -1275,7 +1269,7 @@ private void PoolCreateRequest(object state) } catch { - // Catch all the exceptions occuring during CreateObject so that they + // Catch all the exceptions occuring during CreateObject so that they // don't emerge as unhandled on the thread pool and don't crash applications // The error is handled in CreateObject and surfaced to the caller of the Connection Pool // using the ErrorEvent. Hence it is OK to swallow all exceptions here. @@ -1301,15 +1295,10 @@ private void PoolCreateRequest(object state) QueuePoolCreateRequest(); } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - // 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 + // 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}", Id, e); } @@ -1494,14 +1483,14 @@ public void Shutdown() // TransactionEnded merely provides the plumbing for DbConnectionInternal to access the transacted pool // 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 + // 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) 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. + // Note: connection may still be associated with transaction due to Explicit Unbinding requirement. 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 diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs index c7d562750a..15a856dcec 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs @@ -171,84 +171,79 @@ public SniTcpHandle( _socket = Connect(serverName, port, timeout, ipPreference, cachedFQDN, ref pendingDNSInfo); } } + // Connection failed with a retryable exception and we have cached DNS info: retry using cached IPs. catch (Exception ex) + when (!timeout.IsExpired && + hasCachedDNSInfo && + ex is (SocketException or ArgumentException or AggregateException)) { - if (timeout.IsExpired) + // Retry with cached IP address + int portRetry = string.IsNullOrEmpty(cachedDNSInfo.Port) ? port : int.Parse(cachedDNSInfo.Port); + + string firstCachedIP; + string secondCachedIP; + + if (SqlConnectionIPAddressPreference.IPv6First == ipPreference) { - throw; + firstCachedIP = cachedDNSInfo.AddrIPv6; + secondCachedIP = cachedDNSInfo.AddrIPv4; } - // Retry with cached IP address - if (ex is SocketException || ex is ArgumentException || ex is AggregateException) + else + { + firstCachedIP = cachedDNSInfo.AddrIPv4; + secondCachedIP = cachedDNSInfo.AddrIPv6; + } + + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Retrying with cached DNS IP Address {1} and port {2}", args0: _connectionId, args1: firstCachedIP, args2: portRetry); + + try { - if (hasCachedDNSInfo == false) + if (parallel) { - 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; + _socket = TryConnectParallel(firstCachedIP, portRetry, timeout, ref reportError, cachedFQDN, ref pendingDNSInfo); } 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); - - string firstCachedIP; - string secondCachedIP; - - if (SqlConnectionIPAddressPreference.IPv6First == ipPreference) - { - firstCachedIP = cachedDNSInfo.AddrIPv6; - secondCachedIP = cachedDNSInfo.AddrIPv4; - } - else - { - firstCachedIP = cachedDNSInfo.AddrIPv4; - secondCachedIP = cachedDNSInfo.AddrIPv6; - } - - try - { - if (parallel) - { - _socket = TryConnectParallel(firstCachedIP, portRetry, timeout, ref reportError, cachedFQDN, ref pendingDNSInfo); - } - else - { - _socket = Connect(firstCachedIP, portRetry, timeout, ipPreference, cachedFQDN, ref pendingDNSInfo); - } - } - catch (Exception exRetry) - { - if (timeout.IsExpired) - { - throw; - } - if (exRetry is - SocketException or - ArgumentException or - AggregateException) - { - 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); - } - else - { - _socket = Connect(secondCachedIP, portRetry, timeout, ipPreference, cachedFQDN, ref pendingDNSInfo); - } - } - else - { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Retry failed, exception occurred: {1}", args0: _connectionId, args1: exRetry?.Message); - throw; - } - } + _socket = Connect(firstCachedIP, portRetry, timeout, ipPreference, cachedFQDN, ref pendingDNSInfo); } } - else + catch (Exception exRetry) + when (!timeout.IsExpired && + exRetry is + SocketException or + ArgumentNullException or + ArgumentException or + ArgumentOutOfRangeException or + AggregateException) + { + 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); + } + else + { + _socket = Connect(secondCachedIP, portRetry, timeout, ipPreference, cachedFQDN, ref pendingDNSInfo); + } + } + catch (Exception exRetry) when (!timeout.IsExpired) { + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Retry failed, exception occurred: {1}", args0: _connectionId, args1: exRetry?.Message); throw; } } + // Connection failed with a retryable exception but no cached DNS info available: log and rethrow. + catch (Exception ex) + when (!timeout.IsExpired && + !hasCachedDNSInfo && + ex is + SocketException or + ArgumentException or + AggregateException) + { + 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; + } if (_socket == null || !_socket.Connected) { @@ -424,7 +419,7 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, List checkErrorLst; // Repeating Socket.Select several times if our timeout is greater - // than int.MaxValue microseconds because of + // than int.MaxValue microseconds because of // https://github.com/dotnet/SqlClient/pull/1029#issuecomment-875364044 // which states that Socket.Select can't handle timeouts greater than int.MaxValue microseconds do @@ -583,7 +578,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim try { // Repeating Socket.Select several times if our timeout is greater - // than int.MaxValue microseconds because of + // than int.MaxValue microseconds because of // https://github.com/dotnet/SqlClient/pull/1029#issuecomment-875364044 // which states that Socket.Select can't handle timeouts greater than int.MaxValue microseconds do 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 0b75a6178d..94338c8dfb 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 @@ -2384,12 +2384,8 @@ private static void SetCharsOrString_FromReader(ITypedSettersV3 setters, int ord SetChars_FromReader(setters, ordinal, metaData, reader, offset); success = true; } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } } if (!success) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 414be57eb0..af2091d7f6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1092,12 +1092,8 @@ private void Dispose(bool disposing) _internalTransaction = null; } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); } } @@ -1861,12 +1857,8 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re return value; } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs index 36d727575c..141416537f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs @@ -236,21 +236,14 @@ private IAsyncResult BeginExecuteNonQueryInternal( BeginExecuteNonQueryInternalReadStage(localCompletion); } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableOrSecurityExceptionType(e)) { - // @TODO: Invert. - if (!ADP.IsCatchableOrSecurityExceptionType(e)) - { - // Exception is not catchable, the connection has already been caught and - // doomed in a lower level. - throw; - } - // For async, RunExecuteReader will never put the stateObj back into the pool, // so, do so now. ReliablePutStateObject(); throw; } + // Allow other exceptions to bubble up as-is. // When we use query caching for parameter encryption we need to retry on specific errors. // In these cases finalize the call internally and trigger a retry when needed. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs index d9f6c33233..a98d82118b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs @@ -290,15 +290,8 @@ private IAsyncResult BeginExecuteReaderInternal( isRetry, nameof(BeginExecuteReader)); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableOrSecurityExceptionType(e)) { - if (!ADP.IsCatchableOrSecurityExceptionType(e)) - { - // If not catchable - the connection has already been caught and doomed in - // RunExecuteReader. - throw; - } - // For async, RunExecuteReader will never put the stateObj back into the pool, // so, do so now. ReliablePutStateObject(); @@ -307,6 +300,7 @@ private IAsyncResult BeginExecuteReaderInternal( throw; } } + // Allow other exceptions to bubble up as-is. if (writeTask is not null) { @@ -848,28 +842,25 @@ private void FinishExecuteReader( // @TODO: Why does the command set whether the reader is initialized?? ds.IsInitialized = true; } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (ADP.IsCatchableExceptionType(e)) + if (_inPrepare) { - if (_inPrepare) - { - // The flag is expected to be reset by OnReturnValue. We should receive - // the handle unless command execution failed. If it fails, move back - // to pending state. - _inPrepare = false; // reset the flag - IsDirty = true; // mark command as dirty so it will be prepared next time we're coming through - _execType = EXECTYPE.PREPAREPENDING; // reset execution type to pending - } + // The flag is expected to be reset by OnReturnValue. We should receive + // the handle unless command execution failed. If it fails, move back + // to pending state. + _inPrepare = false; // reset the flag + IsDirty = true; // mark command as dirty so it will be prepared next time we're coming through + _execType = EXECTYPE.PREPAREPENDING; // reset execution type to pending + } - try - { - ds.Close(); - } - catch (Exception eClose) - { - Debug.WriteLine($"Received this exception from SqlDataReader.Close() while in another catch block: {eClose}"); - } + try + { + ds.Close(); + } + catch (Exception eClose) + { + Debug.WriteLine($"Received this exception from SqlDataReader.Close() while in another catch block: {eClose}"); } throw; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs index c025e1d3b7..2be6166e8b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs @@ -165,13 +165,9 @@ private static XmlReader CompleteXmlReader(SqlDataReader dataReader, bool isAsyn processAllRows: metaData[0].SqlDbType is not SqlDbType.Xml); xmlReader = sqlStream.ToXmlReader(isAsync); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (ADP.IsCatchableExceptionType(e)) - { - dataReader.Close(); - } - + dataReader.Close(); throw; } } @@ -253,21 +249,14 @@ private IAsyncResult BeginExecuteXmlReaderInternal( // @TODO: NonQuery pathway has the continueTaskWithState block inside this try. One or the other seems wrong } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableOrSecurityExceptionType(e)) { - // @TODO: Invert - if (!ADP.IsCatchableOrSecurityExceptionType(e)) - { - // If not catchable - the connection has already been caught and doomed in - // RunExecuteReader. - throw; - } - // For async, RunExecuteReader will never put the stateObj back into the pool, // so, do so now. ReliablePutStateObject(); throw; } + // Allow other exceptions to bubble up as-is. if (writeTask is not null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index 1073bb137c..ec26b955e1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -1829,12 +1829,8 @@ internal void OnStatementCompleted(int recordCount) handler(this, new StatementCompletedEventArgs(recordCount)); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableOrSecurityExceptionType(e)) { - if (!ADP.IsCatchableOrSecurityExceptionType(e)) - { - throw; - } } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 9021d3e259..47a1be163a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -2610,13 +2610,8 @@ internal void OnInfoMessage(SqlInfoMessageEventArgs imevent, out bool notified) { handler(this, imevent); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableOrSecurityExceptionType(e)) { - if (!ADP.IsCatchableOrSecurityExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index e316bb3c08..ca44267eb6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -1875,12 +1875,8 @@ private TdsOperationStatus TryGetBytesInternal(int i, long dataIndex, byte[] buf Buffer.BlockCopy(data, ndataIndex, buffer, bufferIndex, cbytes); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } cbytes = data.Length; if (length < 0) @@ -2152,17 +2148,10 @@ override public long GetChars(int i, long dataIndex, char[] buffer, int bufferIn { CheckDataIsReady(columnIndex: i, allowPartiallyReadColumn: true); } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(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; - } + throw new TargetInvocationException(ex); } charsRead = GetStreamingXmlChars(i, dataIndex, buffer, bufferIndex, length); } @@ -2232,12 +2221,8 @@ override public long GetChars(int i, long dataIndex, char[] buffer, int bufferIn Array.Copy(_columnDataChars, ndataIndex, buffer, bufferIndex, cchars); _columnDataCharsRead += cchars; } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } cchars = _columnDataChars.Length; if (length < 0) @@ -5044,12 +5029,8 @@ public override Task ReadAsync(CancellationToken cancellationToken) #endif } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } return Task.FromException(ex); } @@ -5146,12 +5127,8 @@ override public Task IsDBNullAsync(int i, CancellationToken cancellationTo { CheckHeaderIsReady(columnIndex: i); } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } return Task.FromException(ex); } @@ -5204,12 +5181,8 @@ override public Task IsDBNullAsync(int i, CancellationToken cancellationTo #endif } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } return Task.FromException(ex); } @@ -5305,12 +5278,8 @@ override public Task GetFieldValueAsync(int i, CancellationToken cancellat } } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } return Task.FromException(ex); } @@ -5346,12 +5315,8 @@ override public Task GetFieldValueAsync(int i, CancellationToken cancellat #endif } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } return Task.FromException(ex); } 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 836fbb7dd4..a101099dd8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -638,13 +638,8 @@ internal static bool Start(string connectionString, string queue, bool useDefaul { Stop(connectionString, queue, useDefaults, true); } - catch (Exception e) - { // Discard stop failure! - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) + { ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now. SqlClientEventSource.Log.TryNotificationTraceEvent(" Exception occurred from Stop() after duplicate was found on Start()."); } @@ -666,13 +661,8 @@ internal static bool Start(string connectionString, string queue, bool useDefaul // to provide options themselves. } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now. SqlClientEventSource.Log.TryNotificationTraceEvent(" Exception occurred from _processDispatcher.Start(...), calling Invalidate(...)."); throw; @@ -785,13 +775,8 @@ internal static bool Stop(string connectionString, string queue, bool useDefault // to provide options themselves. } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now. } } 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 004ae0ed08..faf088b9b5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs @@ -177,13 +177,8 @@ internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, str _timeoutParam.Value = _defaultWaitforTimeout; // Sync successful, extend timeout to 60 seconds. AsynchronouslyQueryServiceBrokerQueue(); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now. if (setupCompleted) { @@ -400,12 +395,8 @@ private void CreateQueueAndService(bool restart) { com.ExecuteNonQuery(); // Cannot add 'IF OBJECT_ID' to create procedure query - wrap and discard failure. } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); try @@ -416,12 +407,8 @@ private void CreateQueueAndService(bool restart) trans = null; } } - catch (Exception f) + catch (Exception f) when (ADP.IsCatchableExceptionType(f)) { - if (!ADP.IsCatchableExceptionType(f)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(f); // Discard failure, but trace for now. } } @@ -479,12 +466,8 @@ private void CreateQueueAndService(bool restart) trans.Rollback(); trans = null; } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now. } } @@ -580,12 +563,8 @@ private void ProcessNotificationResults(SqlDataReader reader) { dispatcher.InvalidateCommandID(notification); // CROSS APP-DOMAIN CALL! } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard failure. User event could throw exception. } } @@ -664,12 +643,8 @@ private void Restart(object unused) { _con.Close(); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard close failure, if it occurs. Only trace it. } } @@ -717,12 +692,8 @@ private void Restart(object unused) { CreateQueueAndService(true); // Ensure service, queue, etc is present, if we created it. } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard failure, but trace for now. failure = true; } @@ -765,12 +736,8 @@ private void Restart(object unused) TearDownAndDispose(); // Function will lock(this). } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); try @@ -786,12 +753,8 @@ private void Restart(object unused) SqlNotificationType.Change, null)); } - catch (Exception f) + catch (Exception f) when (ADP.IsCatchableExceptionType(f)) { - if (!ADP.IsCatchableExceptionType(f)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(f); // Discard exception from Invalidate. User events can throw. } @@ -799,12 +762,8 @@ private void Restart(object unused) { _con.Close(); } - catch (Exception f) + catch (Exception f) when (ADP.IsCatchableExceptionType(f)) { - if (!ADP.IsCatchableExceptionType(f)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(f); // Discard close failure, if it occurs. Only trace it. } @@ -884,12 +843,8 @@ internal bool Stop(string appDomainKey, out bool appDomainStop) // Rather than fighting the race condition, just call it and discard any potential failure. _com.Cancel(); // Cancel the pending command. No-op if connection closed. } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard failure, if it should occur. } _stop = true; @@ -992,12 +947,8 @@ private void TearDownAndDispose() _com.Parameters.Remove(_timeoutParam); _com.ExecuteNonQuery(); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard failure. } } @@ -1016,12 +967,8 @@ private void TearDownAndDispose() { _com.ExecuteNonQuery(); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard failure. } } @@ -1104,12 +1051,8 @@ internal static SqlNotification ProcessMessage(SqlXml xmlMessage) type = temp; } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard failure, if it should occur. } messageAttributes |= MessageAttributes.Type; @@ -1123,12 +1066,8 @@ internal static SqlNotification ProcessMessage(SqlXml xmlMessage) source = temp; } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); // Discard failure, if it should occur. } messageAttributes |= MessageAttributes.Source; @@ -1468,17 +1407,13 @@ private void Invalidate(string server, SqlNotification sqlNotification) { perAppDomainDispatcher.InvalidateServer(server, sqlNotification); } - catch (Exception f) + catch (Exception f) when (ADP.IsCatchableExceptionType(f)) { // Since we are looping over dependency dispatchers, do not allow one Invalidate // that results in a throw prevent us from invalidating all dependencies // related to this server. // NOTE - SqlDependencyPerAppDomainDispatcher already wraps individual dependency invalidates // with try/catch, but we should be careful and do the same here. - if (!ADP.IsCatchableExceptionType(f)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(f); // Discard failure, but trace. } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs index 23fa1c9359..b850376c51 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs @@ -272,15 +272,11 @@ internal void InvalidateCommandID(SqlNotification sqlNotification) { dependency.Invalidate(sqlNotification.Type, sqlNotification.Info, sqlNotification.Source); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { // Since we are looping over dependencies, do not allow one Invalidate // that results in a throw prevent us from invalidating all dependencies // related to this server. - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); } } @@ -327,15 +323,11 @@ internal void InvalidateServer(string server, SqlNotification sqlNotification) { dependency.Invalidate(sqlNotification.Type, sqlNotification.Info, sqlNotification.Source); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { // Since we are looping over dependencies, do not allow one Invalidate // that results in a throw prevent us from invalidating all dependencies // related to this server. - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } ADP.TraceExceptionWithoutRethrow(e); } } @@ -581,13 +573,8 @@ private static void TimeoutTimerCallback(object state) // to invoke user-code while holding an internal lock. dependencies[i].Invalidate(SqlNotificationType.Change, SqlNotificationInfo.Error, SqlNotificationSource.Timeout); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - // This is an exception in user code, and we're in a thread-pool thread // without user's code up in the stack, no much we can do other than // eating the exception. 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 bc5f5cbccb..ee6557e55b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs @@ -181,12 +181,8 @@ private void CheckTransactionLevelAndZombie() Zombie(); } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } #if NETFRAMEWORK ADP.TraceExceptionWithoutRethrow(e); #endif @@ -243,13 +239,9 @@ internal void Commit() _innerConnection.ExecuteTransaction(TransactionRequest.Commit, null, IsolationLevel.Unspecified, null, false); ZombieParent(); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (ADP.IsCatchableExceptionType(e)) - { - CheckTransactionLevelAndZombie(); - } - + CheckTransactionLevelAndZombie(); throw; } } @@ -350,18 +342,11 @@ internal void Rollback() // server transaction level. This transaction has been completed. Zombie(); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (ADP.IsCatchableExceptionType(e)) - { - CheckTransactionLevelAndZombie(); + CheckTransactionLevelAndZombie(); - if (!_disposing) - { - throw; - } - } - else + if (!_disposing) { throw; } @@ -394,12 +379,9 @@ internal void Rollback(string transactionName) { _innerConnection.ExecuteTransaction(TransactionRequest.Rollback, transactionName, IsolationLevel.Unspecified, null, false); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (ADP.IsCatchableExceptionType(e)) - { - CheckTransactionLevelAndZombie(); - } + CheckTransactionLevelAndZombie(); throw; } } @@ -426,13 +408,9 @@ internal void Save(string savePointName) { _innerConnection.ExecuteTransaction(TransactionRequest.Save, savePointName, IsolationLevel.Unspecified, null, false); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (ADP.IsCatchableExceptionType(e)) - { - CheckTransactionLevelAndZombie(); - } - + CheckTransactionLevelAndZombie(); throw; } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs index 1d9ceebafd..841c71d410 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -422,12 +422,8 @@ private async ValueTask ExecuteCommandAsync(DataRow requestedCollecti { reader = isAsync ? await command.ExecuteReaderAsync(cancellationToken) : command.ExecuteReader(); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } throw ADP.QueryFailed(collectionName, e); } 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 621413f9c1..e030fb11f5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -2419,13 +2419,8 @@ value is IEnumerable value = Convert.ChangeType(value, destinationType.ClassType, null); } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - throw ADP.ParameterConversionFailed(value, destinationType.ClassType, e); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs index 51f0abc7cf..3fb1ec1db6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs @@ -123,29 +123,29 @@ internal bool GetQueryMetadataIfExists(SqlCommand sqlCommand) { SqlSecurityUtility.DecryptSymmetricKey(cipherMdCopy, sqlCommand.Connection, sqlCommand); } - catch (Exception ex) + catch (Exception ex) when (ex is SqlException or ArgumentException) { // Invalidate the cache entry. InvalidateCacheEntry(sqlCommand); - // If we get one of the expected exceptions, just fail the cache lookup, otherwise throw. - if (ex is SqlException or ArgumentException) + foreach (SqlParameter paramToCleanup in sqlCommand.Parameters) { - foreach (SqlParameter paramToCleanup in sqlCommand.Parameters) - { - paramToCleanup.CipherMetadata = null; - } - - IncrementCacheMisses(); - return false; + paramToCleanup.CipherMetadata = null; } + IncrementCacheMisses(); + return false; + } + catch (Exception) + { + // Invalidate the cache entry. + InvalidateCacheEntry(sqlCommand); throw; } } } - ConcurrentDictionary enclaveKeys = + ConcurrentDictionary enclaveKeys = _cache.Get>(enclaveLookupKey); if (enclaveKeys is not null) { 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 66e9c3e52e..0e8548c2b1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -208,7 +208,7 @@ static TdsParser() { // For CoreCLR, we need to register the ANSI Code Page encoding provider before attempting to get an Encoding from a CodePage // For a default installation of SqlServer the encoding exchanged during Login is 1252. This encoding is not loaded by default - // See Remarks at https://msdn.microsoft.com/en-us/library/system.text.encodingprovider(v=vs.110).aspx + // See Remarks at https://msdn.microsoft.com/en-us/library/system.text.encodingprovider(v=vs.110).aspx // SqlClient needs to register the encoding providers to make sure that even basic scenarios work with Sql Server. Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); } @@ -683,7 +683,7 @@ internal void RemoveEncryption() // create a new packet encryption changes the internal packet size Bug# 228403 _physicalStateObj.ClearAllWritePackets(); - } + } internal void EnableMars() { @@ -1376,11 +1376,11 @@ internal void TdsLogin( int feOffset = length; // calculate and reserve the required bytes for the featureEx length = ApplyFeatureExData( - requestedFeatures, - recoverySessionData, + requestedFeatures, + recoverySessionData, fedAuthFeatureExtensionData, UserAgent.Ucs2Bytes, - useFeatureExt, + useFeatureExt, length ); @@ -2792,7 +2792,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle { _connHandler._federatedAuthenticationInfoReceived = true; SqlFedAuthInfo info; - + result = TryProcessFedAuthInfo(stateObj, tokenLength, out info); if (result != TdsOperationStatus.Done) { @@ -5713,7 +5713,7 @@ private TdsOperationStatus TryCommonProcessMetaData(TdsParserStateObject stateOb { return result; } - + // read flags and set appropriate flags in structure byte flags; result = stateObj.TryReadByte(out flags); @@ -7042,7 +7042,7 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, return result; } - // Internally, we use Sqlbinary to deal with varbinary data and store it in + // Internally, we use Sqlbinary to deal with varbinary data and store it in // SqlBuffer as SqlBinary value. #if NET value.SqlBinary = SqlBinary.WrapBytes(b); @@ -8796,14 +8796,8 @@ private void ProcessAttention(TdsParserStateObject stateObj) // Call run loop to process looking for attention ack. Run(RunBehavior.Attention, null, null, null, stateObj); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - // UNDONE - should not be catching all exceptions!!! - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - // If an exception occurs - break the connection. // Attention error will not be thrown in this case by Run(), but other failures may. ADP.TraceExceptionWithoutRethrow(e); @@ -9171,7 +9165,7 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD /// internal int WriteVectorSupportFeatureRequest(bool write) { - const int len = 6; + const int len = 6; if (write) { @@ -9529,15 +9523,11 @@ private void WriteLoginData(SqlLogin rec, true ); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - // UNDONE - should not be catching all exceptions!!! - if (ADP.IsCatchableExceptionType(e)) - { - // be sure to wipe out our buffer if we started sending stuff - _physicalStateObj.ResetPacketCounters(); - _physicalStateObj.ResetBuffer(); - } + // be sure to wipe out our buffer if we started sending stuff + _physicalStateObj.ResetPacketCounters(); + _physicalStateObj.ResetBuffer(); throw; } @@ -9892,13 +9882,8 @@ internal SqlDataReader TdsExecuteTransactionManagerRequest( return dtcReader; } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - FailureCleanup(stateObj, e); throw; @@ -10069,14 +10054,8 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques // Finished sync return null; } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - // UNDONE - should not be catching all exceptions!!! - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - FailureCleanup(stateObj, e); throw; @@ -10369,7 +10348,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet { 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. - // Output parameter of SqlDbType vector are defined through SqlParameter.Value. + // 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) { @@ -10654,7 +10633,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet Debug.Assert(udtVal != null, "GetBytes returned null instance. Make sure that it always returns non-null value"); size = udtVal.Length; - + if (size >= maxSupportedSize && maxsize != -1) { throw SQL.UDTInvalidSize(maxsize, maxSupportedSize); @@ -13155,7 +13134,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { if (type.NullableType == TdsEnums.SQLJSON) { - // TODO : Performance and BOM check. Saurabh + // TODO : Performance and BOM check. Saurabh byte[] jsonAsBytes = Encoding.UTF8.GetBytes((string)value); WriteInt(jsonAsBytes.Length, stateObj); return stateObj.WriteByteArray(jsonAsBytes, jsonAsBytes.Length, 0, canAccumulate: false); @@ -13813,13 +13792,13 @@ internal TdsOperationStatus TryReadPlpUnicodeCharsWithContinue(TdsParserStateObj } TdsOperationStatus result = TryReadPlpUnicodeChars( - ref temp, - 0, - length >> 1, - stateObj, - out length, + ref temp, + 0, + length >> 1, + stateObj, + out length, supportRentedBuff: !canContinue, // do not use the arraypool if we are going to keep the buffer in the snapshot - rentedBuff: ref buffIsRented, + rentedBuff: ref buffIsRented, startOffset, canContinue ); @@ -14029,7 +14008,7 @@ bool writeDataSizeToSnapshot stateObj._longlenleft--; if (writeDataSizeToSnapshot) { - // we need to write the single b1 byte to the array because we may run out of data + // we need to write the single b1 byte to the array because we may run out of data // and need to wait for another packet buff[offst] = (char)((b1 & 0xff)); currentPacketId = IncrementSnapshotDataSize(stateObj, restartingDataSizeCount, currentPacketId, 1); 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 ad8651cebd..45ed29c2ce 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -1083,13 +1083,8 @@ internal bool Deactivate() goodForReuse = true; } } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); } return goodForReuse; @@ -3835,12 +3830,8 @@ private bool OnTimeoutCore(int expectedState, int targetState, bool asyncClose = { SendAttention(mustTakeWriteLock: true, asyncClose); } - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } // if unable to send attention, cancel the _networkPacketTaskSource to // request the parser be broken. SNIWritePacket errors will already // be in the _errors collection. From bfbdd30d89d7381073ee350e04ef4f0b5743cee5 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 29 May 2026 13:17:44 -0700 Subject: [PATCH 125/298] Connect timeout propagated through pool (#4270) --- Directory.Packages.props | 2 + .../src/Microsoft.Data.SqlClient.csproj | 1 + .../src/Microsoft.Data.SqlClient.nuspec | 2 + .../Data/ProviderBase/DbConnectionClosed.cs | 21 +- .../Data/ProviderBase/DbConnectionInternal.cs | 19 +- .../Data/ProviderBase/TimeoutTimer.cs | 467 +++++++++++----- .../Connection/SqlConnectionInternal.cs | 53 +- .../ConnectionPool/ChannelDbConnectionPool.cs | 28 +- .../ConnectionPool/IDbConnectionPool.cs | 7 +- .../WaitHandleDbConnectionPool.cs | 95 +++- .../Data/SqlClient/LocalAppContextSwitches.cs | 26 + .../ManagedSni/SniTcpHandle.netcore.cs | 3 + .../Microsoft/Data/SqlClient/SqlConnection.cs | 15 +- .../Data/SqlClient/SqlConnectionFactory.cs | 28 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- .../Common/LocalAppContextSwitchesHelper.cs | 15 + .../tests/Directory.Packages.props | 1 + .../AsyncCancelledConnectionsTest.cs | 136 ++++- .../SQL/InstanceNameTest/InstanceNameTest.cs | 12 +- .../DistributedTransactionTest.cs | 5 +- .../ChannelDbConnectionPoolTest.cs | 248 +++++++-- .../TransactedConnectionPoolTest.cs | 4 +- .../WaitHandleDbConnectionPoolBudgetTest.cs | 232 ++++++++ ...itHandleDbConnectionPoolTransactionTest.cs | 5 +- .../Microsoft.Data.SqlClient.UnitTests.csproj | 9 + .../SqlClient/LocalAppContextSwitchesTest.cs | 1 + .../SqlConnectionInternalTimeoutTests.cs | 74 +++ .../ProviderBase/TimeoutTimerTest.cs | 513 ++++++++++++++++++ .../ConnectionFailoverTests.cs | 1 + 29 files changed, 1757 insertions(+), 268 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBudgetTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionInternalTimeoutTests.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ProviderBase/TimeoutTimerTest.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 37b72ca205..a74caf225a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -124,12 +124,14 @@ + + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 24c6a548f9..e9b3ad2741 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -289,6 +289,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.nuspec b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.nuspec index da3a17bb9c..a302359ea0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.nuspec +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.nuspec @@ -37,6 +37,7 @@ + @@ -79,6 +80,7 @@ + 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 96704bacce..f5ae12f5a1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -62,8 +62,9 @@ protected internal override Task GetSchemaAsync( internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) => - TryOpenConnectionInternal(outerConnection, connectionFactory, retry); + TaskCompletionSource retry, + TimeoutTimer timeout) => + TryOpenConnectionInternal(outerConnection, connectionFactory, retry, timeout); /// internal override void ResetConnection() => throw ADP.ClosedConnectionError(); @@ -78,7 +79,8 @@ protected DbConnectionBusy(ConnectionState state) : base(state, true, false) internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) + TaskCompletionSource retry, + TimeoutTimer timeout) => throw ADP.ConnectionAlreadyOpen(State); } @@ -119,13 +121,15 @@ internal override void CloseConnection(DbConnection owningObject, SqlConnectionF internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) => - TryOpenConnection(outerConnection, connectionFactory, retry); + TaskCompletionSource retry, + TimeoutTimer timeout) => + TryOpenConnection(outerConnection, connectionFactory, retry, timeout); internal override bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) + TaskCompletionSource retry, + TimeoutTimer timeout) { if (retry == null || !retry.Task.IsCompleted) { @@ -173,7 +177,8 @@ private DbConnectionClosedPreviouslyOpened() : base(ConnectionState.Closed, true internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) => - TryOpenConnection(outerConnection, connectionFactory, retry); + TaskCompletionSource retry, + TimeoutTimer timeout) => + TryOpenConnection(outerConnection, connectionFactory, retry, timeout); } } 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 ac3949c726..b295f84b3d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -686,14 +686,6 @@ internal void MakePooledConnection(IDbConnectionPool connectionPool) Pool = connectionPool; } - internal virtual void OpenConnection(DbConnection outerConnection, SqlConnectionFactory connectionFactory) - { - if (!TryOpenConnection(outerConnection, connectionFactory, null)) - { - throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending); - } - } - internal void PostPop(DbConnection newOwner) { // Called by IDbConnectionPool right after it pulls this from its pool, we take this @@ -800,7 +792,8 @@ internal void SetInStasis() internal virtual bool TryOpenConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) + TaskCompletionSource retry, + TimeoutTimer timeout) { throw ADP.ConnectionAlreadyOpen(State); } @@ -808,7 +801,8 @@ internal virtual bool TryOpenConnection( internal virtual bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) + TaskCompletionSource retry, + TimeoutTimer timeout) { throw ADP.MethodNotImplemented(); } @@ -910,7 +904,8 @@ protected virtual void ReleaseAdditionalLocksForClose(bool lockToken) protected bool TryOpenConnectionInternal( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) + TaskCompletionSource retry, + TimeoutTimer timeout) { // ?->Connecting: prevent set_ConnectionString during Open if (connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this)) @@ -919,7 +914,7 @@ protected bool TryOpenConnectionInternal( try { connectionFactory.PermissionDemand(outerConnection); - if (!connectionFactory.TryGetConnection(outerConnection, retry, this, out openConnection)) + if (!connectionFactory.TryGetConnection(outerConnection, retry, this, timeout, out openConnection)) { return false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/TimeoutTimer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/TimeoutTimer.cs index 37c94fe355..fdb6a52705 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/TimeoutTimer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/TimeoutTimer.cs @@ -2,200 +2,185 @@ // 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; +#nullable enable + using System; using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.Data.ProviderBase { - // Purpose: - // Manages determining and tracking timeouts - // - // Intended use: - // Call StartXXXXTimeout() to get a timer with the given expiration point - // Get remaining time in appropriate format to pass to subsystem timeouts - // Check for timeout via IsExpired for checks in managed code. - // Simply abandon to GC when done. + /// + /// Manages determining and tracking timeouts for use by subsystems that perform + /// time-bounded operations. + /// + /// + /// + /// Intended use: + /// + /// + /// Call (or the overload that accepts a + /// ) to get a timer with the given expiration point. + /// Read the remaining time in the appropriate format to pass to subsystem timeouts. + /// Check for timeout via for checks in managed code. + /// Simply abandon the instance to the GC when done. + /// + /// + /// All time reads (current time and remaining time calculations) and any + /// instances created by this timer flow + /// through the supplied . This allows tests to inject + /// a fake time provider (for example + /// Microsoft.Extensions.Time.Testing.FakeTimeProvider) and deterministically + /// trigger expiration without relying on wall-clock delays. + /// + /// internal class TimeoutTimer { - //------------------- - // Fields - //------------------- - private long _timerExpire; - private bool _isInfiniteTimeout; - private long _originalTimerTicks; - - //------------------- - // Timeout-setting methods - //------------------- - - // Get a new timer that will expire in the given number of seconds - // For input, a value of zero seconds indicates infinite timeout - internal static TimeoutTimer StartSecondsTimeout(int seconds) - { - //-------------------- - // Preconditions: None (seconds must conform to SetTimeoutSeconds requirements) + #region Fields - //-------------------- - // Method body - var timeout = new TimeoutTimer(); - timeout.SetTimeoutSeconds(seconds); + /// + /// The sentinel value (0) used to indicate an infinite timeout when starting a timer. + /// + internal const long InfiniteTimeout = 0; - //--------------------- - // Postconditions - Debug.Assert(timeout != null); // Need a valid timeouttimer if no error + #endregion - return timeout; - } + #region Constructors - // Get a new timer that will expire in the given number of milliseconds - // No current need to support infinite milliseconds timeout - internal static TimeoutTimer StartMillisecondsTimeout(long milliseconds) + /// + /// Initializes a new instance of the class with the + /// specified expiration duration and time source. + /// + /// + /// The duration before the timer expires. A value whose ticks equal + /// indicates an infinite timeout. + /// + /// + /// The used to read the current time and schedule + /// cancellation. + /// + /// + /// Thrown when computing the absolute expiration point in checked arithmetic, + /// if the sum of the current file-time ticks and + /// ticks falls outside the range. + /// + private TimeoutTimer(TimeSpan expiration, TimeProvider timeProvider) { - //-------------------- - // Preconditions - Debug.Assert(0 <= milliseconds); - - //-------------------- - // Method body - var timeout = new TimeoutTimer(); - timeout._originalTimerTicks = milliseconds * TimeSpan.TicksPerMillisecond; - timeout._timerExpire = checked(ADP.TimerCurrent() + timeout._originalTimerTicks); - timeout._isInfiniteTimeout = false; - - //--------------------- - // Postconditions - Debug.Assert(timeout != null); // Need a valid timeouttimer if no error - - return timeout; + TimeProvider = timeProvider; + OriginalTicks = expiration.Ticks; + IsInfinite = OriginalTicks == InfiniteTimeout; + ExpirationTicks = checked(NowTicks() + OriginalTicks); } - //------------------- - // Methods for changing timeout - //------------------- - - internal void SetTimeoutSeconds(int seconds) - { - //-------------------- - // Preconditions - Debug.Assert(0 <= seconds || InfiniteTimeout == seconds); // no need to support negative seconds at present + #endregion - //-------------------- - // Method body - if (InfiniteTimeout == seconds) - { - _isInfiniteTimeout = true; - } - else - { - // Stash current time + timeout - _originalTimerTicks = ADP.TimerFromSeconds(seconds); - _timerExpire = checked(ADP.TimerCurrent() + _originalTimerTicks); - _isInfiniteTimeout = false; - } + #region Properties - //--------------------- - // Postconditions:None - } - - // Reset timer to original duration. - internal void Reset() + /// + /// Gets the tick value at which this timer is considered expired. + /// Do not use this value directly; instead, use to check if the timer has expired. + /// Does not return a meaningful value when the timer is infinite. + /// + /// + /// The tick count, in file-time units (100-nanosecond intervals since + /// 1601-01-01 UTC), at which the timer expires. + /// + /// + /// The tick scale is intentionally compatible with + /// + /// + internal long ExpirationTicks { - if (InfiniteTimeout == _originalTimerTicks) - { - _isInfiniteTimeout = true; - } - else - { - _timerExpire = checked(ADP.TimerCurrent() + _originalTimerTicks); - _isInfiniteTimeout = false; - } + get; + //TODO: Remove this when we disable Reset() + private set; } - //------------------- - // Timeout info properties - //------------------- - - // Indicator for infinite timeout when starting a timer - internal static readonly long InfiniteTimeout = 0; - - // Is this timer in an expired state? + /// + /// Gets a value indicating whether this timer has expired. + /// + /// + /// if the timer is not infinite and the current time + /// (as read from the configured ) has passed + /// ; otherwise, . + /// internal bool IsExpired { get { - return !IsInfinite && ADP.TimerHasExpired(_timerExpire); - } - } - - // is this an infinite-timeout timer? - internal bool IsInfinite - { - get - { - return _isInfiniteTimeout; + return !IsInfinite && NowTicks() > ExpirationTicks; } } - // Special accessor for TimerExpire for use when thunking to legacy timeout methods. - public long LegacyTimerExpire - { - get - { - return (_isInfiniteTimeout) ? long.MaxValue : _timerExpire; - } - } + /// + /// Gets a value indicating whether this timer represents an infinite timeout. + /// + /// + /// if the timer was created with an expiration whose + /// ticks equal ; otherwise, . + /// + internal bool IsInfinite { get; } - // Returns milliseconds remaining trimmed to zero for none remaining - // and long.MaxValue for infinite - // This method should be preferred for internal calculations that are not - // yet common enough to code into the TimeoutTimer class itself. + /// + /// Gets the number of milliseconds remaining before this timer expires, + /// truncated to 0 when none remain, and approximated to + /// when the timer is infinite. + /// + /// + /// A non-negative count of milliseconds remaining; + /// when is . + /// + /// + /// This property should be preferred for internal calculations that are not + /// yet common enough to code into the class itself. + /// internal long MillisecondsRemaining { get { - //------------------- - // Preconditions: None - - //------------------- - // Method Body long milliseconds; - if (_isInfiniteTimeout) + if (IsInfinite) { milliseconds = long.MaxValue; } else { - milliseconds = ADP.TimerRemainingMilliseconds(_timerExpire); + milliseconds = TicksToMilliseconds(ExpirationTicks - NowTicks()); if (0 > milliseconds) { milliseconds = 0; } } - //-------------------- - // Postconditions Debug.Assert(0 <= milliseconds); // This property guarantees no negative return values return milliseconds; } } - // Returns milliseconds remaining trimmed to zero for none remaining + /// + /// Gets the number of milliseconds remaining before this timer expires as + /// a 32-bit integer, trimmed to 0 when none remain and approximated to + /// when the remaining time exceeds that value or + /// when the timer is infinite. + /// + /// + /// A non-negative count of milliseconds remaining, never exceeding + /// . + /// internal int MillisecondsRemainingInt { get { - //------------------- - // Method Body int milliseconds; - if (_isInfiniteTimeout) + if (IsInfinite) { milliseconds = int.MaxValue; } else { - long longMilliseconds = ADP.TimerRemainingMilliseconds(_timerExpire); + long longMilliseconds = TicksToMilliseconds(ExpirationTicks - NowTicks()); if (0 > longMilliseconds) { milliseconds = 0; @@ -210,12 +195,220 @@ internal int MillisecondsRemainingInt } } - //-------------------- - // Postconditions Debug.Assert(0 <= milliseconds); return milliseconds; } } + + /// + /// Gets the original timeout duration, in ticks, that was supplied when the + /// timer was created. Used by to restore the original + /// expiration window. + /// + internal long OriginalTicks { get; } + + /// + /// Gets the used by this timer. Exposed for + /// callers that need to construct related timers or schedule cancellation + /// against the same time source. + /// + internal TimeProvider TimeProvider { get; } + + #endregion + + #region Methods + + /// + /// Creates and starts a new with the specified + /// expiration duration, using as the time + /// source. + /// + /// + /// The duration before the returned timer expires. A value whose ticks equal + /// produces an infinite timer. + /// + /// A new instance that has already started. + internal static TimeoutTimer StartNew(TimeSpan expiration) + => new TimeoutTimer(expiration, TimeProvider.System); + + /// + /// Creates and starts a new with the specified + /// expiration duration and time source. + /// + /// + /// The duration before the returned timer expires. A value whose ticks equal + /// produces an infinite timer. + /// + /// + /// The used to read the current time and schedule + /// cancellation. Pass a fake provider in tests to deterministically control + /// expiration. + /// + /// A new instance that has already started. + internal static TimeoutTimer StartNew(TimeSpan expiration, TimeProvider timeProvider) + => new TimeoutTimer(expiration, timeProvider); + + /// + /// Creates a new that is already expired, + /// using as the time source. + /// + /// + /// A finite whose is + /// already and whose + /// is zero. + /// + internal static TimeoutTimer StartExpired() + => StartExpired(TimeProvider.System); + + /// + /// Creates a new that is already expired. + /// + /// + /// The used to read the current time and schedule + /// cancellation. + /// + /// + /// A finite whose is + /// already and whose + /// is zero. Useful when a code path needs to hand off an already-exhausted + /// timeout (for example, a child timer whose parent has no remaining + /// budget) without resorting to negative durations or the + /// sentinel. + /// + /// + /// Implemented by anchoring the expiration one tick before "now" on the + /// supplied . The timer is finite, so + /// is . + /// + internal static TimeoutTimer StartExpired(TimeProvider timeProvider) + => new TimeoutTimer(TimeSpan.FromTicks(-1), timeProvider); + + /// + /// Creates and starts a new nested under this + /// (parent) timer. The child shares the parent's + /// and is capped so that it cannot outlast the parent's remaining time. + /// + /// + /// The desired duration of the child timer, interpreted literally — a + /// value of means "expire immediately" and + /// is not treated as the + /// sentinel. A non-positive value yields an already-expired child. + /// + /// + /// A new that uses this timer's + /// . The child is finite unless the parent is + /// infinite, in which case the requested is + /// honored as-is. When the parent is finite, the child's expiration is + /// capped at the parent's remaining time. + /// + /// + /// Behavior matrix: + /// + /// Parent infinite → finite child with the requested duration (or already-expired when ≤ 0). + /// Parent finite, duration longer than parent's remaining → finite child capped at the parent's remaining time. + /// Parent finite, duration shorter than parent's remaining → finite child with the requested duration. + /// Parent finite with no remaining time, or ≤ 0 → already-expired child (see ). + /// + /// To request a truly infinite timeout, call + /// directly with ; this method does not + /// produce infinite children. + /// + internal TimeoutTimer StartChild(TimeSpan duration) + { + long requestedMs = (long)duration.TotalMilliseconds; + + // Caller asked for a non-positive duration: already expired. + if (requestedMs <= 0) + { + return StartExpired(TimeProvider); + } + + // Parent finite: cap at parent's remaining time. If the cap leaves + // no time, return an already-expired timer rather than colliding + // with the 0-ticks-means-infinite sentinel. + long childMs = Math.Min(requestedMs, MillisecondsRemaining); + if (childMs <= 0) + { + return StartExpired(TimeProvider); + } + + return new TimeoutTimer(TimeSpan.FromMilliseconds(childMs), TimeProvider); + } + + /// + /// Creates a new that will be canceled + /// when this timer expires, using the same the + /// timer was constructed with. + /// + /// + /// A scheduled to cancel after + /// milliseconds. When + /// is , the returned source + /// is never automatically canceled. When the timer has already expired, the + /// returned source is already canceled. + /// + internal CancellationTokenSource CreateCancellationTokenSource() + { + if (IsInfinite) + { + return new CancellationTokenSource(); + } + + int remaining = MillisecondsRemainingInt; + if (remaining == 0) + { + CancellationTokenSource cts = new CancellationTokenSource(); + cts.Cancel(); + return cts; + } + + // Route the timer through the configured TimeProvider so that fake + // time providers can advance virtual time and trigger cancellation + // deterministically in tests. +#if NET + // On .NET 8+ the BCL provides this constructor directly; avoid the + // Microsoft.Bcl.TimeProvider extension so the produced assembly + // doesn't carry a hard reference to the polyfill package, which the + // .NET SDK prunes from downstream consumers' restore graphs. + return new CancellationTokenSource(TimeSpan.FromMilliseconds(remaining), TimeProvider); +#else + // .NET Framework lacks the constructor overload; use the extension + // method shipped by Microsoft.Bcl.TimeProvider. + return TimeProvider.CreateCancellationTokenSource(TimeSpan.FromMilliseconds(remaining)); +#endif + } + + /// + /// Resets the timeout to its original duration. + /// + /// + /// This method is only used to retry after federated authentication timeouts, + /// which can use up the whole timeout due to MFA. Has no effect when + /// is . + /// + internal void Reset() + { + if (!IsInfinite) + { + ExpirationTicks = checked(NowTicks() + OriginalTicks); + } + } + + /// + /// Reads the configured 's current UTC time and + /// returns it as file-time ticks (100-nanosecond intervals since + /// 1601-01-01 UTC). This keeps in the same + /// scale historically produced by DateTime.UtcNow.ToFileTimeUtc(). + /// + internal long NowTicks() => TimeProvider.GetUtcNow().UtcDateTime.ToFileTimeUtc(); + + /// + /// Converts a tick count (100-nanosecond intervals) to milliseconds, matching + /// the conversion historically performed by ADP.TimerToMilliseconds. + /// + internal static long TicksToMilliseconds(long ticks) => ticks / TimeSpan.TicksPerMillisecond; + + #endregion } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index ac9d36bdd7..295286b1e5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -317,6 +317,7 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable internal SqlConnectionInternal( DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, + TimeoutTimer timeout, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, string newPassword, @@ -399,7 +400,10 @@ internal SqlConnectionInternal( try { - _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout); + // If we want to consider pool operations against the overall connect timeout, + // use the provided timeout. Otherwise, start a fresh timeout to receive the full + // connect timeout. + _timeout = ResolveLoginTimeout(timeout, connectionOptions.ConnectTimeout); // If transient fault handling is enabled then we can retry the login up to the // ConnectRetryCount. @@ -761,6 +765,25 @@ private SqlInternalTransaction AvailableInternalTransaction // @TODO: Make private field. private bool IsAzureSqlConnection { get; set; } + internal TimeoutTimer Timeout => _timeout; + + /// + /// Selects the that governs the login phase based on + /// . + /// + /// + /// When the switch is enabled the caller-supplied + /// is returned as-is so any time already consumed (e.g., waiting for the pool) counts + /// against the overall ConnectTimeout. When disabled, a fresh timer is started from + /// , preserving legacy behavior. Extracted so + /// this branch can be unit-tested without standing up a real connection. + /// + internal static TimeoutTimer ResolveLoginTimeout(TimeoutTimer callerTimeout, int connectTimeoutSeconds) + { + return LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait + ? callerTimeout + : TimeoutTimer.StartNew(TimeSpan.FromSeconds(connectTimeoutSeconds)); + } #endregion #region Public and Internal Methods @@ -1939,9 +1962,10 @@ internal void OnLoginAck(SqlLoginAck rec) internal override bool TryReplaceConnection( DbConnection outerConnection, SqlConnectionFactory connectionFactory, - TaskCompletionSource retry) + TaskCompletionSource retry, + TimeoutTimer timeout) { - return TryOpenConnectionInternal(outerConnection, connectionFactory, retry); + return TryOpenConnectionInternal(outerConnection, connectionFactory, retry, timeout); } internal void ValidateConnectionForExecute(SqlCommand command) @@ -2194,6 +2218,7 @@ private void AttemptOneLogin( /// if a cached token exists from a previous auth attempt (see GetFedAuthToken). /// // @TODO: Rename to meet naming conventions + // TODO: if this call timed out, what reason do we have to believe some other call succeeded? why not just fail? private bool AttemptRetryADAuthWithTimeoutError( SqlException sqlex, SqlConnectionOptions connectionOptions, // @TODO: this is not used @@ -3190,7 +3215,6 @@ private void LoginNoFailover( // Set timeout for this attempt, but don't exceed original timer long nextTimeoutInterval = checked(timeoutUnitInterval * multiplier); - long milliseconds = timeout.MillisecondsRemaining; #if NETFRAMEWORK // If it is the first attempt at TNIR connection, then allow at least 500ms for @@ -3202,11 +3226,11 @@ private void LoginNoFailover( } #endif - if (nextTimeoutInterval > milliseconds) - { - nextTimeoutInterval = milliseconds; - } - intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval); + // StartChild propagates the parent TimeProvider, caps the + // requested duration at the parent's remaining time, and + // returns an already-expired timer when the parent has no + // remaining budget (no 0-means-infinite ambiguity). + intervalTimer = timeout.StartChild(TimeSpan.FromMilliseconds(nextTimeoutInterval)); } // Re-allocate parser each time to make sure state is known. @@ -3485,13 +3509,12 @@ private void LoginWithFailover( { // Set timeout for this attempt, but don't exceed original timer long nextTimeoutInterval = checked(timeoutUnitInterval * ((attemptNumber / 2) + 1)); - long milliseconds = timeout.MillisecondsRemaining; - if (nextTimeoutInterval > milliseconds) - { - nextTimeoutInterval = milliseconds; - } - TimeoutTimer intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval); + // StartChild propagates the parent TimeProvider, caps the + // requested duration at the parent's remaining time, and + // returns an already-expired timer when the parent has no + // remaining budget (no 0-means-infinite ambiguity). + TimeoutTimer intervalTimer = timeout.StartChild(TimeSpan.FromMilliseconds(nextTimeoutInterval)); // Re-allocate parser each time to make sure state is known. If parser was created // by previous attempt, dispose it to properly close the socket, if created. 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 index b83434cac6..45e82eda91 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -224,7 +224,8 @@ public void PutObjectFromTransactedPool(DbConnectionInternal connection) /// public DbConnectionInternal ReplaceConnection( DbConnection owningObject, - DbConnectionInternal oldConnection) + DbConnectionInternal oldConnection, + TimeoutTimer timeout) { throw new NotImplementedException(); } @@ -281,10 +282,9 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans public bool TryGetConnection( DbConnection owningObject, TaskCompletionSource? taskCompletionSource, + TimeoutTimer timeout, out DbConnectionInternal? connection) { - var timeout = TimeSpan.FromSeconds(owningObject.ConnectionTimeout); - // If taskCompletionSource is null, we are in a sync context. if (taskCompletionSource is null) { @@ -372,13 +372,16 @@ public bool TryGetConnection( /// /// The owning connection. /// The cancellation token to cancel the operation. + /// The overall timeout budget. Passed through to the physical connection + /// so it uses the remaining budget rather than starting a fresh timeout. /// A task representing the asynchronous operation, with a result of the new internal connection. /// /// Thrown when the cancellation token is cancelled before the connection operation completes. /// private DbConnectionInternal? OpenNewInternalConnection( DbConnection? owningConnection, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + TimeoutTimer timeout) { cancellationToken.ThrowIfCancellationRequested(); @@ -397,9 +400,11 @@ public bool TryGetConnection( // DbConnectionInternal doesn't support an async open. It's better to block this thread and keep // throughput high than to queue all of our opens onto a single worker thread. Add an async path // when this support is added to DbConnectionInternal. + // TODO: ultimately, the connection factory should also accept our cancellation token. var connection = ConnectionFactory.CreatePooledConnection( owningConnection, - this); + this, + timeout); if (connection is not null) { @@ -492,7 +497,8 @@ private void RemoveConnection(DbConnectionInternal connection) ///
/// The DbConnection that will own this internal connection /// A boolean indicating whether the operation should be asynchronous. - /// The timeout for the operation. + /// The overall timeout budget for this connection request. Time spent waiting + /// in the pool is deducted from the budget available for physical connection creation. /// Returns a DbConnectionInternal that is retrieved from the pool. /// /// Thrown when an OperationCanceledException is caught, indicating that the timeout period @@ -505,10 +511,13 @@ private void RemoveConnection(DbConnectionInternal connection) private async Task GetInternalConnection( DbConnection owningConnection, bool async, - TimeSpan timeout) + TimeoutTimer timeout) { DbConnectionInternal? connection = null; - using CancellationTokenSource cancellationTokenSource = new(timeout); + + // Derive a CancellationTokenSource from the TimeoutTimer so pool-internal wait operations + // (channel reads, semaphore waits) are cancelled when the overall budget expires. + using CancellationTokenSource cancellationTokenSource = timeout.CreateCancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; // Continue looping until we create or retrieve a connection @@ -524,7 +533,8 @@ private async Task GetInternalConnection( // If we didn't find an idle connection, try to open a new one. connection ??= OpenNewInternalConnection( owningConnection, - cancellationToken); + cancellationToken, + timeout); // If we're at max capacity and couldn't open a connection. Block on the idle channel with a // timeout. Note that Channels guarantee fair FIFO behavior to callers of ReadAsync 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 index 43dae3fa0f..2ae8e8ea98 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -119,17 +119,20 @@ internal interface IDbConnectionPool /// 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 overall timeout budget for this connection request. Time spent waiting in + /// the pool is deducted from the budget available for physical connection creation. /// 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, out DbConnectionInternal? connection); + bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, TimeoutTimer timeout, out DbConnectionInternal? connection); /// /// Replaces the internal connection currently associated with owningObject with a new internal connection from the pool. /// /// The connection whose internal connection should be replaced. /// The internal connection currently associated with the owning object. + /// The overall timeout budget for this connection request. /// A reference to the new DbConnectionInternal. - DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection); + DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection, TimeoutTimer timeout); /// /// Returns an internal connection to the pool. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index fa704ab438..ecf8840ebb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -61,15 +61,17 @@ internal sealed class WaitHandleDbConnectionPool : IDbConnectionPool private sealed class PendingGetConnection { - public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource completion) + public PendingGetConnection(long dueTime, DbConnection owner, TaskCompletionSource completion, TimeoutTimer timeout) { DueTime = dueTime; Owner = owner; Completion = completion; + Timeout = timeout; } public long DueTime { get; private set; } public DbConnection Owner { get; private set; } public TaskCompletionSource Completion { get; private set; } + public TimeoutTimer Timeout { get; private set; } } private sealed class PoolWaitHandles @@ -520,7 +522,7 @@ private bool IsBlockingPeriodEnabled() } } - private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionInternal oldConnection) + private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionInternal oldConnection, TimeoutTimer timeout) { DbConnectionInternal newObj = null; @@ -528,7 +530,8 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio { newObj = _connectionFactory.CreatePooledConnection( owningObject, - this); + this, + timeout); lock (_objectList) { @@ -828,6 +831,7 @@ private void WaitForPendingOpen() delay, allowCreate: true, onlyOneCheckConnection: false, + next.Timeout, out connection); } // @TODO: CER Exception Handling was removed here (see GH#3581) @@ -865,24 +869,42 @@ private void WaitForPendingOpen() } while (_pendingOpens.TryPeek(out next)); } - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, out DbConnectionInternal connection) + /// + /// Resolves the WaitHandle.WaitAny timeout (milliseconds) for a synchronous + /// pool acquire. + /// + /// + /// When + /// is enabled the caller's remaining budget is used so + /// the pool wait participates in the overall ConnectTimeout. Otherwise the legacy + /// behavior is preserved: the static pool CreationTimeout is used, with + /// 0 mapped to . Extracted so this branch can + /// be unit-tested without timing-based assertions. + /// + internal static uint ResolvePoolWaitTimeoutMs(TimeoutTimer timeout, int creationTimeoutMs) + { + if (LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait) + { + return timeout.IsInfinite + ? unchecked((uint)Timeout.Infinite) + : (uint)timeout.MillisecondsRemainingInt; + } + + uint legacy = (uint)creationTimeoutMs; + return legacy == 0 ? unchecked((uint)Timeout.Infinite) : legacy; + } + + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, TimeoutTimer timeout, out DbConnectionInternal connection) { uint waitForMultipleObjectsTimeout = 0; bool allowCreate = false; if (taskCompletionSource == null) { - waitForMultipleObjectsTimeout = (uint)CreationTimeout; - - // Set the wait timeout to INFINITE (-1) if the SQL connection timeout is 0 (== infinite) - if (waitForMultipleObjectsTimeout == 0) - { - waitForMultipleObjectsTimeout = unchecked((uint)Timeout.Infinite); - } - + waitForMultipleObjectsTimeout = ResolvePoolWaitTimeoutMs(timeout, CreationTimeout); allowCreate = true; - } - + } + if (State is not Running) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, DbConnectionInternal State != Running.", Id); @@ -891,7 +913,7 @@ public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource {0}, Creating new connection.", Id); try { - obj = UserCreateRequest(owningObject); + obj = UserCreateRequest(owningObject, timeout); } catch { @@ -1034,7 +1073,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj if (semaphoreHolder.Obtained) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", Id); - obj = UserCreateRequest(owningObject); + obj = UserCreateRequest(owningObject, timeout); } else { @@ -1121,11 +1160,12 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o /// /// Outer connection that currently owns /// Inner connection that will be replaced + /// Overall timeout budget for this connection request. /// A new inner connection that is attached to the - public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection, TimeoutTimer timeout) { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", Id); - DbConnectionInternal newConnection = UserCreateRequest(owningObject, oldConnection); + DbConnectionInternal newConnection = UserCreateRequest(owningObject, timeout, oldConnection); if (newConnection != null) { @@ -1265,7 +1305,12 @@ private void PoolCreateRequest(object state) { try { - newObj = CreateObject(owningObject: null, oldConnection: null); + // Pool replenishment runs on a background worker without an + // owning Open() call, so use a fresh per-attempt timeout based on + // the pool's CreationTimeout (matches the original behavior). + TimeoutTimer replenishTimeout = TimeoutTimer.StartNew( + TimeSpan.FromMilliseconds(CreationTimeout)); + newObj = CreateObject(owningObject: null, oldConnection: null, timeout: replenishTimeout); } catch { @@ -1504,7 +1549,7 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans } } - private DbConnectionInternal UserCreateRequest(DbConnection owningObject, DbConnectionInternal oldConnection = null) + private DbConnectionInternal UserCreateRequest(DbConnection owningObject, TimeoutTimer timeout, DbConnectionInternal oldConnection = null) { // called by user when they were not able to obtain a free object but // instead obtained creation mutex @@ -1524,7 +1569,7 @@ private DbConnectionInternal UserCreateRequest(DbConnection owningObject, DbConn // TODO: Consider implement a control knob here; why do we only check for dead objects ever other time? why not every 10th time or every time? if ((oldConnection != null) || (Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects()) { - obj = CreateObject(owningObject, oldConnection); + obj = CreateObject(owningObject, oldConnection, timeout); } } return obj; 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 0177338bf2..df762ed7ae 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -125,6 +125,13 @@ internal static class LocalAppContextSwitches private const string UseConnectionPoolV2String = "Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + /// + /// The name of the app context switch that controls whether pool operations + /// should count against the caller's overall ConnectTimeout budget. + /// + private const string UseOverallConnectTimeoutForPoolWaitString = + "Switch.Microsoft.Data.SqlClient.UseOverallConnectTimeoutForPoolWait"; + #if NET && _WINDOWS /// /// The name of the app context switch that controls whether to use the @@ -234,6 +241,11 @@ private enum SwitchValue : byte /// private static SwitchValue s_useConnectionPoolV2 = SwitchValue.None; + /// + /// The cached value of the UseOverallConnectTimeoutForPoolWait switch. + /// + private static SwitchValue s_useOverallConnectTimeoutForPoolWait = SwitchValue.None; + #if NET && _WINDOWS /// /// The cached value of the UseManagedNetworking switch. @@ -564,6 +576,20 @@ public static bool UseCompatibilityAsyncBehaviour defaultValue: false, ref s_useConnectionPoolV2); + /// + /// When set to true, pool operations count against the + /// caller's ConnectTimeout budget. This includes waits and async operations. + /// When false, pool operations receive a full ConnectTimeout and + /// network calls receive a further full ConnectTimeout. + /// + /// The default value of this switch is false. + /// + public static bool UseOverallConnectTimeoutForPoolWait => + AcquireAndReturn( + UseOverallConnectTimeoutForPoolWaitString, + defaultValue: false, + ref s_useOverallConnectTimeoutForPoolWait); + #if NET && _WINDOWS /// /// When set to true, .NET on Windows will use the managed SNI diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs index 15a856dcec..176420db87 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs @@ -892,6 +892,9 @@ public override uint Receive(out SniPacket packet, int timeoutInMilliseconds) try { + // TODO: convert these to async versions that accept a cancellation token + // this will let us pass fake time providers all the way down the stack + // and easily test timeout behavior. if (timeoutInMilliseconds > 0) { _socket.ReceiveTimeout = timeoutInMilliseconds; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 47a1be163a..6a4d7b8972 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -2226,16 +2226,26 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec private bool TryOpenInner(TaskCompletionSource retry) { + // Create a single TimeoutTimer that represents the overall connection-open budget for this + // attempt. The timer is threaded through every layer (inner connection state transitions, + // connection factory, pool wait, physical connection establishment) so that all of those + // costs are charged against the same budget. This prevents the cumulative wait from exceeding + // the configured ConnectTimeout. A fresh timer is created per TryOpen call so that each + // retry attempt gets its own budget, matching the existing behavior. + // Note: TimeoutTimer treats 0 seconds as infinite timeout, which matches ConnectTimeout=0 semantics. + TimeoutTimer timeout = TimeoutTimer.StartNew( + TimeSpan.FromSeconds(ConnectionOptions?.ConnectTimeout ?? ADP.DefaultConnectionTimeout)); + if (ForceNewConnection) { - if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry)) + if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry, timeout)) { return false; } } else { - if (!InnerConnection.TryOpenConnection(this, ConnectionFactory, retry)) + if (!InnerConnection.TryOpenConnection(this, ConnectionFactory, retry, timeout)) { return false; } @@ -2735,6 +2745,7 @@ private static void ChangePassword(string connectionString, SqlConnectionOptions con = new SqlConnectionInternal( identity: null, connectionOptions, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(connectionOptions.ConnectTimeout)), credential, providerInfo: null, newPassword, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 30010d0d4b..4cdee8bdc2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -115,7 +115,8 @@ internal DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(SqlConnec internal DbConnectionInternal CreateNonPooledConnection( DbConnection owningConnection, - DbConnectionPoolGroup poolGroup) + DbConnectionPoolGroup poolGroup, + TimeoutTimer timeout) { Debug.Assert(owningConnection is not null, "null owningConnection?"); Debug.Assert(poolGroup is not null, "null poolGroup?"); @@ -125,7 +126,8 @@ internal DbConnectionInternal CreateNonPooledConnection( poolGroup.PoolKey, poolGroup.ProviderInfo, pool: null, - owningConnection); + owningConnection, + timeout); if (newConnection is not null) { SqlClientDiagnostics.Metrics.HardConnectRequest(); @@ -138,7 +140,8 @@ internal DbConnectionInternal CreateNonPooledConnection( internal DbConnectionInternal CreatePooledConnection( DbConnection owningConnection, - IDbConnectionPool pool) + IDbConnectionPool pool, + TimeoutTimer timeout) { Debug.Assert(pool != null, "null pool?"); @@ -147,7 +150,8 @@ internal DbConnectionInternal CreatePooledConnection( pool.PoolGroup.PoolKey, pool.PoolGroup.ProviderInfo, pool, - owningConnection); + owningConnection, + timeout); if (newConnection is null) { @@ -313,6 +317,7 @@ internal bool TryGetConnection( DbConnection owningConnection, TaskCompletionSource retry, DbConnectionInternal oldConnection, + TimeoutTimer timeout, out DbConnectionInternal connection) { Debug.Assert(owningConnection is not null, "null owningConnection?"); @@ -384,6 +389,7 @@ internal bool TryGetConnection( retry, oldConnection, poolGroup, + timeout, cancellationTokenSource); // Place this new task in the slot so any future work will be queued behind it @@ -407,7 +413,7 @@ internal bool TryGetConnection( return false; } - connection = CreateNonPooledConnection(owningConnection, poolGroup); + connection = CreateNonPooledConnection(owningConnection, poolGroup, timeout); SqlClientDiagnostics.Metrics.EnterNonPooledConnection(); } @@ -417,11 +423,11 @@ internal bool TryGetConnection( { Debug.Assert(oldConnection is not DbConnectionClosed, "Force new connection, but there is no old connection"); - connection = connectionPool.ReplaceConnection(owningConnection, oldConnection); + connection = connectionPool.ReplaceConnection(owningConnection, oldConnection, timeout); } else { - if (!connectionPool.TryGetConnection(owningConnection, retry, out connection)) + if (!connectionPool.TryGetConnection(owningConnection, retry, timeout, out connection)) { return false; } @@ -573,7 +579,8 @@ protected virtual DbConnectionInternal CreateConnection( ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, - DbConnection owningConnection) + DbConnection owningConnection, + TimeoutTimer timeout) { SqlConnectionOptions opt = options; ConnectionPoolKey key = poolKey; @@ -628,6 +635,7 @@ protected virtual DbConnectionInternal CreateConnection( SqlConnectionInternal sseConnection = new SqlConnectionInternal( identity, sseopt, + timeout, key.Credential, providerInfo: null, newPassword: string.Empty, @@ -678,6 +686,7 @@ protected virtual DbConnectionInternal CreateConnection( return new SqlConnectionInternal( identity, opt, + timeout, key.Credential, poolGroupProviderInfo, newPassword: string.Empty, @@ -752,6 +761,7 @@ private Task CreateReplaceConnectionContinuation( TaskCompletionSource retry, DbConnectionInternal oldConnection, DbConnectionPoolGroup poolGroup, + TimeoutTimer timeout, CancellationTokenSource cancellationTokenSource) { return task.ContinueWith( @@ -762,7 +772,7 @@ private Task CreateReplaceConnectionContinuation( { ADP.SetCurrentTransaction(retry.Task.AsyncState as System.Transactions.Transaction); - DbConnectionInternal newConnection = CreateNonPooledConnection(owningConnection, poolGroup); + DbConnectionInternal newConnection = CreateNonPooledConnection(owningConnection, poolGroup, timeout); if (oldConnection?.State == ConnectionState.Open) { 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 0e8548c2b1..bb625d563d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -542,7 +542,7 @@ bool withFailover } _state = TdsParserState.OpenNotLoggedIn; _physicalStateObj.SniContext = SniContext.Snix_PreLoginBeforeSuccessfulWrite; - _physicalStateObj.TimeoutTime = timeout.LegacyTimerExpire; + _physicalStateObj.TimeoutTime = timeout.IsInfinite ? long.MaxValue : timeout.ExpirationTicks; bool marsCapable = false; diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index db84a0a51a..430d6645a8 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -54,6 +54,7 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly bool? _useCompatibilityAsyncBehaviourOriginal; private readonly bool? _useCompatibilityProcessSniOriginal; private readonly bool? _useConnectionPoolV2Original; + private readonly bool? _useOverallConnectTimeoutForPoolWaitOriginal; #if NET && _WINDOWS private readonly bool? _useManagedNetworkingOriginal; #endif @@ -115,6 +116,8 @@ public LocalAppContextSwitchesHelper() GetSwitchValue("s_useCompatibilityProcessSni"); _useConnectionPoolV2Original = GetSwitchValue("s_useConnectionPoolV2"); + _useOverallConnectTimeoutForPoolWaitOriginal = + GetSwitchValue("s_useOverallConnectTimeoutForPoolWait"); #if NET && _WINDOWS _useManagedNetworkingOriginal = GetSwitchValue("s_useManagedNetworking"); @@ -182,6 +185,9 @@ public void Dispose() SetSwitchValue( "s_useConnectionPoolV2", _useConnectionPoolV2Original); + SetSwitchValue( + "s_useOverallConnectTimeoutForPoolWait", + _useOverallConnectTimeoutForPoolWaitOriginal); #if NET && _WINDOWS SetSwitchValue( "s_useManagedNetworking", @@ -327,6 +333,15 @@ public bool? UseConnectionPoolV2 set => SetSwitchValue("s_useConnectionPoolV2", value); } + /// + /// Get or set the UseOverallConnectTimeoutForPoolWait switch value. + /// + public bool? UseOverallConnectTimeoutForPoolWait + { + get => GetSwitchValue("s_useOverallConnectTimeoutForPoolWait"); + set => SetSwitchValue("s_useOverallConnectTimeoutForPoolWait", value); + } + #if NET && _WINDOWS /// /// Get or set the UseManagedNetworking switch value. diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props index 72bddcc898..7925952d11 100644 --- a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props @@ -4,6 +4,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs index 00bba56fc2..26f65b6fae 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs @@ -9,6 +9,58 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + /// + /// Stress test that verifies cancelling a command mid-stream does not + /// corrupt the underlying connection, the connection pool, or (when MARS + /// is enabled) the MARS session state machine. + /// + /// + /// For each of parallel tasks, the test: + /// + /// + /// + /// Runs a single "poisoned" command: a 4-batch query with + /// WAITFOR DELAY '00:00:01' between batches, paired with a + /// background task that fires + /// after a random 100-3000 ms delay. + /// The streaming is expected to throw + /// either or a + /// whose message contains + /// "operation cancelled/canceled"; that exception is swallowed. + /// + /// + /// Runs up to follow-up commands on + /// the same physical resources (the same MARS connection when + /// useMars is true, otherwise fresh pooled connections). These + /// must all complete cleanly; any failure here indicates the prior + /// cancellation corrupted shared state. + /// + /// + /// + /// + /// The test asserts implicitly: any exception that is not the expected + /// cancellation is rethrown and fails the test. The known regression + /// signature for a desynchronized MARS framing buffer + /// ("The MARS TDS header contained errors.") is treated as a hard stop + /// via _continue. + /// + /// + /// + /// What this test is not. It is not a coverage test for + /// OpenAsync, Connect Timeout, or pool queue-wait behavior. + /// OpenAsync is treated as pre-work that must succeed so the + /// cancellation/poisoning scenario can run. Because + /// simultaneous opens race against an empty + /// pool whose connection-creation path is serialized internally + /// (WaitHandleDbConnectionPool.WaitForPendingOpen), the caller's + /// per-open Connect Timeout budget must be generous enough to + /// cover queue-wait + physical connect for the last open in the burst. + /// The test therefore overrides Connect Timeout on the builder + /// (see ) rather than relying on the + /// default 15 s; bump that constant if slow CI agents are seeing + /// pool-timeout failures on otherwise healthy runs. + /// + /// public class AsyncCancelledConnectionsTest { /// @@ -21,9 +73,28 @@ public class AsyncCancelledConnectionsTest /// private const int NumberOfNonPoisoned = 10; + /// + /// Per-open Connect Timeout applied to every connection in + /// this test. Sized to comfortably cover the serialized + /// connection-creation queue depth produced by + /// simultaneous opens on slow CI agents. + /// Note: with strict timeout propagation through the pool, the + /// caller's budget covers both pool queue wait and physical connect, + /// so the default 15 s is too tight for this burst pattern. + /// + private const int ConnectTimeoutSeconds = 60; + private bool _continue = true; private Random _random; + /// + /// Drives parallel + /// runs against the configured TCP test server and waits for all of + /// them to complete. The theory matrix toggles MARS so that both the + /// shared-connection (MARS) and per-call-connection (non-MARS) paths + /// are exercised. The test passes if every task either succeeds or + /// fails only with the expected cancellation exception. + /// // Disabled on Azure since this test fails on concurrent runs on same database. // Disabled on Kerberos and Managed Instance pipelines due to environment-specific instability. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), @@ -36,6 +107,12 @@ public async Task CancelAsyncConnections(bool useMars) // Arrange SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); builder.MultipleActiveResultSets = useMars; + // The pool serializes physical connection creation, and with + // strict Connect Timeout propagation through the pool, the + // caller's budget must cover queue-wait time for the last open + // in a NumberOfTasks-wide burst. Bump the default 15 s budget so + // a slow CI agent doesn't time out legitimately-queued opens. + builder.ConnectTimeout = ConnectTimeoutSeconds; SqlConnection.ClearAllPools(); @@ -53,7 +130,16 @@ public async Task CancelAsyncConnections(bool useMars) // Assert - If test runs to completion, it is successful } - // This is the main body that our Tasks run + /// + /// Body run by each parallel task. When + /// is enabled, opens a single long-lived MARS connection that is + /// reused by every call in this task so that + /// cancellation effects on the shared MARS session are observable. + /// Then runs exactly one poisoned attempt followed by up to + /// non-poisoned attempts (gated by + /// , which is cleared on a MARS-header + /// corruption signature). + /// private async Task DoManyAsync(SqlConnectionStringBuilder connectionStringBuilder) { string connectionString = connectionStringBuilder.ToString(); @@ -74,6 +160,24 @@ private async Task DoManyAsync(SqlConnectionStringBuilder connectionStringBuilde } } + /// + /// Executes one 4-batch query and reads every result set. When + /// is true, the batches are interleaved + /// with WAITFOR DELAY '00:00:01' so the command runs long + /// enough for to cancel it mid-stream; + /// the resulting cancellation exception is expected and swallowed. + /// When is false the command must complete + /// cleanly - this is the assertion that prior cancellation did not + /// corrupt shared state (the MARS session or the pooled connection). + /// + /// Shared MARS connection to reuse when + /// open; otherwise a fresh per-call is + /// opened from . + /// Connection string used for the + /// non-MARS path. + /// If true, schedules a time-bomb + /// and expects the cancellation + /// exception; if false, the command must succeed. private async Task DoOneAsync(SqlConnection marsConnection, string connectionString, bool poison) { // This will do our work, open a connection, and run a query (that returns 4 results sets) @@ -121,6 +225,14 @@ private async Task DoOneAsync(SqlConnection marsConnection, string connectionStr } } + /// + /// Recognizes the two exception shapes that a mid-stream + /// can surface as: a managed + /// , or a + /// whose message reports the server-side + /// "operation cancelled/canceled" error. Any other exception is, by + /// design, treated as a real failure of the test. + /// private static bool IsExpectedCancellation(Exception ex) { switch (ex) @@ -135,6 +247,19 @@ private static bool IsExpectedCancellation(Exception ex) } } + /// + /// Issues on + /// via + /// and drains every + /// row of every result set. When is true a + /// is started in parallel to cancel the + /// command mid-read, and the inner catch (SqlException) + /// deliberately tries to drain remaining result sets after the + /// initial failure - this simulates the realistic dispose-on-error + /// pattern where a caller may attempt cleanup reads on a reader that + /// already faulted, and ensures it doesn't itself wedge the + /// connection. + /// private async Task RunCommand(SqlConnection connection, string commandText, bool poison) { using SqlCommand command = connection.CreateCommand(); @@ -187,6 +312,15 @@ private async Task RunCommand(SqlConnection connection, string commandText, bool } } + /// + /// Waits a random 100-3000 ms and then calls + /// on the supplied command. The + /// randomized delay is intentional: it spreads cancellations across + /// different points in the reader's lifecycle (pre-execute, + /// mid-first-result, between result sets, etc.) to exercise more of + /// the cancellation state machine across the + /// parallel runs. + /// private async Task TimeBombAsync(SqlCommand command) { // Sleep a random amount between 100 and 3000 ms. 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 42a2331be6..ca22d6eafd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -172,12 +172,14 @@ private static string GetSPNInfo(string dataSource, string inInstanceName) Type[] getPortByInstanceNameTypesArray = new Type[] { typeof(string), typeof(string), timeoutTimerType, typeof(bool), typeof(Microsoft.Data.SqlClient.SqlConnectionIPAddressPreference) }; - Type[] startSecondsTimeoutTypesArray = new Type[] { typeof(int) }; + // The current TimeoutTimer API exposes only a static StartNew(TimeSpan) + // factory; the legacy parameterless constructor + StartSecondsTimeout(int) + // shape no longer exists. + Type[] startNewTypesArray = new Type[] { typeof(TimeSpan) }; ConstructorInfo sniProxyConstructor = sniProxyType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null); ConstructorInfo SSRPConstructor = ssrpType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null); ConstructorInfo dataSourceConstructor = dataSourceType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, dataSourceConstructorTypesArray, null); - ConstructorInfo timeoutTimerConstructor = timeoutTimerType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null); object sniProxyObj = sniProxyConstructor.Invoke(new object[] { }); @@ -185,11 +187,9 @@ private static string GetSPNInfo(string dataSource, string inInstanceName) object ssrpObj = SSRPConstructor.Invoke(new object[] { }); - object timeoutTimerObj = timeoutTimerConstructor.Invoke(new object[] { }); + MethodInfo startNewInfo = timeoutTimerType.GetMethod("StartNew", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, startNewTypesArray, null); - MethodInfo startSecondsTimeoutInfo = timeoutTimerObj.GetType().GetMethod("StartSecondsTimeout", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, startSecondsTimeoutTypesArray, null); - - timeoutTimerObj = startSecondsTimeoutInfo.Invoke(dataSourceObj, new object[] { 30 }); + object timeoutTimerObj = startNewInfo.Invoke(null, new object[] { TimeSpan.FromSeconds(30) }); MethodInfo parseServerNameInfo = dataSourceObj.GetType().GetMethod("ParseServerName", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, dataSourceConstructorTypesArray, null); object dataSrcInfo = parseServerNameInfo.Invoke(dataSourceObj, new object[] { dataSource }); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs index 28aa62ccf5..9ed25a6e74 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs @@ -55,7 +55,10 @@ public async Task Delegated_transaction_deadlock_in_SinglePhaseCommit() public async Task Test_EnlistedTransactionPreservedWhilePooled() { #if NET - TransactionManager.ImplicitDistributedTransactions = true; + if (OperatingSystem.IsWindows()) + { + TransactionManager.ImplicitDistributedTransactions = true; + } #endif await RunTestSet(EnlistedTransactionPreservedWhilePooled); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index 7c1593af14..1fea8aede1 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -12,6 +12,7 @@ using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; +using Microsoft.Extensions.Time.Testing; using Xunit; namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool @@ -63,6 +64,7 @@ public void GetConnectionEmptyPool_ShouldCreateNewConnection(int numConnections) var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); @@ -92,6 +94,7 @@ public async Task GetConnectionAsyncEmptyPool_ShouldCreateNewConnection(int numC var completed = pool.TryGetConnection( new SqlConnection(), tcs, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); @@ -117,6 +120,7 @@ public void GetConnectionMaxPoolSize_ShouldTimeoutAfterPeriod() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); @@ -124,23 +128,27 @@ out DbConnectionInternal? internalConnection Assert.NotNull(internalConnection); } - try + // Build a timer backed by a fake time provider, then advance virtual time past + // the timer's expiration so the pool's CancellationTokenSource is created + // already-cancelled and the timeout path fires deterministically without any + // wall-clock wait. + var fakeTime = new FakeTimeProvider(); + TimeoutTimer expiredTimer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(1), fakeTime); + fakeTime.Advance(TimeSpan.FromSeconds(2)); + + // Act & Assert + var ex = Assert.Throws(() => { - // Act - var exceeded = pool.TryGetConnection( - new SqlConnection("Timeout=1"), + pool.TryGetConnection( + new SqlConnection(), taskCompletionSource: null, - out DbConnectionInternal? extraConnection - ); - } - catch (Exception ex) - { - // Assert - Assert.IsType(ex); - Assert.Equal("Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.", ex.Message); - } + expiredTimer, + out DbConnectionInternal? extraConnection); + }); - // Assert + Assert.Equal( + "Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.", + ex.Message); Assert.Equal(pool.PoolGroupOptions.MaxPoolSize, pool.Count); } @@ -155,6 +163,7 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldTimeoutAfterPeriod() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); @@ -162,25 +171,25 @@ out DbConnectionInternal? internalConnection Assert.NotNull(internalConnection); } - try - { - // Act - TaskCompletionSource taskCompletionSource = new(); - var exceeded = pool.TryGetConnection( - new SqlConnection("Timeout=1"), - taskCompletionSource, - out DbConnectionInternal? extraConnection - ); - await taskCompletionSource.Task; - } - catch (Exception ex) - { - // Assert - Assert.IsType(ex); - Assert.Equal("Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.", ex.Message); - } + // Build a timer backed by a fake time provider then advance past expiration so + // the pool's CTS is created already-cancelled. + var fakeTime = new FakeTimeProvider(); + TimeoutTimer expiredTimer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(1), fakeTime); + fakeTime.Advance(TimeSpan.FromSeconds(2)); - // Assert + // Act & Assert + TaskCompletionSource taskCompletionSource = new(); + pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource, + expiredTimer, + out _); + + var ex = await Assert.ThrowsAsync(() => taskCompletionSource.Task); + + Assert.Equal( + "Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.", + ex.Message); Assert.Equal(pool.PoolGroupOptions.MaxPoolSize, pool.Count); } @@ -194,6 +203,7 @@ public async Task GetConnectionMaxPoolSize_ShouldReuseAfterConnectionReleased() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? firstConnection ); @@ -202,6 +212,7 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); @@ -217,6 +228,7 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? extraConnection ); return extraConnection; @@ -238,6 +250,7 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldReuseAfterConnectionReleas pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? firstConnection ); @@ -246,6 +259,7 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); @@ -259,6 +273,7 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), taskCompletionSource, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? recycledConnection ); pool.ReturnInternalConnection(firstConnection!, firstOwningConnection); @@ -279,6 +294,7 @@ public async Task GetConnectionMaxPoolSize_ShouldRespectOrderOfRequest() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? firstConnection ); @@ -287,6 +303,7 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); @@ -307,6 +324,7 @@ out DbConnectionInternal? internalConnection pool.TryGetConnection( new SqlConnection(""), null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? recycledConnection ); return recycledConnection; @@ -319,6 +337,7 @@ out DbConnectionInternal? recycledConnection pool.TryGetConnection( new SqlConnection("Timeout=1"), null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(1)), out DbConnectionInternal? failedConnection ); return failedConnection; @@ -344,6 +363,7 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldRespectOrderOfRequest() pool.TryGetConnection( firstOwningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? firstConnection ); @@ -352,6 +372,7 @@ out DbConnectionInternal? firstConnection var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); @@ -366,6 +387,7 @@ out DbConnectionInternal? internalConnection var exceeded = pool.TryGetConnection( new SqlConnection(""), recycledTaskCompletionSource, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? recycledConnection ); @@ -375,6 +397,7 @@ out DbConnectionInternal? recycledConnection var exceeded2 = pool.TryGetConnection( new SqlConnection("Timeout=1"), failedCompletionSource, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(1)), out DbConnectionInternal? failedConnection ); @@ -397,6 +420,7 @@ public void ConnectionsAreReused() var completed1 = pool.TryGetConnection( owningConnection, null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection1 ); @@ -411,6 +435,7 @@ out DbConnectionInternal? internalConnection1 var completed2 = pool.TryGetConnection( owningConnection, null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection2 ); @@ -432,11 +457,14 @@ public void GetConnectionTimeout_ShouldThrowTimeoutException() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); }); - Assert.Equal("Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.", ex.Message); + // Use the resource-backed message rather than a hardcoded English + // string so the assertion stays meaningful under any localized build. + Assert.Equal(ADP.PooledOpenTimeout().Message, ex.Message); } [Fact] @@ -452,13 +480,16 @@ public async Task GetConnectionAsyncTimeout_ShouldThrowTimeoutException() var completed = pool.TryGetConnection( new SqlConnection(), taskCompletionSource, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); await taskCompletionSource.Task; }); - Assert.Equal("Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.", ex.Message); + // Use the resource-backed message rather than a hardcoded English + // string so the assertion stays meaningful under any localized build. + Assert.Equal(ADP.PooledOpenTimeout().Message, ex.Message); } [Fact] @@ -476,6 +507,7 @@ public void StressTest() var completed = pool.TryGetConnection( owningObject, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); if (completed) @@ -509,6 +541,7 @@ public void StressTestAsync() var completed = pool.TryGetConnection( owningObject, taskCompletionSource, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? internalConnection ); internalConnection = await taskCompletionSource.Task; @@ -666,7 +699,7 @@ public void TestPutObjectFromTransactedPool() public void TestReplaceConnection() { var pool = ConstructPool(SuccessfulConnectionFactory); - Assert.Throws(() => pool.ReplaceConnection(null!, null!)); + Assert.Throws(() => pool.ReplaceConnection(null!, null!, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)))); } [Fact] @@ -705,6 +738,7 @@ public void Clear_MultipleIdleConnections_AllAreDestroyed() pool.TryGetConnection( owningConnections[i], taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out internalConnections[i] ); Assert.Equal(0, internalConnections[i]!.ClearGeneration); @@ -733,6 +767,7 @@ public void Clear_BusyConnection_NotDestroyedImmediately() pool.TryGetConnection( owningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? busyConnection ); Assert.NotNull(busyConnection); @@ -756,6 +791,7 @@ public void Clear_BusyConnectionReturned_IsDestroyed() pool.TryGetConnection( owningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? busyConnection ); Assert.NotNull(busyConnection); @@ -785,11 +821,13 @@ public void Clear_MixedBusyAndIdle_OnlyIdleDestroyedImmediately() pool.TryGetConnection( busyOwner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? busyConnection ); pool.TryGetConnection( idleOwner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? idleConnection ); Assert.NotNull(busyConnection); @@ -822,6 +860,7 @@ public void Clear_NewConnectionsAfterClear_ArePooledNormally() pool.TryGetConnection( owningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? oldConnection ); Assert.Equal(0, oldConnection!.ClearGeneration); @@ -835,6 +874,7 @@ out DbConnectionInternal? oldConnection pool.TryGetConnection( newOwner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? newConnection ); Assert.NotNull(newConnection); @@ -852,6 +892,7 @@ out DbConnectionInternal? newConnection pool.TryGetConnection( reuseOwner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? reusedConnection ); Assert.Same(newConnection, reusedConnection); @@ -868,6 +909,7 @@ public void Clear_MultipleClearCalls_DoNotCorruptState() pool.TryGetConnection( owningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? connection ); Assert.Equal(0, connection!.ClearGeneration); @@ -886,6 +928,7 @@ out DbConnectionInternal? connection pool.TryGetConnection( newOwner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? newConnection ); Assert.NotNull(newConnection); @@ -898,13 +941,17 @@ out DbConnectionInternal? newConnection #region Test classes internal class SuccessfulSqlConnectionFactory : SqlConnectionFactory { + internal TimeoutTimer? CapturedTimeout { get; private set; } + protected override DbConnectionInternal CreateConnection( SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, - DbConnection owningConnection) + DbConnection owningConnection, + TimeoutTimer timeout) { + CapturedTimeout = timeout; return new StubDbConnectionInternal(); } } @@ -916,7 +963,8 @@ protected override DbConnectionInternal CreateConnection( ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, - DbConnection owningConnection) + DbConnection owningConnection, + TimeoutTimer timeout) { throw ADP.PooledOpenTimeout(); } @@ -1081,5 +1129,131 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() Assert.NotNull(pool2); Assert.Equal(0, pool2.Count); } + + #region Connection Timeout Awareness Tests + + /// + /// Verifies that two concurrent callers waiting for the same exhausted + /// pool observe their own per-caller deadlines + /// independently: the caller with the shorter timeout fails with the + /// pool-timeout error while the caller with the longer timeout continues + /// to wait and eventually succeeds when a connection is returned. + /// + /// + /// Both callers share a single so that + /// advancing virtual time deterministically expires only the short-timeout + /// caller's CTS without consuming any wall-clock time. + /// + [Fact] + public async Task ConcurrentCallers_ShouldTimeoutIndependently() + { + // Arrange: pool at max capacity so both callers must wait + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 1, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true + ); + var pool = ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); + + SqlConnection firstOwner = new(); + pool.TryGetConnection(firstOwner, taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? firstConnection); + Assert.NotNull(firstConnection); + + // Use a single fake time provider shared by both callers so we can independently + // expire each caller's timeout via virtual time without any wall-clock waits. + // Build the timers up-front so they are anchored at virtual time t=0. + var fakeTime = new FakeTimeProvider(); + TimeoutTimer timerA = TimeoutTimer.StartNew(TimeSpan.FromSeconds(1), fakeTime); + TimeoutTimer timerB = TimeoutTimer.StartNew(TimeSpan.FromSeconds(10), fakeTime); + + // Caller A: 1s virtual timeout, Caller B: 10s virtual timeout. Both run in + // background tasks so the sync pool path can block on the channel as in production. + var callerATask = Task.Run(() => + { + pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + timerA, + out DbConnectionInternal? connectionA); + return connectionA; + }); + + var callerBTask = Task.Run(() => + { + pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + timerB, + out DbConnectionInternal? connectionB); + return connectionB; + }); + + // Act: advance virtual time past A's 1s timeout but well within B's 10s timeout. + // A's CancellationTokenSource fires (cancelling its channel wait), B's does not. + fakeTime.Advance(TimeSpan.FromSeconds(2)); + + // Assert: Caller A should observe the timeout + var exA = await Assert.ThrowsAsync(() => callerATask); + Assert.Equal( + "Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.", + exA.Message); + + // Caller B should still be waiting (8s of virtual budget remain) + Assert.False(callerBTask.IsCompleted, "Caller B should still be waiting"); + + // Release the connection so caller B can succeed + pool.ReturnInternalConnection(firstConnection, firstOwner); + + // Bound the wait so a regression in the pool can't hang the test suite + // indefinitely; a real success completes well under this budget. + Task completed = await Task.WhenAny(callerBTask, Task.Delay(TimeSpan.FromSeconds(30))); + Assert.Same(callerBTask, completed); + var resultB = await callerBTask; + + // Caller B got the connection + Assert.NotNull(resultB); + Assert.Same(firstConnection, resultB); + } + + /// + /// Verifies that the the pool hands to the + /// connection factory reports a reduced remaining-time budget once the + /// timer's clock has advanced. This guarantees the factory observes the + /// actual remaining budget at the moment of the call rather than a + /// fresh, full timeout. + /// + /// + /// Drives elapsed time deterministically with a + /// so the test does not depend on real + /// wall-clock waits or thread sleeps. + /// + [Fact] + public void GetConnection_TimeoutTimerReflectsPoolWaitTime() + { + // Arrange: a capturing factory and a fake-time-backed timer with a + // 30-second budget anchored at virtual time t = 0. + var factory = new SuccessfulSqlConnectionFactory(); + var pool = ConstructPool(factory); + var owner = new SqlConnection("Timeout=30"); + var fakeTime = new FakeTimeProvider(); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(30), fakeTime); + + // Act: advance virtual time by 5 seconds before invoking the pool, + // simulating budget that was consumed elsewhere (e.g., waiting on a + // pool slot) before the factory was called. + fakeTime.Advance(TimeSpan.FromSeconds(5)); + pool.TryGetConnection(owner, taskCompletionSource: null, timer, out DbConnectionInternal? connection); + + // Assert: factory received the same timer, and it reports the + // reduced 25-second remaining budget. + Assert.NotNull(connection); + Assert.Same(timer, factory.CapturedTimeout); + Assert.Equal(25_000, factory.CapturedTimeout!.MillisecondsRemainingInt); + } + + #endregion } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs index 521ded14b8..a29abd6bbb 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs @@ -676,12 +676,12 @@ internal class MockDbConnectionPool : IDbConnectionPool public void Clear() => throw new NotImplementedException(); - public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, out DbConnectionInternal? connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource? taskCompletionSource, TimeoutTimer timeout, out DbConnectionInternal? connection) { throw new NotImplementedException(); } - public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionInternal oldConnection, TimeoutTimer timeout) { throw new NotImplementedException(); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBudgetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBudgetTest.cs new file mode 100644 index 0000000000..6c00f8e38e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBudgetTest.cs @@ -0,0 +1,232 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Microsoft.Data.SqlClient.Tests.Common; +using Microsoft.Extensions.Time.Testing; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool; + +/// +/// Verifies that propagates the +/// caller's overall budget through both the pool +/// wait and the physical connection-creation factory call, mirroring the +/// budget-propagation coverage already in place for +/// ChannelDbConnectionPool. +/// +public class WaitHandleDbConnectionPoolBudgetTest : IDisposable +{ + private const int DefaultMaxPoolSize = 50; + private const int DefaultMinPoolSize = 0; + private const int DefaultCreationTimeoutInMilliseconds = 15_000; + + private WaitHandleDbConnectionPool? _pool; + + public void Dispose() + { + _pool?.Shutdown(); + _pool?.Clear(); + } + + private WaitHandleDbConnectionPool CreatePool( + SqlConnectionFactory connectionFactory, + int maxPoolSize = DefaultMaxPoolSize, + int creationTimeoutMs = DefaultCreationTimeoutInMilliseconds) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: DefaultMinPoolSize, + maxPoolSize: maxPoolSize, + creationTimeout: creationTimeoutMs, + loadBalanceTimeout: 0, + hasTransactionAffinity: true); + + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + poolGroupOptions); + + var pool = new WaitHandleDbConnectionPool( + connectionFactory, + dbConnectionPoolGroup, + DbConnectionPoolIdentity.NoIdentity, + new DbConnectionPoolProviderInfo()); + + pool.Startup(); + _pool = pool; + return pool; + } + + /// + /// Verifies that the the pool hands to the + /// connection factory reports a reduced remaining-time budget when the + /// timer's clock has advanced before the pool was entered. Both the + /// synchronous (taskCompletionSource == null) and asynchronous + /// paths must forward the caller's already-advanced timer rather than + /// constructing a fresh one from CreationTimeout. Mirrors + /// ChannelDbConnectionPoolTest.GetConnection_TimeoutTimerReflectsPoolWaitTime. + /// + [Theory] + [InlineData(false)] // sync + [InlineData(true)] // async + public async Task GetConnection_TimeoutTimerReflectsTimeAlreadyConsumed(bool async) + { + // Arrange: capturing factory and a fake-time-backed timer with a + // 30-second budget anchored at virtual time t = 0. + var factory = new MockSqlConnectionFactory(); + var pool = CreatePool(factory); + var owner = new SqlConnection("Timeout=30"); + var fakeTime = new FakeTimeProvider(); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(30), fakeTime); + TaskCompletionSource? tcs = + async ? new TaskCompletionSource() : null; + + // Act: simulate 5s of budget consumed elsewhere (e.g., higher-level + // Open() work) before the pool is entered, then request a connection. + fakeTime.Advance(TimeSpan.FromSeconds(5)); + bool completed = pool.TryGetConnection( + owner, + tcs, + timer, + out DbConnectionInternal? connection); + + if (async) + { + // Bound the await so a regression in the pool can't hang the suite. + Task winner = await Task.WhenAny(tcs!.Task, Task.Delay(TimeSpan.FromSeconds(30))); + Assert.Same(tcs.Task, winner); + connection = await tcs.Task; + } + else + { + Assert.True(completed); + } + + // Assert: factory received the same timer, and it reports the reduced + // 25-second remaining budget rather than the original 30s or the + // pool's static 15s CreationTimeout. + Assert.NotNull(connection); + Assert.Same(timer, factory.CapturedTimeout); + Assert.Equal(25_000, factory.CapturedTimeout!.MillisecondsRemainingInt); + } + + /// + /// Identifies which kind of caller-supplied a + /// parameterized test should construct. Used because + /// cannot carry a live + /// instance. + /// + public enum TimerKind + { + Expired, + Infinite, + } + + /// + /// Verifies the resolution matrix for the synchronous + /// WaitHandle.WaitAny timeout: + /// + /// switch ON → use the caller timer's remaining budget + /// (expired → 0, infinite → ); + /// switch OFF → ignore the caller timer and use + /// CreationTimeout, treating 0 as + /// per legacy behavior. + /// + /// + [Theory] + [InlineData(true, TimerKind.Expired, 5_000, 0u)] + [InlineData(true, TimerKind.Infinite, 5_000, unchecked((uint)Timeout.Infinite))] + [InlineData(false, TimerKind.Expired, 1_500, 1_500u)] + [InlineData(false, TimerKind.Expired, 0, unchecked((uint)Timeout.Infinite))] + public void ResolvePoolWaitTimeoutMs_ReturnsExpected( + bool switchEnabled, + TimerKind timerKind, + int creationTimeoutMs, + uint expected) + { + // Arrange + using LocalAppContextSwitchesHelper switches = new() + { + UseOverallConnectTimeoutForPoolWait = switchEnabled, + }; + TimeoutTimer timer = timerKind switch + { + TimerKind.Expired => TimeoutTimer.StartExpired(), + TimerKind.Infinite => TimeoutTimer.StartNew(TimeSpan.Zero), + _ => throw new ArgumentOutOfRangeException(nameof(timerKind)), + }; + + // Act + uint result = WaitHandleDbConnectionPool.ResolvePoolWaitTimeoutMs( + timer, + creationTimeoutMs); + + // Assert + Assert.Equal(expected, result); + } + + /// + /// SqlConnectionFactory test double that captures the + /// handed to CreateConnection so tests + /// can assert the pool propagated the caller's budget rather than + /// constructing a fresh timer from CreationTimeout. + /// + internal sealed class MockSqlConnectionFactory : SqlConnectionFactory + { + internal TimeoutTimer? CapturedTimeout { get; private set; } + + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + CapturedTimeout = timeout; + return new MockDbConnectionInternal(); + } + } + + /// + /// Minimal stub. Mirrors the helper in + /// WaitHandleDbConnectionPoolTransactionTest but is duplicated + /// locally so this test file remains self-contained. + /// + internal sealed class MockDbConnectionInternal : DbConnectionInternal + { + public override string ServerVersion => "Mock"; + + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) + => throw new NotImplementedException(); + + public override void EnlistTransaction(Transaction? transaction) + { + if (transaction != null) + { + EnlistedTransaction = transaction; + } + } + + protected override void Activate(Transaction? transaction) + { + EnlistedTransaction = transaction; + } + + protected override void Deactivate() + { + } + + internal override void ResetConnection() + { + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs index a5e5d0339f..555de74b29 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs @@ -82,6 +82,7 @@ private DbConnectionInternal GetConnection(SqlConnection owner) _pool.TryGetConnection( owner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? connection); return connection!; } @@ -94,6 +95,7 @@ private async Task GetConnectionAsync( _pool.TryGetConnection( owner, taskCompletionSource: tcs, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? connection); return connection ?? await tcs.Task; } @@ -902,7 +904,8 @@ protected override DbConnectionInternal CreateConnection( ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, - DbConnection owningConnection) + DbConnection owningConnection, + TimeoutTimer timeout) { return new MockDbConnectionInternal(); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index 4501d710a9..e09e49c967 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -89,6 +89,14 @@ + + @@ -105,6 +113,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index 075cca0cb1..c5c6f7ec73 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -57,6 +57,7 @@ public void TestDefaultAppContextSwitchValues() Assert.True(LocalAppContextSwitches.UseCompatibilityProcessSni); Assert.True(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); + Assert.False(LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait); Assert.False(LocalAppContextSwitches.TruncateScaledDecimal); Assert.False(LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner); Assert.False(LocalAppContextSwitches.UseLegacyFailoverAlternationOnLoginSqlErrors); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionInternalTimeoutTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionInternalTimeoutTests.cs new file mode 100644 index 0000000000..acf6e27622 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionInternalTimeoutTests.cs @@ -0,0 +1,74 @@ +// 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.ProviderBase; +using Microsoft.Data.SqlClient.Connection; +using Microsoft.Data.SqlClient.Tests.Common; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests; + +/// +/// Verifies how selects the +/// that governs the login phase, driven by the +/// UseOverallConnectTimeoutForPoolWait AppContext switch. +/// +/// When the switch is enabled the connection must reuse the caller-supplied +/// timer as-is so the remaining budget (already reflecting any time spent +/// waiting for the pool) is honored during login. When disabled it must +/// construct a fresh timer from ConnectTimeout, preserving legacy +/// behavior. Asserting against the extracted +/// helper keeps this +/// branch coverage free of any real network connection. +/// +public class SqlConnectionInternalTimeoutTests +{ + /// + /// Verifies the branch selection in + /// : + /// + /// switch ON → the caller's instance + /// flows through unchanged (asserted by reference identity), so any + /// time already consumed counts against the overall ConnectTimeout; + /// switch OFF → a fresh timer is started from + /// ConnectTimeout (asserted by inspecting + /// ), preserving legacy + /// behavior where login always gets the full configured budget. + /// + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ResolveLoginTimeout_HonorsSwitch(bool switchEnabled) + { + // Arrange + using LocalAppContextSwitchesHelper switches = new() + { + UseOverallConnectTimeoutForPoolWait = switchEnabled, + }; + const int ConnectTimeoutSeconds = 30; + TimeoutTimer callerTimeout = TimeoutTimer.StartNew(TimeSpan.FromSeconds(ConnectTimeoutSeconds)); + + // Act + TimeoutTimer resolved = SqlConnectionInternal.ResolveLoginTimeout( + callerTimeout, + ConnectTimeoutSeconds); + + // Assert + if (switchEnabled) + { + // Switch on: caller's timer must flow through unchanged so any + // time already consumed counts against the overall budget. + Assert.Same(callerTimeout, resolved); + } + else + { + // Switch off (legacy): a fresh timer must be started from + // ConnectTimeout, independent of the caller's timer. + Assert.NotSame(callerTimeout, resolved); + Assert.Equal(TimeSpan.FromSeconds(ConnectTimeoutSeconds).Ticks, resolved.OriginalTicks); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ProviderBase/TimeoutTimerTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ProviderBase/TimeoutTimerTest.cs new file mode 100644 index 0000000000..5d98837806 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ProviderBase/TimeoutTimerTest.cs @@ -0,0 +1,513 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Data.Common; +using Microsoft.Data.ProviderBase; +using Microsoft.Extensions.Time.Testing; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ProviderBase +{ + /// + /// Verifies behavior: expiration evaluation, + /// remaining-time reporting, reset, infinite timers, and the cancellation + /// token source it produces. + /// + public class TimeoutTimerTest + { + /// + /// Verifies that flips from + /// to once the timer's + /// configured duration has elapsed (as measured by its + /// ), and that + /// reports zero in + /// the expired state. + /// + [Fact] + public void IsExpired_BecomesTrueAfterDuration() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(5), fake); + Assert.False(timer.IsExpired); + + // Act: advance virtual time past the expiration; no real time elapses. + fake.Advance(TimeSpan.FromSeconds(6)); + + // Assert + Assert.True(timer.IsExpired); + Assert.Equal(0, timer.MillisecondsRemainingInt); + } + + /// + /// Verifies that + /// counts down as virtual time advances, matching the original duration + /// minus the elapsed amount. + /// + [Fact] + public void MillisecondsRemaining_DecreasesAsTimeElapses() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(10), fake); + Assert.Equal(10_000, timer.MillisecondsRemainingInt); + + // Act + fake.Advance(TimeSpan.FromSeconds(3)); + + // Assert + Assert.Equal(7_000, timer.MillisecondsRemainingInt); + } + + /// + /// Verifies that restarts the countdown + /// from the original duration, discarding any time that had already + /// elapsed. + /// + [Fact] + public void Reset_RestoresOriginalDuration() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(5), fake); + fake.Advance(TimeSpan.FromSeconds(4)); + Assert.Equal(1_000, timer.MillisecondsRemainingInt); + + // Act + timer.Reset(); + + // Assert + Assert.Equal(5_000, timer.MillisecondsRemainingInt); + } + + /// + /// Verifies that the produced by + /// is wired to the + /// timer's rather than the system clock. + /// + /// + /// The CTS is constructed with a one-hour delay. If it were backed by real + /// time, the test could not complete within the runner's per-test timeout. + /// Because CreateCancellationTokenSource passes the timer's + /// to the CTS constructor, advancing the + /// by two virtual hours synchronously fires + /// the registered timer callback (queued to the thread pool by the fake + /// provider), which cancels the source. The test then polls briefly via + /// to absorb thread-pool dispatch latency + /// before asserting cancellation. A successful run completes in + /// milliseconds, proving cancellation is driven by virtual time and not + /// by wall-clock elapsed time. + /// + [Fact] + public async Task CreateCancellationTokenSource_FiresWhenTimerExpires() + { + // Arrange: use an hour-long timer; if the CTS were backed by real + // time the test would never complete in the runner's timeout. It + // only finishes promptly because the CTS is scheduled through the + // fake provider. + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromHours(1), fake); + using CancellationTokenSource cts = timer.CreateCancellationTokenSource(); + Assert.False(cts.IsCancellationRequested); + + // Act: advancing the fake provider past the expiration deadline must + // cause the CTS to fire deterministically; no real time passes. + fake.Advance(TimeSpan.FromHours(2)); + + // Assert: FakeTimeProvider schedules timer callbacks on the thread + // pool, so yield briefly to let the cancellation propagate before + // asserting. + await WaitForAsync(() => cts.IsCancellationRequested); + Assert.True(cts.IsCancellationRequested); + } + + /// + /// Verifies that requesting a from + /// a timer whose deadline has already passed returns a source that is + /// already canceled, rather than scheduling a new timer callback. + /// + [Fact] + public void CreateCancellationTokenSource_AlreadyExpired_ReturnsCanceledSource() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(1), fake); + fake.Advance(TimeSpan.FromSeconds(2)); + + // Act + using CancellationTokenSource cts = timer.CreateCancellationTokenSource(); + + // Assert + Assert.True(cts.IsCancellationRequested); + } + + /// + /// Verifies that an infinite timer (constructed from + /// ) produces a + /// that never auto-cancels, even + /// after a large amount of virtual time has elapsed. + /// + [Fact] + public void CreateCancellationTokenSource_InfiniteTimer_NeverCancels() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.Zero, fake); + using CancellationTokenSource cts = timer.CreateCancellationTokenSource(); + + // Act + fake.Advance(TimeSpan.FromHours(1)); + + // Assert + Assert.True(timer.IsInfinite); + Assert.False(cts.IsCancellationRequested); + } + + /// + /// Verifies that exposes the + /// exact instance supplied to + /// . + /// + [Fact] + public void TimeProvider_ReturnsProviderPassedToStartNew() + { + // Arrange + var fake = new FakeTimeProvider(); + + // Act + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(1), fake); + + // Assert + Assert.Same(fake, timer.TimeProvider); + } + + /// + /// Verifies that + /// returns a finite timer that is already expired, reports zero + /// remaining time, and uses the supplied . + /// + [Fact] + public void StartExpired_ReturnsFiniteAlreadyExpiredTimer() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + + // Act + TimeoutTimer timer = TimeoutTimer.StartExpired(fake); + + // Assert + Assert.False(timer.IsInfinite); + Assert.True(timer.IsExpired); + Assert.Equal(0, timer.MillisecondsRemainingInt); + Assert.Same(fake, timer.TimeProvider); + } + + /// + /// Verifies that the produced by + /// an expired timer is already canceled. + /// + [Fact] + public void StartExpired_CreateCancellationTokenSource_IsAlreadyCanceled() + { + // Arrange + TimeoutTimer timer = TimeoutTimer.StartExpired(new FakeTimeProvider(DateTimeOffset.UtcNow)); + + // Act + using CancellationTokenSource cts = timer.CreateCancellationTokenSource(); + + // Assert + Assert.True(cts.IsCancellationRequested); + } + + /// + /// Verifies that propagates the + /// parent's to the child so child timers see + /// the same virtual clock as their parent. + /// + [Fact] + public void StartChild_PropagatesParentTimeProvider() + { + // Arrange + var fake = new FakeTimeProvider(); + TimeoutTimer parent = TimeoutTimer.StartNew(TimeSpan.FromSeconds(30), fake); + + // Act + TimeoutTimer child = parent.StartChild(TimeSpan.FromSeconds(5)); + + // Assert + Assert.Same(fake, child.TimeProvider); + } + + /// + /// Verifies that caps the child's + /// duration at the parent's remaining time when the requested duration + /// would otherwise outlast the parent. + /// + [Fact] + public void StartChild_RequestedDurationLongerThanParent_IsCappedAtParentRemaining() + { + // Arrange: parent has 5 s remaining; caller asks for 30 s. + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer parent = TimeoutTimer.StartNew(TimeSpan.FromSeconds(5), fake); + + // Act + TimeoutTimer child = parent.StartChild(TimeSpan.FromSeconds(30)); + + // Assert: child remaining should match parent remaining (5 s). + Assert.Equal(parent.MillisecondsRemainingInt, child.MillisecondsRemainingInt); + Assert.False(child.IsInfinite); + } + + /// + /// Verifies that uses the requested + /// duration when it is shorter than the parent's remaining time. + /// + [Fact] + public void StartChild_RequestedDurationShorterThanParent_UsesRequested() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer parent = TimeoutTimer.StartNew(TimeSpan.FromSeconds(30), fake); + + // Act + TimeoutTimer child = parent.StartChild(TimeSpan.FromSeconds(5)); + + // Assert + Assert.Equal(5_000, child.MillisecondsRemainingInt); + Assert.False(child.IsInfinite); + } + + /// + /// Verifies that returns an + /// already-expired child when the parent has already expired. + /// + [Fact] + public void StartChild_ParentExpired_ReturnsAlreadyExpiredChild() + { + // Arrange: parent is already expired. + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer parent = TimeoutTimer.StartNew(TimeSpan.FromSeconds(1), fake); + fake.Advance(TimeSpan.FromSeconds(2)); + Assert.True(parent.IsExpired); + + // Act + TimeoutTimer child = parent.StartChild(TimeSpan.FromSeconds(30)); + + // Assert + Assert.False(child.IsInfinite); + Assert.True(child.IsExpired); + Assert.Equal(0, child.MillisecondsRemainingInt); + } + + /// + /// Verifies that with an infinite + /// parent honors the requested finite duration rather than producing + /// another infinite timer. + /// + [Fact] + public void StartChild_InfiniteParent_UsesRequestedDuration() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer parent = TimeoutTimer.StartNew(TimeSpan.Zero, fake); + Assert.True(parent.IsInfinite); + + // Act + TimeoutTimer child = parent.StartChild(TimeSpan.FromSeconds(5)); + + // Assert + Assert.False(child.IsInfinite); + Assert.Equal(5_000, child.MillisecondsRemainingInt); + } + + /// + /// Verifies that interprets + /// literally as "expire immediately" + /// rather than as the infinite-timeout sentinel, even when the parent + /// is infinite. + /// + [Fact] + public void StartChild_ZeroDuration_IsLiteralAndReturnsAlreadyExpiredChild() + { + // Arrange: an infinite parent so the only way Zero could become + // "infinite" would be via the sentinel; verify it does not. + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer parent = TimeoutTimer.StartNew(TimeSpan.Zero, fake); + Assert.True(parent.IsInfinite); + + // Act + TimeoutTimer child = parent.StartChild(TimeSpan.Zero); + + // Assert + Assert.False(child.IsInfinite); + Assert.True(child.IsExpired); + Assert.Equal(0, child.MillisecondsRemainingInt); + } + + // Polls the predicate on a short cadence so test runs aren't sensitive + // to thread-pool scheduling latency when FakeTimeProvider fires its + // registered timer callbacks. + private static async Task WaitForAsync(Func predicate) + { + for (int i = 0; i < 50; i++) + { + if (predicate()) + { + return; + } + await Task.Delay(20); + } + } + + /// + /// Verifies that the wall-clock reading the timer derives from + /// matches the legacy + /// reading. Both are expected to return + /// UTC "now" expressed in file-time ticks (100 ns since 1601-01-01 UTC), + /// so two back-to-back samples should differ by no more than a small + /// scheduling jitter. + /// + [Fact] + public void SystemTimeProvider_AgreesWithAdpTimerCurrent() + { + // 50 ms in file-time ticks. Generous enough to absorb GC pauses + // and CI jitter while still being far smaller than any meaningful + // timeout this class is used for. + const long ToleranceTicks = 50 * TimeSpan.TicksPerMillisecond; + + // Sample both clocks back-to-back, then bracket the TimeoutTimer + // reading between two ADP readings. + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(1)); + long adpBefore = ADP.TimerCurrent(); + long providerNow = timer.NowTicks(); + long adpAfter = ADP.TimerCurrent(); + + Assert.InRange(providerNow, adpBefore - ToleranceTicks, adpAfter + ToleranceTicks); + } + + /// + /// Verifies the same equivalence end-to-end: a timer started with + /// places its ExpirationTicks + /// at ADP.TimerCurrent() + duration within scheduling jitter. + /// This is the relationship legacy callers depend on when comparing + /// TimeoutTimer.ExpirationTicks against . + /// + [Fact] + public void StartNew_WithSystemTimeProvider_ExpirationMatchesAdpClock() + { + const long ToleranceTicks = 50 * TimeSpan.TicksPerMillisecond; + TimeSpan duration = TimeSpan.FromSeconds(30); + + long adpBefore = ADP.TimerCurrent(); + TimeoutTimer timer = TimeoutTimer.StartNew(duration); + long adpAfter = ADP.TimerCurrent(); + + Assert.InRange( + timer.ExpirationTicks, + adpBefore + duration.Ticks - ToleranceTicks, + adpAfter + duration.Ticks + ToleranceTicks); + } + + /// + /// Verifies that + /// (the variant) counts down as virtual time + /// elapses and reports the full original duration when no time has + /// passed yet. + /// + [Fact] + public void MillisecondsRemaining_Long_DecreasesAsTimeElapses() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(10), fake); + Assert.Equal(10_000L, timer.MillisecondsRemaining); + + // Act + fake.Advance(TimeSpan.FromSeconds(3)); + + // Assert + Assert.Equal(7_000L, timer.MillisecondsRemaining); + } + + /// + /// Verifies that + /// floors at zero (rather than going negative) once the timer's + /// deadline has passed. + /// + [Fact] + public void MillisecondsRemaining_Long_IsZeroAfterExpiration() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(1), fake); + + // Act + fake.Advance(TimeSpan.FromSeconds(5)); + + // Assert + Assert.True(timer.IsExpired); + Assert.Equal(0L, timer.MillisecondsRemaining); + } + + /// + /// Verifies that + /// reports for an infinite timer, even + /// after a large amount of virtual time has elapsed. + /// + [Fact] + public void MillisecondsRemaining_Long_InfiniteTimer_ReturnsMaxValue() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.Zero, fake); + Assert.True(timer.IsInfinite); + + // Act + fake.Advance(TimeSpan.FromHours(1)); + + // Assert + Assert.Equal(long.MaxValue, timer.MillisecondsRemaining); + } + + /// + /// Verifies that + /// reports for an infinite timer. + /// + [Fact] + public void MillisecondsRemainingInt_InfiniteTimer_ReturnsMaxValue() + { + // Arrange + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.Zero, fake); + Assert.True(timer.IsInfinite); + + // Assert + Assert.Equal(int.MaxValue, timer.MillisecondsRemainingInt); + } + + /// + /// Verifies that + /// saturates at when the remaining time + /// would otherwise overflow a 32-bit integer (anything beyond ~24.8 + /// days), while + /// reports the full value. + /// + [Fact] + public void MillisecondsRemainingInt_LargeDuration_SaturatesAtMaxValue() + { + // Arrange: a finite duration that exceeds int.MaxValue ms. + TimeSpan longDuration = TimeSpan.FromMilliseconds((long)int.MaxValue + 1_000L); + var fake = new FakeTimeProvider(DateTimeOffset.UtcNow); + TimeoutTimer timer = TimeoutTimer.StartNew(longDuration, fake); + + // Assert + Assert.False(timer.IsInfinite); + Assert.Equal(int.MaxValue, timer.MillisecondsRemainingInt); + Assert.Equal((long)longDuration.TotalMilliseconds, timer.MillisecondsRemaining); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index 18e12aa368..ea3e2d3da7 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -495,6 +495,7 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e Assert.Equal(0, failoverServer.PreLoginCount); } + [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] From 0521cdea3baf4a175e819922c4591eef49707b3d Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Wed, 3 Jun 2026 04:27:59 -0500 Subject: [PATCH 126/298] Test | Break Up SqlBulkCopyTest (#4076) * Break down SqlBulkCopyTest.cs and distribute test attributes to the test methods. * Replace calls to mirrors of DataTestUtility members with direct references to DataTestUtility. * Move bulk copy tests to the root of the project. * Correct namespaces for bulk copy tests * SqlBulkCopyTest.ConnectionString -> DataTestUtility.TCPConnectionString SqlBulkCopyTest.AddGuid -> DataTestUtility.GetShortName * Try bug84548 on azure server --- .../AdjustPrecScaleForBulkCopy.cs | 5 +- .../AzureDistributedTransaction.cs | 10 +- .../SqlBulkCopyTest => BulkCopy}/Bug84548.cs | 10 +- .../SqlBulkCopyTest => BulkCopy}/Bug85007.cs | 10 +- .../SqlBulkCopyTest => BulkCopy}/Bug903514.cs | 8 +- .../SqlBulkCopyTest => BulkCopy}/Bug98182.cs | 11 +- .../CacheMetadata.cs | 54 ++- .../CheckConstraints.cs | 10 +- .../ColumnCollation.cs | 9 +- .../CopyAllFromReader.cs | 10 +- .../CopyAllFromReader1.cs | 12 +- .../CopyAllFromReaderAsync.cs | 11 +- .../CopyAllFromReaderCancelAsync.cs | 10 +- .../CopyAllFromReaderConnectionCloseAsync.cs | 9 +- ...llFromReaderConnectionCloseOnEventAsync.cs | 10 +- .../CopyMultipleReaders.cs | 10 +- .../CopySomeFromDatatable.cs | 10 +- .../CopySomeFromDatatableAsync.cs | 9 +- .../CopySomeFromReader.cs | 10 +- .../CopySomeFromRowArray.cs | 10 +- .../CopySomeFromRowArrayAsync.cs | 9 +- .../CopyVariants.cs | 9 +- .../CopyWidenNullInexactNumerics.cs | 10 +- .../CopyWithEvent.cs | 10 +- .../CopyWithEvent1.cs | 11 +- .../CopyWithEventAsync.cs | 10 +- .../DataConversionErrorMessageTest.cs | 7 +- .../DestinationTableNameWithSpecialChar.cs | 9 +- .../ErrorOnRowsMarkedAsDeleted.cs | 10 +- .../FireTrigger.cs | 9 +- .../SqlBulkCopyTest => BulkCopy}/Helpers.cs | 3 +- .../HiddenTargetColumn.cs | 4 +- .../InvalidAccessFromEvent.cs | 10 +- .../SqlBulkCopyTest => BulkCopy}/KeepNulls.cs | 15 +- .../MissingTargetColumn.cs | 10 +- .../MissingTargetColumns.cs | 10 +- .../MissingTargetTable.cs | 9 +- .../SqlBulkCopyTest => BulkCopy}/OrderHint.cs | 13 +- .../OrderHintAsync.cs | 15 +- .../OrderHintDuplicateColumn.cs | 12 +- .../OrderHintIdentityColumn.cs | 13 +- .../OrderHintMissingTargetColumn.cs | 12 +- .../OrderHintTransaction.cs | 15 +- .../SpecialCharacterNames.cs | 12 +- .../SqlGraphTables.cs | 3 +- .../SqlBulkCopyTest => BulkCopy}/TableLock.cs | 15 +- .../TestBulkCopyWithUTF8.cs | 3 +- .../Transaction.cs | 10 +- .../Transaction1.cs | 10 +- .../Transaction2.cs | 9 +- .../Transaction3.cs | 10 +- .../Transaction4.cs | 10 +- .../TransactionTestAsync.cs | 9 +- .../WriteToServerTest.cs | 4 +- ...icrosoft.Data.SqlClient.ManualTests.csproj | 109 +++--- .../SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs | 366 ------------------ 56 files changed, 475 insertions(+), 568 deletions(-) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/AdjustPrecScaleForBulkCopy.cs (96%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/AzureDistributedTransaction.cs (84%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Bug84548.cs (87%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Bug85007.cs (91%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Bug903514.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Bug98182.cs (84%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CacheMetadata.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CheckConstraints.cs (84%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/ColumnCollation.cs (83%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyAllFromReader.cs (91%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyAllFromReader1.cs (80%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyAllFromReaderAsync.cs (84%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyAllFromReaderCancelAsync.cs (83%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyAllFromReaderConnectionCloseAsync.cs (82%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyAllFromReaderConnectionCloseOnEventAsync.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyMultipleReaders.cs (83%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopySomeFromDatatable.cs (84%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopySomeFromDatatableAsync.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopySomeFromReader.cs (79%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopySomeFromRowArray.cs (81%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopySomeFromRowArrayAsync.cs (85%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyVariants.cs (92%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyWidenNullInexactNumerics.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyWithEvent.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyWithEvent1.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/CopyWithEventAsync.cs (88%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/DataConversionErrorMessageTest.cs (97%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/DestinationTableNameWithSpecialChar.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/ErrorOnRowsMarkedAsDeleted.cs (93%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/FireTrigger.cs (85%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Helpers.cs (97%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/HiddenTargetColumn.cs (96%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/InvalidAccessFromEvent.cs (91%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/KeepNulls.cs (72%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/MissingTargetColumn.cs (79%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/MissingTargetColumns.cs (80%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/MissingTargetTable.cs (81%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/OrderHint.cs (92%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/OrderHintAsync.cs (89%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/OrderHintDuplicateColumn.cs (84%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/OrderHintIdentityColumn.cs (84%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/OrderHintMissingTargetColumn.cs (86%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/OrderHintTransaction.cs (78%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/SpecialCharacterNames.cs (80%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/SqlGraphTables.cs (98%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/TableLock.cs (72%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/TestBulkCopyWithUTF8.cs (98%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Transaction.cs (79%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Transaction1.cs (80%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Transaction2.cs (83%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Transaction3.cs (83%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/Transaction4.cs (78%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/TransactionTestAsync.cs (83%) rename src/Microsoft.Data.SqlClient/tests/ManualTests/{SQL/SqlBulkCopyTest => BulkCopy}/WriteToServerTest.cs (98%) delete mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AdjustPrecScaleForBulkCopy.cs similarity index 96% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AdjustPrecScaleForBulkCopy.cs index 49912202d6..6fdd82a6a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AdjustPrecScaleForBulkCopy.cs @@ -5,10 +5,11 @@ using System; using System.Data; using System.Data.SqlTypes; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Microsoft.Data.SqlClient.Tests.Common; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public static class AdjustPrecScaleForBulkCopy { @@ -29,7 +30,7 @@ public static void RunTest() Assert.Equal("12.3", value.ToString()); value = BulkCopySqlDecimalToTable(new SqlDecimal(123.45), 10, 2, 4, 1); - + bool? truncate = appContextSwitches.TruncateScaledDecimal; if (truncate.HasValue && truncate.Value) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AzureDistributedTransaction.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AzureDistributedTransaction.cs index 2a853d7ed4..e767d5e6eb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AzureDistributedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AzureDistributedTransaction.cs @@ -1,12 +1,13 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Transactions; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class AzureDistributedTransaction { @@ -15,8 +16,9 @@ public class AzureDistributedTransaction private static readonly string s_createTableCmd = $"CREATE TABLE {s_tableName} (NAME NVARCHAR(40), AGE INT)"; private static readonly string s_sqlBulkCopyCmd = "SELECT * FROM(VALUES ('Fuller', 33), ('Davon', 49)) AS q (FirstName, Age)"; private static readonly int s_commandTimeout = 30; - - public static void Test() + // Synapse: Promote Transaction not supported by Azure Synapse + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsAzureServer))] + public void Test() { try { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug84548.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs similarity index 87% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug84548.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs index 48c65a4247..02ac15a4c4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug84548.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs @@ -3,13 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Bug84548 { - public static void Test(string srcConstr, string dstConstr, string targettable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string targettable = DataTestUtility.GetShortName("SqlBulkCopyTest_Bug84548", false); string targetCustomerTable = targettable + "_customer"; using (SqlConnection dstConn = new SqlConnection(dstConstr)) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug85007.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs similarity index 91% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug85007.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs index 26d9db5bda..05eb812bbb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug85007.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs @@ -3,13 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Bug85007 { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Bug85007", false); string targetCustomerTable = dstTable + "_customer"; using (SqlConnection dstConn = new SqlConnection(dstConstr)) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug903514.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug903514.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs index c1a1f9824c..2e51711339 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug903514.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs @@ -4,14 +4,18 @@ using System; using System.Data; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Bug903514 { - public static void Test(string constr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string constr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Bug903514", false); using (SqlConnection dstConn = new SqlConnection(constr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug98182.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug98182.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs index c6e629b40a..49699a81b7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Bug98182.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs @@ -3,13 +3,18 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Bug98182 { - public static void Test(string constr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string constr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("@SqlBulkCopyTest_Bug98182 ", false); string srctable = "[" + dstTable + " src]"; dstTable = "[" + dstTable + "]"; @@ -47,7 +52,7 @@ public static void Test(string constr, string dstTable) ColumnMappings.Add("col 2", "[col 2]"); bulkcopy.WriteToServer(reader); - + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 1, "Unexpected number of rows."); } Helpers.VerifyResults(dstConn, dstTable, 2, 1); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CacheMetadata.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CacheMetadata.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs index 36f37fc2e7..15f591524d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CacheMetadata.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs @@ -5,9 +5,10 @@ using System; using System.Data; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CacheMetadata { @@ -16,8 +17,12 @@ public class CacheMetadata private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; // Test that CacheMetadata option works for multiple WriteToServer calls to the same table. - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadata", false); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); string initialQuery = string.Format(initialQueryTemplate, dstTable); @@ -76,8 +81,12 @@ public class CacheMetadataInvalidate private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; // Test that ClearCachedMetadata forces a fresh metadata query. - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataInvalidate", false); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); string initialQuery = string.Format(initialQueryTemplate, dstTable); @@ -128,8 +137,13 @@ public class CacheMetadataDestinationChange private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; // Test that changing DestinationTableName invalidates the cache and works correctly with a new table. - public static void Test(string srcConstr, string dstConstr, string dstTable1, string dstTable2) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable1 = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataDstChange0", false); + string dstTable2 = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataDstChange1", false); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); string initialQuery1 = string.Format(initialQueryTemplate, dstTable1); string initialQuery2 = string.Format(initialQueryTemplate, dstTable2); @@ -182,8 +196,12 @@ public class CacheMetadataWithoutFlag private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; // Test that without the CacheMetadata flag, multiple writes still work (no regression). - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataNoFlag", false); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); string initialQuery = string.Format(initialQueryTemplate, dstTable); @@ -230,8 +248,11 @@ public class CacheMetadataWithDataTable private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50), col3 nvarchar(50))"; // Test that CacheMetadata works with DataTable source as well as IDataReader. - public static void Test(string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataDT", false); string initialQuery = string.Format(initialQueryTemplate, dstTable); using DataTable sourceData = new(); @@ -275,8 +296,11 @@ public class CacheMetadataColumnMappingsChange // Test that changing ColumnMappings between WriteToServer calls works correctly with CacheMetadata. // The cached metadata describes the destination table schema, not the column mappings, // so modifying mappings between calls should work without cache invalidation. - public static void Test(string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataColMap", false); string initialQuery = string.Format(initialQueryTemplate, dstTable); using DataTable sourceData = new DataTable(); @@ -348,8 +372,11 @@ public class CacheMetadataColumnSubsetChange // second call, works correctly with CacheMetadata. This verifies that null-pruning of // unmatched columns in AnalyzeTargetAndCreateUpdateBulkCommand does not mutate the // cached metadata, which would cause a NullReferenceException on the second call. - public static void Test(string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataSubset", false); string initialQuery = string.Format(initialQueryTemplate, dstTable); using DataTable sourceData = new DataTable(); @@ -407,8 +434,12 @@ public class CacheMetadataAsync private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; // Test that CacheMetadata works correctly with WriteToServerAsync. - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataAsync", false); Task t = TestAsync(srcConstr, dstConstr, dstTable); t.Wait(); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); @@ -472,8 +503,11 @@ public class CacheMetadataCombinedWithKeepNulls private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50) default 'DefaultVal', col3 nvarchar(50))"; // Test that CacheMetadata works correctly when combined with other SqlBulkCopyOptions. - public static void Test(string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataKeepNulls", false); string initialQuery = string.Format(initialQueryTemplate, dstTable); using DataTable sourceData = new(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CheckConstraints.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CheckConstraints.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs index ed1a57ba91..f083cd28d1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CheckConstraints.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs @@ -3,13 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CheckConstraints { - public static void Test(string constr, string srctable, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string constr = DataTestUtility.TCPConnectionString; + string srctable = DataTestUtility.GetShortName("SqlBulkCopyTest_Extensionsrc", false); + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Extensiondst", false); using (SqlConnection dstConn = new SqlConnection(constr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/ColumnCollation.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs similarity index 83% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/ColumnCollation.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs index 5e39517391..df2ccc5f6b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/ColumnCollation.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs @@ -3,13 +3,18 @@ // See the LICENSE file in the project root for more information. using System.Data; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class ColumnCollation { - public static void Test(string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_ColumnCollation", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs similarity index 91% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs index 7dcb0a8111..1211d861d6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs @@ -5,9 +5,10 @@ using System.Collections; using System.Data.Common; using System.Diagnostics; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyAllFromReader { @@ -15,9 +16,12 @@ public class CopyAllFromReader private static readonly string sourceTable = "employees"; private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(20), col3 nvarchar(10))"; private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; - - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopyAllFromReader", false); Debug.Assert((int)SqlBulkCopyOptions.UseInternalTransaction == 1 << 5, "Compiler screwed up the options"); dstTable = destinationTable != null ? destinationTable : dstTable; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs similarity index 80% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader1.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs index 9dea10c9e4..1557217f31 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs @@ -3,13 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyAllFromReader1 { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopyAllFromReader1", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { @@ -34,7 +40,7 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) ColumnMappings.Add("FirstName", "col3"); bulkcopy.WriteToServer(reader); - + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 5, "Unexpected number of rows."); DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied64, (long)5, "Unexpected number of rows."); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs index cdeffa2e89..ab62f4843e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs @@ -5,14 +5,19 @@ using System.Data.Common; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyAllFromReaderAsync { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_AsyncTest1", false); // Use this semaphore to ensure that results are written to the log in the correct order SemaphoreSlim outputSemaphore = new SemaphoreSlim(0, 1); @@ -50,7 +55,7 @@ private static async Task TestAsync(string srcConstr, string dstConstr, string d await bulkcopy.WriteToServerAsync(reader); await outputSemaphore.WaitAsync(); - + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 5, "Unexpected number of rows."); DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied64, (long)5, "Unexpected number of rows."); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderCancelAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs similarity index 83% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderCancelAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs index 3fb3d051a1..acf93a4228 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderCancelAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs @@ -6,16 +6,20 @@ using System.Data.Common; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyAllFromReaderCancelAsync { static CancellationTokenSource cts = null; - - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_AsyncTest5", false); cts = new CancellationTokenSource(); cts.Cancel(); Task t = TestAsync(srcConstr, dstConstr, dstTable, cts.Token); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs similarity index 82% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs index ede16437d3..5cea2952cd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs @@ -5,14 +5,19 @@ using System; using System.Data.Common; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyAllFromReaderConnectionClosedAsync { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_AsyncTest6", false); Task t = TestAsync(srcConstr, dstConstr, dstTable); DataTestUtility.AssertThrowsInner(() => t.Wait()); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseOnEventAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseOnEventAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs index 3d832925a6..89b354c3b2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReaderConnectionCloseOnEventAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs @@ -5,13 +5,19 @@ using System; using System.Data.Common; using System.IO; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyAllFromReaderConnectionClosedOnEventAsync { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_AsyncTest7", false); #if DEBUG string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 varchar(8000))"; string sourceQuery = "select EmployeeID, LastName, FirstName, REPLICATE('a', 8000) from employees"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyMultipleReaders.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs similarity index 83% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyMultipleReaders.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs index a9c41818f3..f97020024a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyMultipleReaders.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs @@ -3,13 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyMultipleReaders { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopyMultipleReaders", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromDatatable.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromDatatable.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs index b0306fa278..f380dbcf41 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromDatatable.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs @@ -3,13 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopySomeFromDataTable { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopySomeFromDataTable", false); DataSet dataset; SqlDataAdapter adapter; DataTable datatable; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromDatatableAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromDatatableAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs index 0942ddb723..afac0b87b3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromDatatableAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs @@ -5,14 +5,19 @@ using System.Data; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopySomeFromDataTableAsync { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_AsyncTest3", false); // Use this semaphore to ensure that results are written to the log in the correct order SemaphoreSlim outputSemaphore = new SemaphoreSlim(0, 1); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs similarity index 79% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromReader.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs index a2b3c9f005..ada0ea4d1e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs @@ -3,13 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopySomeFromReader { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopySomeFromReader", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromRowArray.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs similarity index 81% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromRowArray.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs index 8093f9f3e1..5a3975ca22 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromRowArray.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs @@ -3,13 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopySomeFromRowArray { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopySomeFromRowArray", false); DataSet dataset; SqlDataAdapter adapter; DataTable datatable; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromRowArrayAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs similarity index 85% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromRowArrayAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs index de275989ff..20349dd153 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopySomeFromRowArrayAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs @@ -5,14 +5,19 @@ using System.Data; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopySomeFromRowArrayAsync { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_AsyncTest2", false); // Use this semaphore to ensure that results are written to the log in the correct order SemaphoreSlim outputSemaphore = new SemaphoreSlim(0, 1); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyVariants.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs similarity index 92% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyVariants.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs index 1670c097db..6126a50c1a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyVariants.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs @@ -3,13 +3,18 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyVariants { - public static void Test(string constr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string constr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Variants", false); string[] prologue = { "create table " + dstTable + "_src (col_1 int primary key, col_2 sql_variant)", diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWidenNullInexactNumerics.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWidenNullInexactNumerics.cs index f961521233..9fd56392c3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWidenNullInexactNumerics.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWidenNullInexactNumerics.cs @@ -1,17 +1,21 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyWidenNullInexactNumerics { - public static void Test(string sourceDatabaseConnectionString, string destinationDatabaseConnectionString) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string sourceDatabaseConnectionString = DataTestUtility.TCPConnectionString; + string destinationDatabaseConnectionString = DataTestUtility.TCPConnectionString; string sourceTableName = DataTestUtility.GetLongName("BCP_SRC"); string destTableName = DataTestUtility.GetLongName("BCP_DST"); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs index 284b82e8c5..00aae3c5a9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs @@ -3,9 +3,10 @@ // See the LICENSE file in the project root for more information. using System.Data; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyWithEvent { @@ -17,9 +18,12 @@ protected static void OnRowCopied(object sender, SqlRowsCopiedEventArgs e) Assert.True(currentRowCopyResult < ExpectedRowCopiedResults.Length, "More row copies than expected!"); DataTestUtility.AssertEqualsWithDescription(ExpectedRowCopiedResults[currentRowCopyResult++], e.RowsCopied, "Unexpected Rows Copied count."); } - - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopyWithEvent", false); DataSet dataset; SqlDataAdapter adapter; DataTable datatable; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent1.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs index 8bc72435d0..60d4dc5774 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEvent1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs @@ -3,14 +3,14 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyWithEvent1 { private static SqlBulkCopy bulkcopy; - private static readonly long[] ExpectedRowCopiedResults = { 50, 100 }; private static int currentRowCopyResult = 0; @@ -32,9 +32,12 @@ private static void OnRowCopied(object sender, SqlRowsCopiedEventArgs e) bulkcopy.NotifyAfter = 10; // decrease notification frequency } } - - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopyWithEvent1", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEventAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs similarity index 88% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEventAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs index 60e3ee0583..24cad97bf0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyWithEventAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs @@ -5,9 +5,10 @@ using System.Data; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class CopyWithEventAsync { @@ -19,9 +20,12 @@ private static void OnRowCopied(object sender, SqlRowsCopiedEventArgs e) Assert.True(currentRowCopyResult < ExpectedRowCopiedResults.Length, "More row copies than expected!"); DataTestUtility.AssertEqualsWithDescription(ExpectedRowCopiedResults[currentRowCopyResult++], e.RowsCopied, "Unexpected Rows Copied count."); } - - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_AsyncTest4", false); // Use this semaphore to ensure that results are written to the log in the correct order SemaphoreSlim outputSemaphore = new SemaphoreSlim(0, 1); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs index 4a722dd409..bb9af9d2fd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DataConversionErrorMessageTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs @@ -5,9 +5,10 @@ using System; using System.Data; using System.Text; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { internal enum ColumnsEnum { @@ -101,8 +102,8 @@ public DataConversionErrorMessageTest(InitialDatabase fixture) _fixture = fixture; } - // Synapse: Column count in target table does not match column count specified in input. - // If BCP command, ensure format file column count matches destination table. + // Synapse: Column count in target table does not match column count specified in input. + // If BCP command, ensure format file column count matches destination table. // If SSIS data import, check column mappings are consistent with target. [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public void StringToIntErrorMessageTest() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DestinationTableNameWithSpecialChar.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DestinationTableNameWithSpecialChar.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs index ce0cff6ae0..301f272190 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/DestinationTableNameWithSpecialChar.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs @@ -3,13 +3,18 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class DestinationTableNameWithSpecialChar { - public static void Test(string constr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void Test() { + string constr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_DestinationTableNameWithSpecialChar", false); string srctable = "[" + dstTable + "src]"; string dsttable = "@" + dstTable; // a tablename that cannot be created without brackets (e.g., @sometablename) string[] dsttablecombo = diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/ErrorOnRowsMarkedAsDeleted.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs similarity index 93% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/ErrorOnRowsMarkedAsDeleted.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs index 85e08190ef..d52c62947e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/ErrorOnRowsMarkedAsDeleted.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs @@ -5,8 +5,10 @@ using System; using System.Data; using System.Text; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { /// /// Test case for SqlBulkEdit raising exception on rows marked as deleted @@ -25,9 +27,11 @@ enum SqlBulkCopyInputType DataTable, RowsList } - - public static void Test(string dstConstr, string targettable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string dstConstr = DataTestUtility.TCPConnectionString; + string targettable = DataTestUtility.GetShortName("SqlBulkCopyTest_ErrorOnRowsMarkedAsDeleted", false); using (SqlConnection destConn = new SqlConnection(dstConstr)) { destConn.Open(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/FireTrigger.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs similarity index 85% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/FireTrigger.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs index a57217cb09..321cd5b738 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/FireTrigger.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs @@ -3,14 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class FireTrigger { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_FireTrigger", false); string dstTable1 = dstTable + "_1"; // this table will receive a value if the trigger fires! string trigger = dstTable + "_2"; string[] prologue = diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Helpers.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Helpers.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Helpers.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Helpers.cs index e28a1d02b5..274750bc09 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Helpers.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Helpers.cs @@ -4,8 +4,9 @@ using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Helpers { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/HiddenTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs similarity index 96% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/HiddenTargetColumn.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs index 1322120b55..50c56dbd02 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/HiddenTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs @@ -2,11 +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. -using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SqlBulkCopyTests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class HiddenTargetColumn { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/InvalidAccessFromEvent.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs similarity index 91% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/InvalidAccessFromEvent.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs index c22039c2de..bcc3a50471 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/InvalidAccessFromEvent.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs @@ -4,9 +4,10 @@ using System; using System.Data; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class InvalidAccessFromEvent { @@ -47,9 +48,12 @@ protected static void OnRowCopiedBulkCopy(object sender, SqlRowsCopiedEventArgs { InnerTest(null); } - - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_InvalidAccessFromEvent", false); _dstConstr = dstConstr; _dstTable = dstTable; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/KeepNulls.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs similarity index 72% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/KeepNulls.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs index 0c054dd1f4..7067cf5d7b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/KeepNulls.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs @@ -1,15 +1,22 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { - class KeepNulls + public class KeepNulls { - public static void Test(string srcconstr, string dstconstr, string srctable, string dsttable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcconstr = DataTestUtility.TCPConnectionString; + string dstconstr = DataTestUtility.TCPConnectionString; + string srctable = DataTestUtility.GetShortName("SqlBulkCopyTest_KeepNulls0", false); + string dsttable = DataTestUtility.GetShortName("SqlBulkCopyTest_KeepNulls1", false); using SqlConnection destConn = new(dstconstr); destConn.Open(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs similarity index 79% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs index e4df366137..c3e0f62789 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs @@ -4,13 +4,19 @@ using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class MissingTargetColumn { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_MissingTargetColumn", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs similarity index 80% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs index 5fb47f44c1..9265263b5d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs @@ -4,13 +4,19 @@ using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class MissingTargetColumns { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_MissingTargetColumns", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetTable.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs similarity index 81% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetTable.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs index 0592f2cb09..a440771203 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetTable.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs @@ -4,14 +4,19 @@ using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class MissingTargetTable { - public static void Test(string srcConstr, string dstConstr, string targetTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string targetTable = DataTestUtility.GetShortName("@SqlBulkCopyTest_MissingTargetTable", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHint.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHint.cs similarity index 92% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHint.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHint.cs index fbfbb57ecd..77512a4fdd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHint.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHint.cs @@ -1,13 +1,14 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class OrderHint { @@ -18,9 +19,13 @@ public class OrderHint private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}"; private static readonly string sourceQueryTemplate2 = "SELECT LastName, FirstName FROM {0}"; private static readonly string getRowCountQueryTemplate = "SELECT COUNT(*) FROM {0}"; - - public static void Test(string connStr, string dstTable, string dstTable2) + // TODO Synapse: Remove dependency on Northwind database + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public void Test() { + string connStr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_OrderHint", false); + string dstTable2 = DataTestUtility.GetShortName("SqlBulkCopyTest_OrderHint2", false); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); string sourceQuery2 = string.Format(sourceQueryTemplate2, sourceTable2); string initialQuery = string.Format(initialQueryTemplate, dstTable); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintAsync.cs similarity index 89% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintAsync.cs index c818200864..7b84097c1f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintAsync.cs @@ -1,14 +1,14 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class OrderHintAsync { @@ -19,9 +19,14 @@ public class OrderHintAsync private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}"; private static readonly string sourceQueryTemplate2 = "SELECT LastName, FirstName FROM {0}"; private static readonly string getRowCountQueryTemplate = "SELECT COUNT(*) FROM {0}"; - - public static void Test(string srcConstr, string dstTable, string dstTable2) + // Synapse: Cannot create more than one clustered index on table ''. + // Drop the existing clustered index 'ClusteredIndex_fe3d8c967ac142468ec4f81ff1faaa50' before creating another. + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_OrderHintAsync", false); + string dstTable2 = DataTestUtility.GetShortName("SqlBulkCopyTest_OrderHintAsync2", false); Task t = TestAsync(srcConstr, dstTable, dstTable2); t.Wait(); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintDuplicateColumn.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintDuplicateColumn.cs index ebed5e9918..0b15f4e063 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintDuplicateColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintDuplicateColumn.cs @@ -1,12 +1,13 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class OrderHintDuplicateColumn { @@ -14,9 +15,12 @@ public class OrderHintDuplicateColumn private static readonly string initialQueryTemplate = "create table {0} (CustomerID nvarchar(50), CompanyName nvarchar(50), ContactName nvarchar(50))"; private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}"; private static readonly string getRowCountQueryTemplate = "SELECT COUNT(*) FROM {0}"; - - public static void Test(string connStr, string dstTable) + // Synapse: Remove dependency on Northwind database. + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public void Test() { + string connStr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_OrderHintDuplicateColumn", false); string initialQuery = string.Format(initialQueryTemplate, dstTable); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); string getRowCountQuery = string.Format(getRowCountQueryTemplate, sourceTable); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintIdentityColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintIdentityColumn.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintIdentityColumn.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintIdentityColumn.cs index 04cd1977b4..4bc75496ee 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintIdentityColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintIdentityColumn.cs @@ -1,11 +1,13 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class OrderHintIdentityColumn { @@ -13,9 +15,12 @@ public class OrderHintIdentityColumn private static readonly string initialQueryTemplate = "create table {0} (CustomerID int IDENTITY NOT NULL, CompanyName nvarchar(50), ContactName nvarchar(50))"; private static readonly string sourceQueryTemplate = "SELECT CustomerId, CompanyName, ContactName FROM {0}"; private static readonly string getRowCountQueryTemplate = "SELECT COUNT(*) FROM {0}"; - - public static void Test(string connStr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ActiveIssue("12219")] + public void Test() { + string connStr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_OrderHintIdentityColumn", false); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); string initialQuery = string.Format(initialQueryTemplate, dstTable); string getRowCountQuery = string.Format(getRowCountQueryTemplate, sourceTable); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintMissingTargetColumn.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintMissingTargetColumn.cs index cc486e9e4e..2245453bc4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintMissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintMissingTargetColumn.cs @@ -1,21 +1,25 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class OrderHintMissingTargetColumn { private static readonly string sourceTable = "Customers"; private static readonly string initialQueryTemplate = "create table {0} (CustomerID nvarchar(50), CompanyName nvarchar(50), ContactName nvarchar(50))"; private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}"; - - public static void Test(string connStr, string dstTable) + // Synapse: Remove dependency on Northwind database. + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public void Test() { + string connStr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_OrderHintMissingTargetColumn", false); string initialQuery = string.Format(initialQueryTemplate, dstTable); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintTransaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs similarity index 78% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintTransaction.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs index 94bd28b02a..30a17024f9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/OrderHintTransaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs @@ -1,19 +1,24 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { - class OrderHintTransaction + public class OrderHintTransaction { private static readonly string sourceTable = "Customers"; private static readonly string initialQueryTemplate = "create table {0} (CustomerID nvarchar(50), CompanyName nvarchar(50), ContactName nvarchar(50))"; private static readonly string sourceQueryTemplate = "SELECT CustomerID, CompanyName, ContactName FROM {0}"; - - public static void Test(string connStr, string dstTable) + // Synapse: 111212;Operation cannot be performed within a transaction. + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public void Test() { + string connStr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_OrderHintTransaction", false); string initialQuery = string.Format(initialQueryTemplate, dstTable); string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SpecialCharacterNames.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs similarity index 80% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SpecialCharacterNames.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs index 96c50225a0..1f57f32f19 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SpecialCharacterNames.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs @@ -2,7 +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. -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class SpecialCharacterNames { @@ -10,9 +13,12 @@ private static string EscapeIdentifier(string name) { return "[" + name.Replace("]", "]]") + "]"; } - - public static void Test(string srcConstr, string dstConstr, string dstPrefix) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstPrefix = DataTestUtility.GetShortName("@SqlBulkCopyTest_SpecialCharacterNames", false); // create schema and table names with special characters, with ] character escaped. string dstschema = dstPrefix + "_Schema'-]['']"; dstschema = EscapeIdentifier(dstschema); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SqlGraphTables.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SqlGraphTables.cs index a5b702f6b4..0c0ec9aa1a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlGraphTables.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SqlGraphTables.cs @@ -3,10 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Data; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SqlBulkCopyTests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class SqlGraphTables { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TableLock.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs similarity index 72% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TableLock.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs index c39ffcc01e..9bddfcea17 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TableLock.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs @@ -1,15 +1,22 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { - class TableLock + public class TableLock { - public static void Test(string srcconstr, string dstconstr, string srctable, string dsttable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcconstr = DataTestUtility.TCPConnectionString; + string dstconstr = DataTestUtility.TCPConnectionString; + string srctable = DataTestUtility.GetShortName("SqlBulkCopyTest_TableLock0", false); + string dsttable = DataTestUtility.GetShortName("SqlBulkCopyTest_TableLock1", false); using SqlConnection destConn = new(dstconstr); destConn.Open(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TestBulkCopyWithUTF8.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TestBulkCopyWithUTF8.cs index da5287e831..db3ea66f94 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TestBulkCopyWithUTF8.cs @@ -5,9 +5,10 @@ using System; using System.Data; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { /// /// Validates SqlBulkCopy functionality when working with UTF-8 encoded data. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs similarity index 79% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs index 0738f32010..6fe82ab11e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs @@ -4,13 +4,19 @@ using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Transaction { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Transaction0", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs similarity index 80% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction1.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs index 930e1ac6f5..c60a76a5f7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs @@ -4,13 +4,19 @@ using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Transaction1 { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Transaction1", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs similarity index 83% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction2.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs index 7b315afcc4..3e28ab5d72 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction2.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs @@ -3,14 +3,19 @@ // See the LICENSE file in the project root for more information. using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Transaction2 { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Transaction2", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction3.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs similarity index 83% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction3.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs index 4b65f2a5d2..0b3bd0b999 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction3.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs @@ -4,13 +4,19 @@ using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Transaction3 { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Transaction3", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction4.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs similarity index 78% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction4.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs index 40b2d2c251..9d219187d8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/Transaction4.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs @@ -4,13 +4,19 @@ using System; using System.Data.Common; +using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Transaction4 { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_Transaction4", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TransactionTestAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs similarity index 83% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TransactionTestAsync.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs index 729dac74a9..11a1049292 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TransactionTestAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs @@ -5,14 +5,19 @@ using System; using System.Data.Common; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class TransactionTestAsync { - public static void Test(string srcConstr, string dstConstr, string dstTable) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void Test() { + string srcConstr = DataTestUtility.TCPConnectionString; + string dstConstr = DataTestUtility.TCPConnectionString; + string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_TransactionTestAsync", false); Task t = TestAsync(srcConstr, dstConstr, dstTable); DataTestUtility.AssertThrowsInner(() => t.Wait()); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/WriteToServerTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/WriteToServerTest.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/WriteToServerTest.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/WriteToServerTest.cs index 20f63a2e0a..871aaf3495 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/WriteToServerTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/WriteToServerTest.cs @@ -2,12 +2,12 @@ // 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 Microsoft.Data.SqlClient.ManualTesting.Tests; using Xunit; -namespace Microsoft.Data.SqlClient.ManualTesting.Tests +namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class WriteToServerTest { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 1145fa4790..83dd4a8351 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -121,61 +121,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs deleted file mode 100644 index 90a4e5cbf3..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs +++ /dev/null @@ -1,366 +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 Xunit; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests -{ - public class SqlBulkCopyTest - { - private string _connStr = null; - private static bool IsAzureServer() => !DataTestUtility.IsNotAzureServer(); - private static bool IsNotAzureSynapse => DataTestUtility.IsNotAzureSynapse(); - private static bool AreConnectionStringsSetup() => DataTestUtility.AreConnStringsSetup(); - - public SqlBulkCopyTest() - { - _connStr = DataTestUtility.TCPConnectionString; - } - - public string AddGuid(string stringin) - { - stringin += "_" + Guid.NewGuid().ToString().Replace('-', '_'); - return stringin; - } - - // Synapse: Promote Transaction not supported by Azure Synapse - [ConditionalFact(nameof(AreConnectionStringsSetup), nameof(IsNotAzureSynapse), nameof(IsAzureServer))] - public void AzureDistributedTransactionTest() - { - AzureDistributedTransaction.Test(); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyAllFromReaderTest() - { - CopyAllFromReader.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CopyAllFromReader")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyAllFromReader1Test() - { - CopyAllFromReader1.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CopyAllFromReader1")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyMultipleReadersTest() - { - CopyMultipleReaders.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CopyMultipleReaders")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopySomeFromReaderTest() - { - CopySomeFromReader.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CopySomeFromReader")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopySomeFromDataTableTest() - { - CopySomeFromDataTable.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CopySomeFromDataTable")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopySomeFromRowArrayTest() - { - CopySomeFromRowArray.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CopySomeFromRowArray")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyWithEventTest() - { - CopyWithEvent.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CopyWithEvent")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyWithEvent1Test() - { - CopyWithEvent1.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CopyWithEvent1")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void InvalidAccessFromEventTest() - { - InvalidAccessFromEvent.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_InvalidAccessFromEvent")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void Bug84548Test() - { - Bug84548.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_Bug84548")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void MissingTargetTableTest() - { - MissingTargetTable.Test(_connStr, _connStr, AddGuid("@SqlBulkCopyTest_MissingTargetTable")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - 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() - { - Bug85007.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_Bug85007")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CheckConstraintsTest() - { - CheckConstraints.Test(_connStr, AddGuid("SqlBulkCopyTest_Extensionsrc"), AddGuid("SqlBulkCopyTest_Extensiondst")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void TableLockTest() - { - TableLock.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_TableLock0"), AddGuid("SqlBulkCopyTest_TableLock1")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void KeepNullsTest() - { - KeepNulls.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_KeepNulls0"), AddGuid("SqlBulkCopyTest_KeepNulls1")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void TransactionTest() - { - Transaction.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_Transaction0")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void Transaction1Test() - { - Transaction1.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_Transaction1")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void Transaction2Test() - { - Transaction2.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_Transaction2")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void Transaction3Test() - { - Transaction3.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_Transaction3")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void Transaction4Test() - { - Transaction4.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_Transaction4")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyVariantsTest() - { - CopyVariants.Test(_connStr, AddGuid("SqlBulkCopyTest_Variants")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void Bug98182Test() - { - Bug98182.Test(_connStr, AddGuid("@SqlBulkCopyTest_Bug98182 ")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void FireTriggerTest() - { - FireTrigger.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_FireTrigger")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void ErrorOnRowsMarkedAsDeletedTest() - { - ErrorOnRowsMarkedAsDeleted.Test(_connStr, AddGuid("SqlBulkCopyTest_ErrorOnRowsMarkedAsDeleted")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void SpecialCharacterNamesTest() - { - SpecialCharacterNames.Test(_connStr, _connStr, AddGuid("@SqlBulkCopyTest_SpecialCharacterNames")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void Bug903514Test() - { - Bug903514.Test(_connStr, AddGuid("SqlBulkCopyTest_Bug903514")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void ColumnCollationTest() - { - ColumnCollation.Test(_connStr, AddGuid("SqlBulkCopyTest_ColumnCollation")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyAllFromReaderAsyncTest() - { - CopyAllFromReaderAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_AsyncTest1")); //Async + Reader - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopySomeFromRowArrayAsyncTest() - { - CopySomeFromRowArrayAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_AsyncTest2")); //Async + Some Rows - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopySomeFromDataTableAsyncTest() - { - CopySomeFromDataTableAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_AsyncTest3")); //Async + Some Table - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyWithEventAsyncTest() - { - CopyWithEventAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_AsyncTest4")); //Async + Rows + Notification - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void CopyAllFromReaderCancelAsyncTest() - { - CopyAllFromReaderCancelAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_AsyncTest5")); //Async + Reader + cancellation token - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void CopyAllFromReaderConnectionClosedAsyncTest() - { - CopyAllFromReaderConnectionClosedAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_AsyncTest6")); //Async + Reader + Connection closed - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void CopyAllFromReaderConnectionClosedOnEventAsyncTest() - { - CopyAllFromReaderConnectionClosedOnEventAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_AsyncTest7")); //Async + Reader + Connection closed during the event - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void TransactionTestAsyncTest() - { - TransactionTestAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_TransactionTestAsync")); //Async + Transaction rollback - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CopyWidenNullInexactNumericsTest() - { - CopyWidenNullInexactNumerics.Test(_connStr, _connStr); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void DestinationTableNameWithSpecialCharTest() - { - DestinationTableNameWithSpecialChar.Test(_connStr, AddGuid("SqlBulkCopyTest_DestinationTableNameWithSpecialChar")); - } - - // TODO Synapse: Remove dependency on Northwind database - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void OrderHintTest() - { - OrderHint.Test(_connStr, AddGuid("SqlBulkCopyTest_OrderHint"), AddGuid("SqlBulkCopyTest_OrderHint2")); - } - - // Synapse: Cannot create more than one clustered index on table ''. - // Drop the existing clustered index 'ClusteredIndex_fe3d8c967ac142468ec4f81ff1faaa50' before creating another. - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void OrderHintAsyncTest() - { - OrderHintAsync.Test(_connStr, AddGuid("SqlBulkCopyTest_OrderHintAsync"), AddGuid("SqlBulkCopyTest_OrderHintAsync2")); - } - - // Synapse: Remove dependency on Northwind database. - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void OrderHintMissingTargetColumnTest() - { - OrderHintMissingTargetColumn.Test(_connStr, AddGuid("SqlBulkCopyTest_OrderHintMissingTargetColumn")); - } - - // Synapse: Remove dependency on Northwind database. - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void OrderHintDuplicateColumnTest() - { - OrderHintDuplicateColumn.Test(_connStr, AddGuid("SqlBulkCopyTest_OrderHintDuplicateColumn")); - } - - // Synapse: 111212;Operation cannot be performed within a transaction. - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void OrderHintTransactionTest() - { - OrderHintTransaction.Test(_connStr, AddGuid("SqlBulkCopyTest_OrderHintTransaction")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [ActiveIssue("12219")] - public void OrderHintIdentityColumnTest() - { - OrderHintIdentityColumn.Test(_connStr, AddGuid("SqlBulkCopyTest_OrderHintIdentityColumn")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataTest() - { - CacheMetadata.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CacheMetadata")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataInvalidateTest() - { - CacheMetadataInvalidate.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CacheMetadataInvalidate")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataDestinationChangeTest() - { - CacheMetadataDestinationChange.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CacheMetadataDstChange0"), AddGuid("SqlBulkCopyTest_CacheMetadataDstChange1")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataWithoutFlagTest() - { - CacheMetadataWithoutFlag.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CacheMetadataNoFlag")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataWithDataTableTest() - { - CacheMetadataWithDataTable.Test(_connStr, AddGuid("SqlBulkCopyTest_CacheMetadataDT")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataColumnMappingsChangeTest() - { - CacheMetadataColumnMappingsChange.Test(_connStr, AddGuid("SqlBulkCopyTest_CacheMetadataColMap")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataColumnSubsetChangeTest() - { - CacheMetadataColumnSubsetChange.Test(_connStr, AddGuid("SqlBulkCopyTest_CacheMetadataSubset")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataCombinedWithKeepNullsTest() - { - CacheMetadataCombinedWithKeepNulls.Test(_connStr, AddGuid("SqlBulkCopyTest_CacheMetadataKeepNulls")); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] - public void CacheMetadataAsyncTest() - { - CacheMetadataAsync.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_CacheMetadataAsync")); - } - } -} From f880e408375d0b4aa9526e26ee6f33c5d933ad40 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Wed, 3 Jun 2026 15:51:46 -0500 Subject: [PATCH 127/298] PR Pipeline - Build/Pack and Local Tests (#4326) * Move install-dotnet to common/steps, first bits of pipeline # Conflicts: # eng/pipelines/common/steps/restore-dotnet-tools.yml * Adding build and pack steps and writing up the build/pack stage. Leaving the unused build step for now just in case we need it later * Move common variables to common folder. Move OneBranch specific variables to OneBranch variables file * Rename prci-pipeline to pr-pipeline * First round of tests * Run build/pack regardless of previous package. * Run tree and publish artifact regardless of previous success * Completely untested, let's see what it does. * Rename build-and-pack-stage.yml * tree on linux * Fix test framework * Switch images to ones that have sql server on it Move a couple things around WIP for manual test configuration generation * :robot: Implement config file generation for manual test * Fix build name * WIP * WIP * Add secret generation stage * Run pack-stage and generate-secrets-stage in parallel * More WIP to wire up manual test config * :robot: Finish wiring up what exists today. Time to try it out and see what's broken. * Fine, :robot:, you were right. Adding back the operating system parameters * Fix link to linux config step * :robot: remove dependency on extutilities * :robot: Get connection string for setting up northwind from config * :robot: Only swap out the initial catalog field. * :robot: vs :robot: :fight: * Sync config.cs and config.json * Cleanup powershells * Config steps * :hammer: * Diagnostic * Stupid powershell. * Making demands. * Fixing the first round of manual tests. * Increase timeout on sql notification test... not ideal but if it helps it helps. * Add retries to dotnet install steps * Cleanup and add explicit disposal logic to AlwaysEncrypted.ApiShould.TestExecuteReaderAsyncWithLargeQuery * Temporarily mark SqlNotificationTest.Test_SingleDependency_AllDefaults_SqlAuth as flay * Comments for pipeline, stages, jobs, variables. Moving the SA password retrieval deeper into the manual test job * Fix SA password issue. * :hammer: * Exclude AE ApiShould tests on debug builds. * Address copilot feedback round 1 of `n` * Config.json changes applied to pipelines. * Restore tools in all instances before running jobs. * Getting really tired of these changes * wouldn't it be nice if we had something to verify this before pushing a bunch of tiny little changes? * Hope it can handle extra spaces. * Fix path to dotnet arm64 installation script * Remove reference to ADO Test Configuration Properties variable group. * Parameterize the pool * It's not a pipeline parameter you stupid :robot: * :rage: * INVERSION * Fix kerberos pipeline install-dotnet.yml path --- .../steps/install-dotnet-arm64.ps1 | 0 .../{ => common}/steps/install-dotnet.yml | 3 +- .../steps/restore-dotnet-tools.yml | 0 .../templates/jobs/ci-build-nugets-job.yml | 4 +- .../templates/jobs/ci-code-coverage-job.yml | 2 +- .../templates/jobs/ci-run-tests-job.yml | 12 +- .../common/templates/steps/pre-build-step.yml | 2 +- .../steps/update-config-file-step.yml | 11 - .../steps/verify-nuget-package-step.yml | 2 +- .../variables/common-variables.yml | 16 +- .../jobs/pack-abstractions-package-ci-job.yml | 2 +- .../jobs/pack-azure-package-ci-job.yml | 2 +- .../jobs/pack-logging-package-ci-job.yml | 2 +- .../jobs/pack-sqlserver-package-ci-job.yml | 2 +- .../jobs/test-abstractions-package-ci-job.yml | 2 +- .../jobs/test-azure-package-ci-job.yml | 2 +- eng/pipelines/kerberos/sqlclient-kerberos.yml | 8 +- .../onebranch/jobs/build-buildproj-job.yml | 4 +- .../onebranch/sqlclient-non-official.yml | 2 +- .../onebranch/sqlclient-official.yml | 2 +- .../onebranch/steps/build-buildproj-step.yml | 4 + .../variables/onebranch-variables.yml | 19 +- eng/pipelines/pr/jobs/test-buildproj-job.yml | 89 ++++++++ .../pr/jobs/test-sqlclientmanual-job.yml | 160 ++++++++++++++ eng/pipelines/pr/pr-pipeline.yml | 77 +++++++ .../pr/stages/generate-secrets-stage.yml | 69 ++++++ eng/pipelines/pr/stages/pack-stage.yml | 128 ++++++++++++ eng/pipelines/pr/stages/test-stages.yml | 197 ++++++++++++++++++ .../pr/steps/build-buildproj-step.yml | 46 ++++ .../steps/configure-sqlserver-linux-step.yml | 127 +++++++++++ .../pr/steps/configure-sqlserver-step.yml | 30 +++ .../configure-sqlserver-windows-step.yml | 115 ++++++++++ eng/pipelines/pr/steps/install-dotnet.yml | 27 +++ .../pr/steps/pack-buildproj-step.yml | 59 ++++++ .../pr/steps/test-buildproj-step.yml | 59 ++++++ eng/pipelines/pr/variables/pr-variables.yml | 30 +++ eng/pipelines/stress/stress-tests-job.yml | 2 +- .../tests/Common/SqlDataReaderExtensions.cs | 28 +++ .../ManualTests/AlwaysEncrypted/ApiShould.cs | 73 ++++--- .../ManualTests/DataCommon/DataTestUtility.cs | 14 ++ .../SQL/InstanceNameTest/InstanceNameTest.cs | 12 +- .../SQL/SqlCommand/SqlCommandCancelTest.cs | 2 + .../SqlNotificationTest.cs | 3 +- .../Config.cs | 11 +- .../config.default.json | 7 +- 45 files changed, 1360 insertions(+), 108 deletions(-) rename eng/pipelines/{ => common}/steps/install-dotnet-arm64.ps1 (100%) rename eng/pipelines/{ => common}/steps/install-dotnet.yml (97%) rename eng/pipelines/{ => common}/steps/restore-dotnet-tools.yml (100%) rename eng/pipelines/{onebranch => common}/variables/common-variables.yml (54%) create mode 100644 eng/pipelines/pr/jobs/test-buildproj-job.yml create mode 100644 eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml create mode 100644 eng/pipelines/pr/pr-pipeline.yml create mode 100644 eng/pipelines/pr/stages/generate-secrets-stage.yml create mode 100644 eng/pipelines/pr/stages/pack-stage.yml create mode 100644 eng/pipelines/pr/stages/test-stages.yml create mode 100644 eng/pipelines/pr/steps/build-buildproj-step.yml create mode 100644 eng/pipelines/pr/steps/configure-sqlserver-linux-step.yml create mode 100644 eng/pipelines/pr/steps/configure-sqlserver-step.yml create mode 100644 eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml create mode 100644 eng/pipelines/pr/steps/install-dotnet.yml create mode 100644 eng/pipelines/pr/steps/pack-buildproj-step.yml create mode 100644 eng/pipelines/pr/steps/test-buildproj-step.yml create mode 100644 eng/pipelines/pr/variables/pr-variables.yml diff --git a/eng/pipelines/steps/install-dotnet-arm64.ps1 b/eng/pipelines/common/steps/install-dotnet-arm64.ps1 similarity index 100% rename from eng/pipelines/steps/install-dotnet-arm64.ps1 rename to eng/pipelines/common/steps/install-dotnet-arm64.ps1 diff --git a/eng/pipelines/steps/install-dotnet.yml b/eng/pipelines/common/steps/install-dotnet.yml similarity index 97% rename from eng/pipelines/steps/install-dotnet.yml rename to eng/pipelines/common/steps/install-dotnet.yml index 5774984663..6ce99316b2 100644 --- a/eng/pipelines/steps/install-dotnet.yml +++ b/eng/pipelines/common/steps/install-dotnet.yml @@ -69,6 +69,7 @@ steps: env: PROCESSOR_ARCHITECTURE: x86 + # Install the desired Runtimes, if any. - ${{ each version in parameters.runtimes }}: - task: UseDotNet@2 @@ -96,7 +97,7 @@ steps: inputs: targetType: filePath pwsh: true - filePath: $(Build.SourcesDirectory)/eng/pipelines/steps/install-dotnet-arm64.ps1 + filePath: $(Build.SourcesDirectory)/eng/pipelines/common/steps/install-dotnet-arm64.ps1 # Arguments: # # -Debug: diff --git a/eng/pipelines/steps/restore-dotnet-tools.yml b/eng/pipelines/common/steps/restore-dotnet-tools.yml similarity index 100% rename from eng/pipelines/steps/restore-dotnet-tools.yml rename to eng/pipelines/common/steps/restore-dotnet-tools.yml diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index de9b4d284f..aa6a1c50c5 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -132,10 +132,10 @@ jobs: targetPath: $(localFeedPath) # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self # Restore dotnet CLI tools (e.g. pwsh, apicompat) before building. - - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self # When we're performing a Debug build, we still want to try _compiling_ the # code in Release mode to ensure downstream pipelines don't encounter 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 09c72e9719..95c0ac0f3c 100644 --- a/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml @@ -44,7 +44,7 @@ jobs: displayName: '[Debug] List Environment Variables' # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} 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 4c42af8dc1..5eed840236 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -30,11 +30,9 @@ parameters: # template expansion. # # EnclaveEnabled - # IsAzureSynapse # IsDNSCachingSupportedCR # IsDNSCachingSupportedTR # ManagedIdentitySupported - # SupportsFileStream # SupportsIntegratedSecurity # TracingEnabled # @@ -211,7 +209,7 @@ jobs: targetPath: $(Build.SourcesDirectory)/packages # Install the .NET SDK and Runtimes. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} ${{ if parameters.isArm64 }}: @@ -223,7 +221,7 @@ jobs: runtimes: [8.x, 9.x] # Restore dotnet CLI tools (e.g. pwsh, apicompat) before building. - - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self - ${{ if ne(parameters.prebuildSteps, '') }}: - ${{ parameters.prebuildSteps }} # extra steps to run before the build like downloading sni and the required configuration @@ -279,8 +277,6 @@ jobs: AliasName: ${{ parameters.configProperties.AliasName }} ${{ if parameters.configProperties.SupportsIntegratedSecurity }}: SupportsIntegratedSecurity: ${{ eq(parameters.configProperties.SupportsIntegratedSecurity, 'true') }} - ${{ if parameters.configProperties.SupportsFileStream }}: - SupportsFileStream: ${{ eq(parameters.configProperties.SupportsFileStream, 'true') }} ${{ if parameters.configProperties.DNSCachingConnString }}: DNSCachingConnString: ${{ parameters.configProperties.DNSCachingConnString }} ${{ if parameters.configProperties.DNSCachingServerCR }}: @@ -293,8 +289,6 @@ jobs: IsDNSCachingSupportedCR: ${{ eq(parameters.configProperties.IsDNSCachingSupportedCR, 'true') }} ${{ if parameters.configProperties.IsDNSCachingSupportedTR }}: IsDNSCachingSupportedTR: ${{ eq(parameters.configProperties.IsDNSCachingSupportedTR, 'true') }} - ${{ if parameters.configProperties.IsAzureSynapse }}: - IsAzureSynapse: ${{ eq(parameters.configProperties.IsAzureSynapse, 'true') }} ${{ if parameters.configProperties.ManagedIdentitySupported }}: ManagedIdentitySupported: ${{ eq(parameters.configProperties.ManagedIdentitySupported, 'true') }} @@ -352,7 +346,7 @@ jobs: - ${{ if and(eq(parameters.enableX86Test, true), eq(parameters.operatingSystem, 'Windows')) }}: - ${{ if ne(variables['dotnetx86RootPath'], '') }}: # Install the .NET SDK and Runtimes for x86. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: architecture: x86 debug: ${{ parameters.debug }} diff --git a/eng/pipelines/common/templates/steps/pre-build-step.yml b/eng/pipelines/common/templates/steps/pre-build-step.yml index 068223af70..1d57d18296 100644 --- a/eng/pipelines/common/templates/steps/pre-build-step.yml +++ b/eng/pipelines/common/templates/steps/pre-build-step.yml @@ -5,7 +5,7 @@ ################################################################################# steps: # Install the .NET SDK and Runtimes. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: runtimes: [8.x, 9.x] 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 b10abdc617..b0612fa248 100644 --- a/eng/pipelines/common/templates/steps/update-config-file-step.yml +++ b/eng/pipelines/common/templates/steps/update-config-file-step.yml @@ -93,10 +93,6 @@ parameters: type: boolean default: false - - name: SupportsFileStream - type: boolean - default: false - - name: DNSCachingConnString type: string default: '' @@ -121,10 +117,6 @@ parameters: type: boolean default: false - - name: IsAzureSynapse - type: boolean - default: false - - name: ManagedIdentitySupported type: boolean default: true @@ -188,8 +180,6 @@ steps: $p.DNSCachingConnString="${{parameters.DNSCachingConnString }}" - $p.SupportsFileStream="${{parameters.SupportsFileStream }}" - $p.LocalDbAppName="${{parameters.LocalDbAppName }}" $p.TCPConnectionStringAASSGX="${{parameters.TCPConnectionStringAASSGX }}" @@ -201,7 +191,6 @@ steps: $p.UseManagedSNIOnWindows=[System.Convert]::ToBoolean("${{parameters.UseManagedSNIOnWindows }}") $p.SupportsIntegratedSecurity=[System.Convert]::ToBoolean("${{parameters.SupportsIntegratedSecurity }}") $p.ManagedIdentitySupported=[System.Convert]::ToBoolean("${{parameters.ManagedIdentitySupported }}") - $p.IsAzureSynapse=[System.Convert]::ToBoolean("${{parameters.IsAzureSynapse }}") $p.IsDNSCachingSupportedTR=[System.Convert]::ToBoolean("${{parameters.IsDNSCachingSupportedTR }}") $p.IsDNSCachingSupportedCR=[System.Convert]::ToBoolean("${{parameters.IsDNSCachingSupportedCR }}") $p.TracingEnabled=[System.Convert]::ToBoolean("${{parameters.TracingEnabled }}") diff --git a/eng/pipelines/common/templates/steps/verify-nuget-package-step.yml b/eng/pipelines/common/templates/steps/verify-nuget-package-step.yml index 6c74c01988..604f1b7894 100644 --- a/eng/pipelines/common/templates/steps/verify-nuget-package-step.yml +++ b/eng/pipelines/common/templates/steps/verify-nuget-package-step.yml @@ -34,7 +34,7 @@ parameters: steps: # Install the .NET SDK, required by the PowerShell script. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self # Invoke the script with the path to the packages to verify. - task: PowerShell@2 diff --git a/eng/pipelines/onebranch/variables/common-variables.yml b/eng/pipelines/common/variables/common-variables.yml similarity index 54% rename from eng/pipelines/onebranch/variables/common-variables.yml rename to eng/pipelines/common/variables/common-variables.yml index 5b6209d59d..5f4532778d 100644 --- a/eng/pipelines/onebranch/variables/common-variables.yml +++ b/eng/pipelines/common/variables/common-variables.yml @@ -4,11 +4,9 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# Common variables shared across all OneBranch Official pipelines. +# Common variables shared across all pipelines. variables: - - name: CommitHead - value: '' # the value will be extracted from the repo's head ########################################################################## # "Well-Known" Variables that are ok to use directly, anywhere in the pipeline. @@ -21,15 +19,3 @@ variables: # ). - name: BUILD_OUTPUT value: $(REPO_ROOT)/artifacts - - # Directory where downloaded pipeline artifacts (NuGet packages from earlier - # stages) are placed. Build jobs use this as a local NuGet package source so - # that downstream packages can resolve dependencies on packages built by - # upstream stages. - - name: JOB_INPUT - value: $(REPO_ROOT)/packages - - # Root directory for all job output artifacts (NuGet packages, symbols, etc.). OneBranch auto - # publishes everything under this directory as the artifact for the job. - - name: JOB_OUTPUT - value: $(REPO_ROOT)/output diff --git a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml index aed7672618..a9cb5b1043 100644 --- a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml @@ -103,7 +103,7 @@ jobs: displayName: '[Debug] Print Environment Variables' # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index 3b03dc0adc..eaf0e3c66f 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -146,7 +146,7 @@ jobs: targetPath: $(Build.SourcesDirectory)/packages # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} diff --git a/eng/pipelines/jobs/pack-logging-package-ci-job.yml b/eng/pipelines/jobs/pack-logging-package-ci-job.yml index dd748cb4b2..f2a42699e0 100644 --- a/eng/pipelines/jobs/pack-logging-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-logging-package-ci-job.yml @@ -94,7 +94,7 @@ jobs: displayName: '[Debug] Print Environment Variables' # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} diff --git a/eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml b/eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml index 915bf1e9bd..6b19e8c154 100644 --- a/eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-sqlserver-package-ci-job.yml @@ -90,7 +90,7 @@ jobs: displayName: '[Debug] Print Environment Variables' # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml index 226dc1ae2a..83366b52a9 100644 --- a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -122,7 +122,7 @@ jobs: displayName: '[Debug] Print Environment Variables' # Install the .NET SDK and Runtimes. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} runtimes: [8.x, 9.x] diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 9c70433e33..42097daa42 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -243,7 +243,7 @@ jobs: targetPath: $(Build.SourcesDirectory)/packages # Install the .NET SDK and Runtimes. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} runtimes: [8.x, 9.x] diff --git a/eng/pipelines/kerberos/sqlclient-kerberos.yml b/eng/pipelines/kerberos/sqlclient-kerberos.yml index af1b04266f..f43ee0a280 100644 --- a/eng/pipelines/kerberos/sqlclient-kerberos.yml +++ b/eng/pipelines/kerberos/sqlclient-kerberos.yml @@ -127,13 +127,13 @@ stages: fetchDepth: 1 fetchTags: false - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: runtimes: [8.x, 9.x] # Restore dotnet local tools (pwsh, apicompat, etc.). Required by build.proj targets # such as _CheckPwshToolRestored that run during the SqlClient ref project build. - - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self # --- Update test configuration --- # Uses runtime variables from the matrix ($(managedSNI)) so we use @@ -224,13 +224,13 @@ stages: fetchTags: false # --- Install .NET SDK and runtimes --- - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: runtimes: [8.x, 9.x] # Restore dotnet local tools (pwsh, apicompat, etc.). Required by build.proj targets # such as _CheckPwshToolRestored that run during the SqlClient ref project build. - - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self # --- Update test configuration (with Kerberos credentials) --- - pwsh: | diff --git a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml index 34c775ad5c..51c70e8c64 100644 --- a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml +++ b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml @@ -128,11 +128,11 @@ jobs: targetPath: $(REPO_ROOT)/packages # Install the .NET SDK. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self # Restore dotnet local tools (pwsh, apicompat, etc.). Required by build.proj targets # such as _CheckPwshToolRestored that run during RoslynAnalyzers and Build. - - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self # Perform Roslyn analysis before building, since this step will clobber build output. - template: /eng/pipelines/onebranch/steps/roslyn-analyzers-buildproj-step.yml@self diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index 584bd050f9..abf9b6dcbe 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -90,7 +90,7 @@ parameters: default: false variables: - - template: /eng/pipelines/onebranch/variables/common-variables.yml@self + - template: /eng/pipelines/common/variables/common-variables.yml@self - template: /eng/pipelines/onebranch/variables/onebranch-variables.yml@self - template: /eng/pipelines/onebranch/variables/package-variables.yml@self parameters: diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index 07b8da1b17..8ea429bb00 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -112,7 +112,7 @@ parameters: default: false variables: - - template: /eng/pipelines/onebranch/variables/common-variables.yml@self + - template: /eng/pipelines/common/variables/common-variables.yml@self - template: /eng/pipelines/onebranch/variables/onebranch-variables.yml@self - template: /eng/pipelines/onebranch/variables/package-variables.yml@self parameters: diff --git a/eng/pipelines/onebranch/steps/build-buildproj-step.yml b/eng/pipelines/onebranch/steps/build-buildproj-step.yml index 080896ee10..2a5730c848 100644 --- a/eng/pipelines/onebranch/steps/build-buildproj-step.yml +++ b/eng/pipelines/onebranch/steps/build-buildproj-step.yml @@ -6,6 +6,10 @@ # This collection of steps to build a project via the build.proj. This will execute the "Build*" # target in build.proj, where * is the packageShortName provided in the parameters. +# +# Note: This differs from the pr/ci build-buildproj-step.yml in that it always strong-name signs +# the assemblies, it only builds in package reference mode, and as such allows for version +# parameters to be provided. parameters: # Build configuration - Release or Debug. diff --git a/eng/pipelines/onebranch/variables/onebranch-variables.yml b/eng/pipelines/onebranch/variables/onebranch-variables.yml index 5a36dbbe75..7d940c1cb7 100644 --- a/eng/pipelines/onebranch/variables/onebranch-variables.yml +++ b/eng/pipelines/onebranch/variables/onebranch-variables.yml @@ -32,8 +32,25 @@ variables: # SymbolsUploadAccount - group: 'symbols-variables-v3' + # Well-Known Variables ################################################### + + # Directory where downloaded pipeline artifacts (NuGet packages from earlier + # stages) are placed. Build jobs use this as a local NuGet package source so + # that downstream packages can resolve dependencies on packages built by + # upstream stages. + - name: JOB_INPUT + value: $(REPO_ROOT)/packages + + # Root directory for all job output artifacts (NuGet packages, symbols, etc.). OneBranch auto + # publishes everything under this directory as the artifact for the job. + - name: JOB_OUTPUT + value: $(REPO_ROOT)/output + # OneBranch Template Variables ########################################### + - name: CommitHead + value: '' # the value will be extracted from the repo's head + # https://aka.ms/obpipelines/sdl - name: ob_sdl_binskim_break value: true @@ -42,7 +59,7 @@ variables: value: true # OneBranch supplies a variety of container images we must use for our jobs. - # + # Windows jobs use this image. - name: WindowsContainerImage value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest diff --git a/eng/pipelines/pr/jobs/test-buildproj-job.yml b/eng/pipelines/pr/jobs/test-buildproj-job.yml new file mode 100644 index 0000000000..0b051bf50e --- /dev/null +++ b/eng/pipelines/pr/jobs/test-buildproj-job.yml @@ -0,0 +1,89 @@ +################################################################################# +# 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: + # General Parameters ===================================================== + + # Configuration with which to build the packages and execute the tests. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Suffix to append to the package version to indicate it is a pre-release/development build. + - name: buildSuffix + type: string + + # Platform Parameters ==================================================== + + # Display name of the platform. This will be formatted into the job's official name as well as + # the display name for the job. As such, only alphanumeric and _ characters are permitted. + - name: platformDisplayName + type: string + + # TFM name of the version of dotnet to run this test with. + - name: platformDotnet + type: string + + # Name of the image in the customized ADO pool to use to execute this job. + - name: platformImage + type: string + + # Name of the pool to use for jobs that require customized VM images. + - name: poolName + type: string + + # Test Parameters ======================================================== + + # Name of the project to run tests for. This will be used to generate the build.proj target to + # execute: Test{packageShortName}{testProject} + - name: packageShortName + type: string + values: + - Azure + - AkvProvider + - Abstractions + - Logging + - SqlClient + - SqlServer + + # Display name of the test target that is being executed. This will be formatted into the job's + # official name as well as the display name for the job. As such, only alphanumeric and _ + # characters are permitted. + - name: testDisplayName + type: string + + # Optional test project name to execute. This only applies to packages that have >1 test target. + # eg, for TestSqlClientManual, this should be "Manual" + - name: testProject + type: string + default: '' + +jobs: + - job: "test_${{ parameters.platformDisplayName }}_${{ parameters.testDisplayName }}" + displayName: "${{ parameters.testDisplayName }}_${{ parameters.platformDisplayName }}" + + pool: + name: ${{ parameters.poolName }} + demands: + - imageOverride -equals ${{ parameters.platformImage }} + + steps: + # Install the version of the dotnet runtime we will use to execute the test target. + - template: /eng/pipelines/pr/steps/install-dotnet.yml@self + parameters: + runtimeVersion: ${{ parameters.platformDotnet }} + + # Execute the test target + - template: /eng/pipelines/pr/steps/test-buildproj-step.yml + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + + packageShortName: ${{ parameters.packageShortName }} + testFramework: ${{ parameters.platformDotnet }} + testProject: ${{ parameters.testProject }} diff --git a/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml b/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml new file mode 100644 index 0000000000..ff81d983a3 --- /dev/null +++ b/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml @@ -0,0 +1,160 @@ +################################################################################# +# 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: + # Configuration with which to build the packages and execute the tests. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Suffix to append to the package version to indicate it is a pre-release/development build. + - name: buildSuffix + type: string + + # Name of the stage that generated the SA password + - name: stageNameSecrets + type: string + + # Platform Parameters ==================================================== + + # Display name of the platform. This will be formatted into the job's official name as well as + # the display name for the job. As such, only alphanumeric and _ characters are permitted. + - name: platformDisplayName + type: string + + # TFM name of the version of dotnet to run this test with. + - name: platformDotnet + type: string + + # Name of the image in the customized ADO pool to use to execute this job. + - name: platformImage + type: string + + # General name of the operating system that will be used to execute the job. + # Note: This is used at compile time to determine which SQL Server setup steps to use. As such, + # this cannot be replaced with the agent's operating system, as this value is only available + # at runtime. + - name: platformOperatingSystem + type: string + values: + - Windows + - Linux + + # Name of the pool to use for jobs that require customized VM images. + - name: poolName + type: string + + # Test Configuration Parameters ========================================== + + # Azure Key Vault tenant ID that will be set in the config.json file. + - name: azureKeyVaultTenantId + type: string + + # Azure Key Vault URL that will be set in the config.json file. + - name: azureKeyVaultUrl + type: string + + # Connection string using named pipes that will be set in the config.json file. + - name: connectionStringNp + type: string + + # Connection string using TCP that will be set in the config.json file. + - name: connectionStringTcp + type: string + + # Path to use for file stream tests, this value will be stored in the config.json file. + - name: fileStreamDirectory + type: string + + # Name of the local DB application name for localdb tests, this value will be stored in the + # config.json + - name: localDbAppName + type: string + + # Name of the local DB shared instance name for localdb tests, this value will be stored in the + # config.json + - name: localDbSharedInstanceName + type: string + +jobs: + - job: "test_${{ parameters.platformDisplayName }}_sqlclient_manual" + displayName: "sqlclient_manual_${{ parameters.platformDisplayName }}" + + pool: + name: ${{ parameters.poolName }} + demands: + - imageOverride -equals ${{ parameters.platformImage }} + + variables: + # Bring the generated SA password from the secrets stage into scope for this test stage. + # Note: Because this is set at runtime, it must be referenced as `$(saPassword)`. + - name: saPassword + value: $[stageDependencies.${{ parameters.stageNameSecrets }}.secrets_job.outputs['SaPassword.Value']] + + steps: + # Install the version of the dotnet runtime we will use to execute the test target. + - template: /eng/pipelines/pr/steps/install-dotnet.yml@self + parameters: + runtimeVersion: ${{ parameters.platformDotnet }} + + # Configure the local SQL Server instance + - template: /eng/pipelines/pr/steps/configure-sqlserver-step.yml@self + parameters: + fileStreamDirectory: ${{ parameters.fileStreamDirectory }} + operatingSystem: ${{ parameters.platformOperatingSystem }} + saPassword: $(saPassword) + + # Assign the generated SA password to the $Password field. This will allow $(Password) to be + # be replaced in connection strings with whatever set in the secrets stage. + - pwsh: | + $password = "$(saPassword)" + Write-Host "##vso[task.setvariable variable=Password;isSecret=true]$password" + displayName: Set Connection String Password + + # Write configuration information to the config.json file. + - pwsh: | + # Read existing default configuration from file + $defaultConfigPath = "$(REPO_ROOT)/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json" + $configDirectory = "$(Agent.TempDirectory)/mds-config" + $configPath = Join-Path $configDirectory "config.json" + $config = Get-Content -Raw $defaultConfigPath | ConvertFrom-Json + + # Update the config values + $config.TCPConnectionString = "${{ parameters.connectionStringTcp }}" + $config.NPConnectionString = "${{ parameters.connectionStringNp }}" + $config.AzureKeyVaultURL = "${{ parameters.azureKeyVaultUrl }}" + $config.AzureKeyVaultTenantId = "${{ parameters.azureKeyVaultTenantId }}" + $config.LocalDbAppName = "${{ parameters.localDbAppName }}" + $config.LocalDbSharedInstanceName = "${{ parameters.localDbSharedInstanceName }}" + $config.ManagedIdentitySupported = $true + $config.SupportsIntegratedSecurity = $false + + if ("${{ parameters.platformOperatingSystem }}" -eq "Windows") { + $config.FileStreamDirectory = "${{ parameters.fileStreamDirectory }}" + } else { + $config.FileStreamDirectory = "" + } + + # Write config object to config file + New-Item -ItemType Directory -Force -Path $configDirectory | Out-Null + $config | ConvertTo-Json | Set-Content $configPath + + # Set generated config file path to environment variable + Write-Host "##vso[task.setvariable variable=TEST_MDS_CONFIG]$configPath" + displayName: 'Generate manual test config' + + # Execute TestSqlClientManual target from build.proj + - template: /eng/pipelines/pr/steps/test-buildproj-step.yml + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + + packageShortName: "SqlClient" + testFramework: ${{ parameters.platformDotnet }} + testProject: "Manual" + diff --git a/eng/pipelines/pr/pr-pipeline.yml b/eng/pipelines/pr/pr-pipeline.yml new file mode 100644 index 0000000000..9774ebdbfa --- /dev/null +++ b/eng/pipelines/pr/pr-pipeline.yml @@ -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. # +################################################################################# + +name: $(DayOfYear)$(Rev:rr) + +variables: + - template: /eng/pipelines/common/variables/common-variables.yml@self + - template: /eng/pipelines/pr/variables/pr-variables.yml@self + +stages: + # Stage 1a: Build and pack all projects in the repository + - template: /eng/pipelines/pr/stages/pack-stage.yml + parameters: + buildConfiguration: Debug + buildSuffix: pr + stageName: ${{ variables.stageNamePack }} + + # Stage 1b: Generate secrets + - template: /eng/pipelines/pr/stages/generate-secrets-stage.yml + parameters: + stageName: ${{ variables.stageNameSecrets }} + + # Stage 2: Execute tests and collect code coverage + - template: /eng/pipelines/pr/stages/test-stages.yml + parameters: + buildConfiguration: Debug + buildSuffix: pr + poolName: $(PoolNameDefault) + stageNamePack: ${{ variables.stageNamePack }} + stageNameSecrets: ${{ variables.stageNameSecrets }} + + platforms: + - displayName: "windows_net462" + dotnet: "net462" + image: "ADO-MMS22-SQL22" + operatingSystem: "Windows" + - displayName: "windows_net8" + dotnet: "net8.0" + image: "ADO-MMS22-SQL22" + operatingSystem: "Windows" + - displayName: "windows_net9" + dotnet: "net9.0" + image: "ADO-MMS22-SQL22" + operatingSystem: "Windows" + - displayName: "windows_net10" + dotnet: "net10.0" + image: "ADO-MMS22-SQL22" + operatingSystem: "Windows" + + - displayName: "linux_net8" + dotnet: "net8.0" + image: "ADO-UB22-SQL22" + operatingSystem: "Linux" + - displayName: "linux_net9" + dotnet: "net9.0" + image: "ADO-UB22-SQL22" + operatingSystem: "Linux" + - displayName: "linux_net10" + dotnet: "net10.0" + image: "ADO-UB22-SQL22" + operatingSystem: "Linux" + + manualTestAzureKeyVaultUrl: $(AzureKeyVaultUrl) + manualTestAzureKeyVaultTenantId: $(AzureKeyVaultTenantId) + manualTestConnectionStringNpLocalhost: $(ConnectionStringNp_LocalhostDefault_UsernamePassword) + manualTestConnectionStringTcpLocalhost: $(ConnectionStringTcp_LocalhostDefault_UsernamePassword) + manualTestConnectionStringNpAzure: $(ConnectionStringNp_Azure_ManagedIdentity) + manualTestConnectionStringTcpAzure: $(ConnectionStringTcp_Azure_ManagedIdentity) + manualTestFileStreamDirectory: "$(Pipeline.Workspace)/filestream" + manualTestLocalDbAppName: $(LocalDbAppName) + manualTestLocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + + # Stage 3: Collect code coverage + # @TODO: diff --git a/eng/pipelines/pr/stages/generate-secrets-stage.yml b/eng/pipelines/pr/stages/generate-secrets-stage.yml new file mode 100644 index 0000000000..20a81fe626 --- /dev/null +++ b/eng/pipelines/pr/stages/generate-secrets-stage.yml @@ -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. +#################################################################################################### + +# This stage generates the following random secrets for use throughout the PR and CI pipelines: +# SaPassword - A random GUID suitable for use as the SA password of local SQL Server instances. +# +# Subsequent stages may reference these variables as: +# $[stageDependencies.${{ stageName }}.secrets_job.outputs['SaPassword.Value']] +# +# For further details on the stage-dependency syntax, see: +# https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&tabs=bash#set-an-output-variable-for-use-in-future-stages +# +# Any stages that use these secrets must depend on this stage: +# ${{ stageName }} +# +# None of the values produced here are actual secrets - they are just random values that are +# suitable for testing purposes, and do not need to be protected. For simplicity, they are emitted +# as regular output variables of script steps, are not formally marked as secrets, and not stored in +# the Azure DevOps Library or Key Vault. + +parameters: + + # True to emit debug information and steps. + - name: debug + type: boolean + default: false + + - name: stageName + type: string + +stages: + + # The stage downstream stages must depend on to ensure the secrets are generated before they are + # used. + - stage: ${{ parameters.stageName }} + displayName: Generate Secrets + dependsOn: [] + + jobs: + + # The job that generates the secrets. Each secret is emitted as an output variable of a + # script step and can be referenced by downstream stages via the stage-dependencies syntax. + - job: secrets_job + displayName: Generate Secrets + pool: + # We don't need anything special, so use the standard Microsoft-hosted Ubuntu image, which + # is typically very fast to spin up. + vmImage: ubuntu-latest + + steps: + + # We don't need the repo checked out. + - checkout: none + + # Generate a password suitable for the SA user of local SQL Server instances. + # This creates the SaPassword.Value variable. + - bash: | + guid=$(cat /proc/sys/kernel/random/uuid) + echo "##vso[task.setvariable variable=Value;isOutput=true]$guid" + name: SaPassword + displayName: Generate SA password + + # Emit the SA password, if desired. + - ${{ if eq(parameters.debug, true) }}: + - bash: | + echo "SA password: $(SaPassword.Value)" + displayName: '[Debug] Emit SA password' diff --git a/eng/pipelines/pr/stages/pack-stage.yml b/eng/pipelines/pr/stages/pack-stage.yml new file mode 100644 index 0000000000..c0a135e246 --- /dev/null +++ b/eng/pipelines/pr/stages/pack-stage.yml @@ -0,0 +1,128 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This stage is meant to perform a sanity check of the source code at the current point in time. +# Although the test targets will implicitly build the projects they depend on, this stage allows +# for fast failure of the build if the source code is not in a state that can be built. +# +# This stage will also be used to generate nuget packages based on the current source code. These +# will be uploaded, along with the build output as the artifacts of the build. +# +# Developer notes --- +# * Because we want a single artifact for build output, and we want to minimize overhead for build, +# we build all packages as a single job rather than one job per package. +# * Although theoretically in package build we could arrange build steps to minimize re-building, +# build.proj isn't properly aware of how to do this. So, instead, arranging the build steps +# alphabetically is the best way to arrange for now. + +parameters: + + # Configuration with which to build the packages and execute the tests. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Suffix to append to the package version to indicate it is a pre-release/development build. + - name: buildSuffix + type: string + + # Name to use for the stage. This is parameterized to allow a higher level of the pipeline to + # define the name and pass it to sibling stages (ie, test stage) and avoid relying on brittle + # hard-coded stage names. + - name: stageName + type: string + +stages: + - stage: ${{ parameters.stageName }} + displayName: Build and Pack Projects + dependsOn: [] + + jobs: + - job: pack_job + displayName: Build and Pack Projects + + pool: + vmImage: 'ubuntu-latest' + + steps: + # Install dotnet SDK and restore tools + - template: /eng/pipelines/pr/steps/install-dotnet.yml@self + + # ################################################################ + # Build/Pack Microsoft.Data.SqlClient + - template: /eng/pipelines/pr/steps/pack-buildproj-step.yml + parameters: + condition: succeeded() # Only run if dotnet was installed successfully + + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + packageShortName: SqlClient + + # ################################################################ + # Build/Pack Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider + - template: /eng/pipelines/pr/steps/pack-buildproj-step.yml + parameters: + condition: succeededOrFailed() + + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + packageShortName: AkvProvider + + # ################################################################ + # Build/Pack Microsoft.Data.SqlClient.Extensions.Abstractions + - template: /eng/pipelines/pr/steps/pack-buildproj-step.yml + parameters: + condition: succeededOrFailed() + + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + packageShortName: Abstractions + + # ################################################################ + # Build/Pack Microsoft.Data.SqlClient.Extensions.Azure + - template: /eng/pipelines/pr/steps/pack-buildproj-step.yml + parameters: + condition: succeededOrFailed() + + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + packageShortName: Azure + + # ################################################################ + # Build/Pack Microsoft.Data.SqlClient.Internal.Logging + - template: /eng/pipelines/pr/steps/pack-buildproj-step.yml + parameters: + condition: succeededOrFailed() + + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + packageShortName: Logging + + # ################################################################ + # Build/Pack Microsoft.SqlServer.Server + - template: /eng/pipelines/pr/steps/pack-buildproj-step.yml + parameters: + condition: succeededOrFailed() + + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + packageShortName: SqlServer + + # Emit the build output + - script: tree -a $(BUILD_OUTPUT) + displayName: Output Build Output Tree + condition: succeededOrFailed() + + # Upload the build output as the artifact of the job + - publish: $(BUILD_OUTPUT) + artifact: build_and_pack_projects + condition: succeededOrFailed() + + + + diff --git a/eng/pipelines/pr/stages/test-stages.yml b/eng/pipelines/pr/stages/test-stages.yml new file mode 100644 index 0000000000..f2ffbc09cf --- /dev/null +++ b/eng/pipelines/pr/stages/test-stages.yml @@ -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. # +################################################################################# + +# These stages are meant to validate a PR against a battery of tests to ensure confidence in the +# changes the PR presents. There is, however, a balance to be struck between completeness of +# confidence and expedience of validation. + +parameters: + # Configuration with which to build the packages and execute the tests. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Suffix to append to the package version to indicate it is a pre-release/development build. + - name: buildSuffix + type: string + + # Name of the build/pack stage that this stage will depend on. + - name: stageNamePack + type: string + + # Name of the secret generation job used to determine the path to the SA password variable. + - name: stageNameSecrets + type: string + + # Name of the pool to use for jobs that require customized VM images. + - name: poolName + type: string + + # Manual Test Configuration Parameters ================================== + + # Azure Key Vault tenant ID that will be set in the config.json file for sqlclient manual tests. + - name: manualTestAzureKeyVaultTenantId + type: string + + # Azure Key Vault URL that will be set in the config.json file for sqlclient manual tests. + - name: manualTestAzureKeyVaultUrl + type: string + + # Connection string to Azure SQL using named pipes that will be set in the config.json file for + # sqlclient manual tests against Azure SQL. + - name: manualTestConnectionStringNpAzure + type: string + + # Connection string to localhost using named pipes that will be set in the config.json file for + # sqlclient manual tests against a local box product. + - name: manualTestConnectionStringNpLocalhost + type: string + + # Connection string to Azure SQL using TCP that will be set in the config.json file for sqlclient + # manual tests against Azure SQL. + - name: manualTestConnectionStringTcpAzure + type: string + + # Connection string to localhost using TCP that will be set in the config.json file for sqlclient + # manual tests against a local box product. + - name: manualTestConnectionStringTcpLocalhost + type: string + + # Path to use for file stream tests, this value will be stored in the config.json for sqlclient + # manual tests. + - name: manualTestFileStreamDirectory + type: string + + # Name of the local DB application name for localdb tests, this value will be stored in the + # config.json for sqlclient manual tests. + - name: manualTestLocalDbAppName + type: string + + # Name of the local DB shared instance name for localdb tests, this value will be stored in the + # config.json for sqlclient manual tests. + - name: manualTestLocalDbSharedInstanceName + type: string + + # This is an array of objects with the following fields: + # - displayName - Friendly name to use when displaying the stage name and job names. This + # must only contain alphanumeric characters and '_'. It will be used for both + # display name fields and stage/job names. + # - dotnet - Version of dotnet runtime to use to execute the tests + # - image - Image to use to execute the test jobs in the pipeline + # - operatingSystem - General OS "class" that the image contains. This is used to switch between + # server configuration behaviors. + # + # Each one of the objects in this parameter will become a stage that executes all test projects + # available. + - name: platforms + type: object + default: [] + +stages: + - ${{ each platform in parameters.platforms }}: + - stage: "test_${{ platform.displayName }}" + displayName: "Test: ${{ platform.displayName }}" + dependsOn: + - ${{ parameters.stageNamePack }} + - ${{ parameters.stageNameSecrets }} + + jobs: + # TestAbstractions + - template: /eng/pipelines/pr/jobs/test-buildproj-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + platformDisplayName: ${{ platform.displayName }} + platformDotnet: ${{ platform.dotnet }} + platformImage: ${{ platform.image }} + poolName: ${{ parameters.poolName }} + + packageShortName: "Abstractions" + testDisplayName: "abstractions" + + # TestAzure + - template: /eng/pipelines/pr/jobs/test-buildproj-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + platformDisplayName: ${{ platform.displayName }} + platformDotnet: ${{ platform.dotnet }} + platformImage: ${{ platform.image }} + poolName: ${{ parameters.poolName }} + + packageShortName: "Azure" + testDisplayName: "azure" + + # TestSqlClientFunctional + - template: /eng/pipelines/pr/jobs/test-buildproj-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + platformDisplayName: ${{ platform.displayName }} + platformDotnet: ${{ platform.dotnet }} + platformImage: ${{ platform.image }} + poolName: ${{ parameters.poolName }} + + packageShortName: "SqlClient" + testDisplayName: "sqlclient_functional" + testProject: "Functional" + + # TestSqlClientManual - Localhost + - template: /eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + stageNameSecrets: ${{ parameters.stageNameSecrets }} + + platformDisplayName: ${{ platform.displayName }} + platformDotnet: ${{ platform.dotnet }} + platformImage: ${{ platform.image }} + platformOperatingSystem: ${{ platform.operatingSystem }} + poolName: ${{ parameters.poolName }} + + azureKeyVaultTenantId: ${{ parameters.manualTestAzureKeyVaultTenantId }} + azureKeyVaultUrl: ${{ parameters.manualTestAzureKeyVaultUrl }} + connectionStringNp: ${{ parameters.manualTestConnectionStringNpLocalhost }} + connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpLocalhost }} + fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} + localDbAppName: ${{ parameters.manualTestLocalDbAppName }} + localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} + + # @TODO: +# # TestSqlClientManual - Azure +# - template: /eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml@self +# parameters: +# buildConfiguration: ${{ parameters.buildConfiguration }} +# buildSuffix: ${{ parameters.buildSuffix }} +# platformDisplayName: ${{ platform.displayName }} +# platformDotnet: ${{ platform.dotnet }} +# platformImage: ${{ platform.image }} +# poolName: ${{ parameters.poolName }} +# +# azureKeyVaultTenantId: ${{ parameters.manualTestAzureKeyVaultTenantId }} +# azureKeyVaultUrl: ${{ parameters.manualTestAzureKeyVaultUrl }} +# connectionStringNp: ${{ parameters.manualTestConnectionStringNpAzure }} +# connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpAzure }} +# fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} +# localDbAppName: ${{ parameters.manualTestLocalDbAppName }} +# localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} +# saPassword: $(manualTestSaPassword) + + # TestSqlClientUnit + - template: /eng/pipelines/pr/jobs/test-buildproj-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + platformDisplayName: ${{ platform.displayName }} + platformDotnet: ${{ platform.dotnet }} + platformImage: ${{ platform.image }} + poolName: ${{ parameters.poolName }} + + packageShortName: "SqlClient" + testDisplayName: "sqlclient_unit" + testProject: "Unit" diff --git a/eng/pipelines/pr/steps/build-buildproj-step.yml b/eng/pipelines/pr/steps/build-buildproj-step.yml new file mode 100644 index 0000000000..3e81edfbf4 --- /dev/null +++ b/eng/pipelines/pr/steps/build-buildproj-step.yml @@ -0,0 +1,46 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This collection of steps to build a project via the build.proj. This will execute the "Build*" +# target in build.proj, where * is the packageShortName provided in the parameters. +# +# Note: This differs from the onebranch build-buildproj-step.yml in that it does *not* strong-name +# sign the assemblies, it only builds in project reference mode, and as such it does not allow +# version parameters or dependencies to be provided. + +parameters: + - name: buildConfiguration + type: string + values: + - Debug + - Release + + - name: buildSuffix + type: string + + - name: packageShortName + type: string + values: + - Azure + - AkvProvider + - Abstractions + - Logging + - SqlClient + - SqlServer + +steps: + - task: DotNetCoreCLI@2 + displayName: 'build.proj - Build${{ parameters.packageShortName }}' + inputs: + command: build + projects: build.proj + arguments: >- + -t:Build${{ parameters.packageShortName }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:BuildNumber='$(Build.BuildNumber)' + -p:BuildSuffix='${{ parameters.buildSuffix }}' + + diff --git a/eng/pipelines/pr/steps/configure-sqlserver-linux-step.yml b/eng/pipelines/pr/steps/configure-sqlserver-linux-step.yml new file mode 100644 index 0000000000..8eb8cc40d6 --- /dev/null +++ b/eng/pipelines/pr/steps/configure-sqlserver-linux-step.yml @@ -0,0 +1,127 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This step configures an existing SQL Server running on the local Linux host. For example, our 1ES +# Hosted Pool has images like ADO-UB20-SQL22 that come with SQL Server 2022 pre-installed and +# running. + +parameters: + + # The SA password to set when configuring SQL Server. + - name: saPassword + type: string + +steps: + + # Configure SQL Server. + - bash: | + set -u + + if ! command -v sudo >/dev/null 2>&1; then + echo "ERROR: 'sudo' is required to configure SQL Server on Linux." + exit 1 + fi + + if [ ! -x /opt/mssql/bin/mssql-conf ]; then + echo "ERROR: '/opt/mssql/bin/mssql-conf' was not found." + exit 1 + fi + + SQLCMD_BIN="" + if command -v sqlcmd >/dev/null 2>&1; then + SQLCMD_BIN="$(command -v sqlcmd)" + elif [ -x /opt/mssql-tools18/bin/sqlcmd ]; then + SQLCMD_BIN="/opt/mssql-tools18/bin/sqlcmd" + elif [ -x /opt/mssql-tools/bin/sqlcmd ]; then + SQLCMD_BIN="/opt/mssql-tools/bin/sqlcmd" + else + echo "ERROR: 'sqlcmd' was not found on PATH or in the standard mssql-tools locations." + exit 1 + fi + + sudo systemctl stop mssql-server + errstatus=$? + if [ $errstatus -ne 0 ] + then + echo "ERROR: stopping mssql-server failed with exit code $errstatus." + exit $errstatus + fi + + # Password for the SA user (required) + MSSQL_SA_PW="${{ parameters.saPassword }}" + + # 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" + + echo Running mssql-conf setup... + sudo MSSQL_SA_PASSWORD="$MSSQL_SA_PW" \ + MSSQL_PID="$MSSQL_PID" \ + /opt/mssql/bin/mssql-conf -n setup accept-eula + errstatus=$? + if [ $errstatus -ne 0 ] + then + echo "ERROR: mssql-conf setup failed with exit code $errstatus." + exit $errstatus + fi + + # Connect to server and get the version: + counter=1 + errstatus=1 + while [ $counter -le 5 ] && [ $errstatus -ne 0 ] + do + echo Waiting for SQL Server to start... + sleep 3s + "$SQLCMD_BIN" \ + -S localhost \ + -U SA \ + -P "$MSSQL_SA_PW" \ + -Q "SELECT @@VERSION" 2>/dev/null + errstatus=$? + ((counter++)) + done + + # Display error if connection failed: + if [ $errstatus -ne 0 ] + then + echo Cannot connect to SQL Server, installation aborted + exit $errstatus + fi + displayName: 'Configure SQL Server [Linux]' + + - bash: | + set -u + + SCRIPT_PATH="$(Build.SourcesDirectory)/tools/testsql/createNorthwindDb.sql" + MSSQL_SA_PW="${{ parameters.saPassword }}" + + SQLCMD_BIN="" + if command -v sqlcmd >/dev/null 2>&1; then + SQLCMD_BIN="$(command -v sqlcmd)" + elif [ -x /opt/mssql-tools18/bin/sqlcmd ]; then + SQLCMD_BIN="/opt/mssql-tools18/bin/sqlcmd" + elif [ -x /opt/mssql-tools/bin/sqlcmd ]; then + SQLCMD_BIN="/opt/mssql-tools/bin/sqlcmd" + else + echo "ERROR: 'sqlcmd' was not found on PATH or in the standard mssql-tools locations." + exit 1 + fi + + "$SQLCMD_BIN" \ + -S localhost \ + -U SA \ + -P "$MSSQL_SA_PW" \ + -Q "IF DB_ID(N'Northwind') IS NOT NULL BEGIN ALTER DATABASE [Northwind] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [Northwind]; END" \ + -b + + "$SQLCMD_BIN" \ + -S localhost \ + -U SA \ + -P "$MSSQL_SA_PW" \ + -i "$SCRIPT_PATH" \ + -b + displayName: 'Create Northwind Database [Linux]' + retryCountOnTaskFailure: 1 diff --git a/eng/pipelines/pr/steps/configure-sqlserver-step.yml b/eng/pipelines/pr/steps/configure-sqlserver-step.yml new file mode 100644 index 0000000000..ebae5390e4 --- /dev/null +++ b/eng/pipelines/pr/steps/configure-sqlserver-step.yml @@ -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. # +################################################################################# + +parameters: + + # Path to use for file stream tests. This only applies to Windows test runs. + - name: fileStreamDirectory + type: string + + - name: operatingSystem + type: string + + # Password to use for the SA account. This should be generated from the generate-secrets-stage. + - name: saPassword + type: string + +steps: + - ${{ if eq(parameters.operatingSystem, 'Windows') }}: + - template: /eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml@self + parameters: + fileStreamDirectory: ${{ parameters.fileStreamDirectory }} + saPassword: ${{ parameters.saPassword }} + + - ${{ elseif eq(parameters.operatingSystem, 'Linux') }}: + - template: /eng/pipelines/pr/steps/configure-sqlserver-linux-step.yml@self + parameters: + saPassword: ${{ parameters.saPassword }} diff --git a/eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml b/eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml new file mode 100644 index 0000000000..d1567beaee --- /dev/null +++ b/eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml @@ -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. # +################################################################################# + +# This step configures an existing SQL Server running on the local Windows host. For example, our +# 1ES Hosted Pool has images like ADO-MMS22-SQL22 that come with SQL Server 2022 pre-installed and +# running. + +parameters: + + # Path to use for file stream tests. + - name: fileStreamDirectory + type: string + + # Password to use for the SA account. This should be generated from the generate-secrets-stage. + - name: saPassword + type: string + +steps: + # GOTCHA: We must use the Windows-only powershell task here instead of the cross-platform pwsh + # task because we call some Windows-specific cmdlets. + + # Enable TCP and NP protocols on the default SQL Server instance + - powershell: | + try + { + echo "> 1. Acquire WMI handle" + Import-Module "sqlps" + $smo = 'Microsoft.SqlServer.Management.Smo.' + $wmi = new-object ($smo + 'Wmi.ManagedComputer') + + + Write-Host "WMI Information:" + $wmi + + echo "> 2. Enable TCP on the default instance" + $Tcp = $wmi.GetSmoObject("ManagedComputer[@Name='$env:COMPUTERNAME']/ ServerInstance[@Name='MSSQLSERVER']/ServerProtocol[@Name='Tcp']") + $Tcp.IsEnabled = $true + $Tcp.Alter() + + Write-Host "TCP Information:" + $Tcp + + echo "> 3. Enable named pipes on the default instance" + $Np = $wmi.GetSmoObject("ManagedComputer[@Name='$env:COMPUTERNAME']/ ServerInstance[@Name='MSSQLSERVER']/ServerProtocol[@Name='Np']") + $Np.IsEnabled = $true + $Np.Alter() + + Write-Host "NP Information:" + $Np + } + catch + { + $error[0] | format-list -force + throw + } + + New-NetFirewallRule -DisplayName "SQL TCP Ports" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action allow + $sqlSrvPath = (Get-WmiObject win32_service | ?{$_.DisplayName -eq 'SQL Server (MSSQLSERVER)'} | select @{Name="Path"; Expression={$_.PathName.split('"')[1]}}).Path + New-NetFirewallRule -DisplayName "sqlservr.exe" -Program "$sqlSrvPath" + displayName: 'Enable TCP, NP & Firewall [Win]' + retryCountOnTaskFailure: 2 + + # Set the SA password on the default instance + - powershell: | + # Propagate parameters to PS variables ############################### + $password = "${{ parameters.saPassword }}" + + # Connect to database and update SA account ########################## + Import-Module "sqlps" + $machineName = $env:COMPUTERNAME + + Invoke-Sqlcmd -ServerInstance "$machineName" @" + ALTER LOGIN [sa] ENABLE; + ALTER LOGIN [sa] WITH PASSWORD = '$password'; + "@ + displayName: 'Set SA Password [Win]' + retryCountOnTaskFailure: 5 + + # Create folder for file stream tests + - powershell: | + New-Item -Path ${{ parameters.fileStreamDirectory }} -ItemType Directory + displayName: 'Create FileStreamFolder' + retryCountOnTaskFailure: 1 + continueOnError: true + + # Install northwind database + - powershell: | + # Propagate parameters to PS variables ############################### + $repoRoot = "$(REPO_ROOT)" + echo "repoRoot= $repoRoot" + + # Install Northwind Database ######################################### + Import-Module "sqlps" + $machineName = $env:COMPUTERNAME + + # Delete any existing northwind database + Invoke-Sqlcmd ` + -ServerInstance "$machineName" ` + -Database "master" ` + -Query @" + IF DB_ID(N'Northwind') IS NOT NULL + BEGIN + ALTER DATABASE [Northwind] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE [Northwind]; + END + "@ + + # Install northwind database via script + $scriptPath = "$repoRoot\tools\testsql\createNorthwindDb.sql" + Invoke-Sqlcmd -ServerInstance "$machineName" -Database "master" -InputFile $scriptPath + displayName: 'Create Northwind Database [Win]' + retryCountOnTaskFailure: 1 diff --git a/eng/pipelines/pr/steps/install-dotnet.yml b/eng/pipelines/pr/steps/install-dotnet.yml new file mode 100644 index 0000000000..fe1c8f7878 --- /dev/null +++ b/eng/pipelines/pr/steps/install-dotnet.yml @@ -0,0 +1,27 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This is a step to wrap the install-dotnet step for use in the PR pipeline. It will always install +# the SDK version specified in the global.json file, and if the runtime version specified does not +# begin with "net4" (ie, indicating netfx runtime is being used), the version will be passed in as +# part of the runtime parameters. +# +# This version chains into the common install-dotnet step to enable the pr pipeline to call a +# single step template and get the desired behavior without complexity. + +parameters: + - name: runtimeVersion + type: string + default: '' + +steps: + - template: /eng/pipelines/common/steps/install-dotnet.yml@self + parameters: + ${{ if and( not(eq(parameters.runtimeVersion, '' )), not(contains(parameters.runtimeVersion, 'net4')) ) }}: + runtimes: + - "${{ replace(parameters.runtimeVersion, 'net', '') }}.x" + + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self diff --git a/eng/pipelines/pr/steps/pack-buildproj-step.yml b/eng/pipelines/pr/steps/pack-buildproj-step.yml new file mode 100644 index 0000000000..6b2980cbb4 --- /dev/null +++ b/eng/pipelines/pr/steps/pack-buildproj-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. # +################################################################################# + +# This is a step to build a project via the build.proj. This will execute the "Pack*" target in +# build.proj, where * is the packageShortName provided in the parameters. Because the "no build" +# parameter is not provided, the target will implicitly build the projects required to create the +# NuGet package. + +parameters: + # General Parameters ===================================================== + + # Condition parameter to forward onto the DotNetCoreCLI task. + - name: condition + type: string + default: succeeded() + + # Build Parameters ======================================================= + + # Configuration to use to build the project. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Suffix to apply to the generated packages such that the package will be considered a prerelease + # version. The resulting package version will look like: `1.2.3-suffix888.1` where 1.2.3 is the + # default version specified in Versions.props, suffix is this parameter, and 888.1 is the build + # number for the currently executing build. + - name: buildSuffix + type: string + + # Package/project to build and pack. + - name: packageShortName + type: string + values: + - Azure + - AkvProvider + - Abstractions + - Logging + - SqlClient + - SqlServer + +steps: + - task: DotNetCoreCLI@2 + displayName: 'build.proj - Build${{ parameters.packageShortName }}' + condition: ${{ parameters.condition }} + inputs: + command: build + projects: build.proj + verbosity: detailed + arguments: >- + -t:Pack${{ parameters.packageShortName }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:BuildNumber='$(Build.BuildNumber)' + -p:BuildSuffix='${{ parameters.buildSuffix }}' diff --git a/eng/pipelines/pr/steps/test-buildproj-step.yml b/eng/pipelines/pr/steps/test-buildproj-step.yml new file mode 100644 index 0000000000..5309e7ef96 --- /dev/null +++ b/eng/pipelines/pr/steps/test-buildproj-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: + # General Parameters ======================================================= + + # Configuration to use to build the project. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Suffix to apply to the generated packages such that the package will be considered a prerelease + # version. The resulting package version will look like: `1.2.3-suffix888.1` where 1.2.3 is the + # default version specified in Versions.props, suffix is this parameter, and 888.1 is the build + # number for the currently executing build. + - name: buildSuffix + type: string + + # Test Parameters ======================================================== + + # Package/project to test. + - name: packageShortName + type: string + values: + - Azure + - AkvProvider + - Abstractions + - Logging + - SqlClient + - SqlServer + + - name: testFramework + type: string + + # If the project to test has multiple projects supported by build.proj, use this optional + # parameter to specify the project to execute. This will be appended to Test{PackageShortName} to + # generate the target to execute. See build.proj for supported targets. + - name: testProject + type: string + default: '' + +steps: + - task: DotNetCoreCLI@2 + displayName: 'build.proj - Test${{ parameters.packageShortName }}${{ parameters.testProject }}' + inputs: + command: build + projects: build.proj + verbosity: detailed + arguments: >- + -t:Test${{ parameters.packageShortName }}${{ parameters.testProject }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:BuildNumber='$(Build.BuildNumber)' + -p:BuildSuffix='${{ parameters.buildSuffix }}' + -p:TestFramework=${{ parameters.testFramework }} diff --git a/eng/pipelines/pr/variables/pr-variables.yml b/eng/pipelines/pr/variables/pr-variables.yml new file mode 100644 index 0000000000..4a28253dc7 --- /dev/null +++ b/eng/pipelines/pr/variables/pr-variables.yml @@ -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. # +################################################################################# + +# This file contains variables used for running PR pipelines. + +variables: + # Libraries ============================================================== + + # These variables are used to construct the config file for manual tests. + # + # AzureKeyVaultTenantId + # AzureKeyVaultUrl + # ConnectionStringNp_Azure_ManagedIdentity + # ConnectionStringNp_LocalhostDefault_UsernamePassword + # ConnectionStringTcp_Azure_ManagedIdentity + # ConnectionStringTcp_LocalhostDefault_UsernamePassword + # LocalDbAppName + # LocalDbSharedInstanceName + # PoolNameDefault + - group: sqlclient-testconfig-v1 + + # General Variables ====================================================== + - name: stageNamePack + value: "pack_stage" + + - name: stageNameSecrets + value: "secrets_stage" diff --git a/eng/pipelines/stress/stress-tests-job.yml b/eng/pipelines/stress/stress-tests-job.yml index a216c23864..ac82002ece 100644 --- a/eng/pipelines/stress/stress-tests-job.yml +++ b/eng/pipelines/stress/stress-tests-job.yml @@ -144,7 +144,7 @@ jobs: steps: # Install the .NET SDK and Runtimes. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: runtimes: [8.x, 9.x] diff --git a/src/Microsoft.Data.SqlClient/tests/Common/SqlDataReaderExtensions.cs b/src/Microsoft.Data.SqlClient/tests/Common/SqlDataReaderExtensions.cs index 43c7bde4bd..8851cb5210 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/SqlDataReaderExtensions.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/SqlDataReaderExtensions.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. +using System.Threading.Tasks; + namespace Microsoft.Data.SqlClient.Tests.Common { /// @@ -21,6 +23,19 @@ public static void FlushAllResults(this SqlDataReader dataReader) } while (dataReader.NextResult()); } + /// + /// Reads all result sets in the provided and discards them + /// asynchronously. + /// + /// Reader to flush results from. + public static async Task FlushAllResultsAsync(this SqlDataReader dataReader) + { + do + { + await dataReader.FlushResultSetAsync(); + } while (await dataReader.NextResultAsync()); + } + /// /// Reads all results in the current result set of the provided /// and discards them. @@ -33,5 +48,18 @@ public static void FlushResultSet(this SqlDataReader dataReader) // Discard results. } } + + /// + /// Reads all results in the current result set of the provided + /// and discards them asynchronously. + /// + /// Reader to flush results from. + public static async Task FlushResultSetAsync(this SqlDataReader dataReader) + { + while (await dataReader.ReadAsync()) + { + // Discard results. + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index 1c16e90ac4..620d7bd99a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -14,13 +14,18 @@ using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; using Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals; +using Microsoft.Data.SqlClient.Tests.Common; using Xunit; +using CommonObjects = Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { /// /// Always Encrypted public API Manual tests. /// + // @TODO: These tests are known to not run reliably in DEBUG mode. + [ConditionalClass(typeof(DataTestUtility), nameof(DataTestUtility.IsNotDebugBuild))] public sealed class ApiShould : IClassFixture, IDisposable { private SQLSetupStrategy _fixture; @@ -726,37 +731,41 @@ public void TestExecuteReader(string connection) [ClassData(typeof(AEConnectionStringProvider))] public async Task TestExecuteReaderAsyncWithLargeQuery(string connectionString) { - string randomName = DataTestUtility.GetShortName(Guid.NewGuid().ToString().Replace("-", ""), false); - if (randomName.Length > 50) - { - randomName = randomName.Substring(0, 50); - } - string tableName = $"VeryLong_{randomName}_TestTableName"; - int columnsCount = 50; - - // Arrange - drops the table with long name and re-creates it with 52 columns (ID, name, ColumnName0..49) - using SqlConnection connection = new SqlConnection(connectionString); + // Arrange + // - Set up connection to run the test + using SqlConnection connection = new(connectionString); await connection.OpenAsync(); - using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects.Table wideTable = new(connection, tableName, GenerateBitTableDefinition(columnsCount)); - string name = "nobody"; + // - Drop and create table with 52 columns (ID, name, ColumnName0..49) + string tableName = DataTestUtility.GetShortName("VeryLongTestTableName_", withBracket: false); + const int columnsCount = 50; + using CommonObjects.Table wideTable = new(connection, tableName, GenerateBitTableDefinition(columnsCount)); - // This creates a "select top 100" query that has over 40k characters - using SqlCommand sqlCommand = new(GenerateSelectQuery(wideTable.Name, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"), + // - Create SELECT query that has over 40k characters *and repeats 10 times* + using SqlCommand sqlCommand = new( + GenerateSelectQuery(wideTable.Name, columnsCount, 10, "WHERE Name = @FirstName AND ID = @CustomerId"), connection, transaction: null, columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled); - sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int); - sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar, name.Length); - sqlCommand.Parameters[0].Value = 0; - sqlCommand.Parameters[1].Value = name; + SqlParameter parameter1 = sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int); + parameter1.Value = 0; + + SqlParameter parameter2 = sqlCommand.Parameters.Add(@"FirstName", SqlDbType.VarChar); + parameter2.Value = "nobody"; - // Act and Assert - // Test that execute reader async does not throw an exception. - // The table is empty so there should be no results; however, the bug previously found is that it causes a TDS RPC exception on enclave. + // Act using SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync(); + + // Assert + // - The table is empty so there should be no results; however, the bug previously + // found is that it causes a TDS RPC exception on enclave. Assert.False(sqlDataReader.HasRows, "The table should be empty"); + + // Cleanup + // - Explicitly throw away the results from the reader to avoid any race conditions in + // disposal logic. + await sqlDataReader.FlushAllResultsAsync(); } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] @@ -1914,7 +1923,7 @@ public void TestExecuteXmlReader(string connection) IList values = GetValues(dataHint: 60); int numberOfRows = 10; - // Insert a bunch of rows in to the table. + // Insert a bunch of rows in to the table. int rowsAffected = InsertRows(tableName: _tableName, numberofRows: numberOfRows, values: values, connection: connection); Assert.True(rowsAffected == numberOfRows, "number of rows affected is unexpected."); @@ -1922,12 +1931,12 @@ public void TestExecuteXmlReader(string connection) { sqlConnection.Open(); - // select the set of rows that were inserted just now. + // select the set of rows that were inserted just now. using (SqlCommand sqlCommand = new SqlCommand($"SELECT LastName FROM [{_tableName}] WHERE FirstName = @FirstName AND CustomerId = @CustomerId FOR XML AUTO;", sqlConnection, transaction: null, columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled)) { if (DataTestUtility.EnclaveEnabled) { - //Increase Time out for enclave-enabled server. + //Increase Time out for enclave-enabled server. sqlCommand.CommandTimeout = 90; } sqlCommand.Parameters.Add(@"CustomerId", SqlDbType.Int); @@ -2031,7 +2040,7 @@ public void TestSqlCommandCancel(string connection, string value) * 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 @@ -2307,7 +2316,7 @@ public void TestConnectionCustomKeyStoreProviderDuringAeQuery(string connectionS () => ExecuteQueryThatRequiresCustomKeyStoreProvider(connection)); AssertExceptionCausedByFailureToDecrypt(ex); - // not required provider will replace the previous entry so required provider will not be found + // not required provider will replace the previous entry so required provider will not be found connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(_notRequiredProvider); ex = Assert.Throws( () => ExecuteQueryThatRequiresCustomKeyStoreProvider(connection)); @@ -2448,7 +2457,7 @@ public void TestRetryWhenAEParameterMetadataCacheIsStale(string connectionString cmd.CommandText = string.Format(alterCekQueryFormatString, _tableName, table.columnEncryptionKey2.Name); cmd.ExecuteNonQuery(); - // execute the select query again. it will attempt to use the stale cache entry, receive + // execute the select query again. it will attempt to use the stale cache entry, receive // a retryable error from the server, remove the stale cache entry, retry and succeed cmd.CommandText = enclaveSelectQuery; cmd.Parameters.AddWithValue("@CustomerId", 0); @@ -2562,7 +2571,7 @@ public void TestRetryWhenAEEnclaveCacheIsStale(string connectionString) readAsyncTask2.GetAwaiter().GetResult(); #endif */ - + // revert the CEK change to the CustomerId column cmd.Parameters.Clear(); cmd.CommandText = string.Format(alterCekQueryFormatString, _tableName, table.columnEncryptionKey1.Name); @@ -2699,8 +2708,8 @@ private DataTable CreateDataTable(string tableName, int numberofRows) }; table.Columns.Add(column); - // Create three new DataRow objects and add - // them to the DataTable + // Create three new DataRow objects and add + // them to the DataTable for (int i = 0; i < numberofRows; i++) { row = table.NewRow(); @@ -2810,7 +2819,7 @@ private async Task VerifyExecuteScalarAsync(SqlCommand sqlCommand) } /// - /// + /// /// /// /// @@ -2821,7 +2830,7 @@ private async Task ExecuteNonQueryAsync(SqlCommand sqlCommand, CancellationToken } /// - /// + /// /// /// /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 623f645ddb..9bf1c0f519 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -188,6 +188,20 @@ public static string SQLServerVersion IsSqlVectorSupported && CheckVectorFloat16Supported(); + public static bool IsDebugBuild + { + get + { + #if DEBUG + return true; + #else + return false; + #endif + } + } + + public static bool IsNotDebugBuild() => !IsDebugBuild; + private static bool CheckVectorFloat16Supported() { try 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 ca22d6eafd..4612145331 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -22,16 +22,10 @@ public static class InstanceNameTest public static void ConnectToSQLWithInstanceNameTest() { SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out _, out _); - bool proceed = true; - string dataSourceStr = builder.DataSource.Replace("tcp:", ""); - string[] serverNamePartsByBackSlash = dataSourceStr.Split('\\'); - string hostname = serverNamePartsByBackSlash[0]; - if (!dataSourceStr.Contains(",") && serverNamePartsByBackSlash.Length == 2) - { - proceed = !string.IsNullOrWhiteSpace(hostname) && IsBrowserAlive(hostname); - } - + // @TODO: This test as it is written will report success even if it doesn't test anything. + bool proceed = !string.IsNullOrWhiteSpace(hostname) && IsBrowserAlive(hostname); if (proceed) { using SqlConnection connection = new(builder.ConnectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs index 552fd4cea5..04b0de8072 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs @@ -150,6 +150,8 @@ public static void CancelAndDisposePreparedCommand_NamedPipe() => public static void TimeOutDuringRead_Tcp() => TimeOutDuringRead(tcp_connStr); + // @TODO: This test does not test NP behavior as the proxy server converts the named pipe connection string to a TCP connection string. + [ActiveIssue("https://dev.azure.com/SqlClientDrivers/ADO.Net/_workitems/edit/40840/")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotNamedInstance))] [PlatformSpecific(TestPlatforms.Windows)] public static void TimeOutDuringRead_NamedPipe() => diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs index e4bd1133ac..24f34bbf12 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs @@ -11,7 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public sealed class SqlNotificationTest : IDisposable { // Misc constants - private const int CALLBACK_TIMEOUT = 5000; // milliseconds + private const int CALLBACK_TIMEOUT = 20000; // milliseconds // Database schema private readonly string _tableName = $"dbo.[SQLDEP_{Guid.NewGuid().ToString()}]"; @@ -143,6 +143,7 @@ public void Test_SingleDependency_Stopped() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + [Trait("Category", "flaky")] public void Test_SingleDependency_AllDefaults_SqlAuth() { Assert.True(SqlDependency.Start(_startConnectionString), "Failed to start listener."); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs index 6e82dc5076..24c9c4f884 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs @@ -15,33 +15,32 @@ public class Config public string TCPConnectionStringHGSVBS = null; public string TCPConnectionStringNoneVBS = null; public string TCPConnectionStringAASSGX = null; + public bool EnclaveEnabled = false; + public bool TracingEnabled = false; public string AADAuthorityURL = null; public string AADPasswordConnectionString = null; public string AADServicePrincipalId = null; public string AADServicePrincipalSecret = null; public string AzureKeyVaultURL = null; public string AzureKeyVaultTenantId = null; + public bool SupportsIntegratedSecurity = false; public string LocalDbAppName = null; public string LocalDbSharedInstanceName = null; - public bool EnclaveEnabled = false; - public bool TracingEnabled = false; - public bool SupportsIntegratedSecurity = false; - public bool ManagedIdentitySupported = true; public string FileStreamDirectory = null; public bool UseManagedSNIOnWindows = false; public string DNSCachingConnString = null; public string DNSCachingServerCR = null; // this is for the control ring public string DNSCachingServerTR = null; // this is for the tenant ring - public bool IsAzureSynapse = false; // True for Azure Data Warehouse/Synapse public bool IsDNSCachingSupportedCR = false; // this is for the control ring public bool IsDNSCachingSupportedTR = false; // this is for the tenant ring public string EnclaveAzureDatabaseConnString = null; + public bool ManagedIdentitySupported = true; public string UserManagedIdentityClientId = null; public string PowerShellPath = null; + public string AliasName = null; public string KerberosDomainPassword = null; public string KerberosDomainUser = null; public bool IsManagedInstance = false; - public string AliasName = null; public static Config Load(string configPath = @"config.json") { 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 4362d07c75..b2200b03c0 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 @@ -22,7 +22,6 @@ "SupportsIntegratedSecurity": true, "LocalDbAppName": "", "LocalDbSharedInstanceName": "", - "SupportsFileStream": false, "FileStreamDirectory": "", "UseManagedSNIOnWindows": false, "DNSCachingConnString": "", @@ -30,11 +29,13 @@ "DNSCachingServerTR": "", "IsDNSCachingSupportedCR": false, "IsDNSCachingSupportedTR": false, - "IsAzureSynapse": false, "EnclaveAzureDatabaseConnString": "", "ManagedIdentitySupported": true, "UserManagedIdentityClientId": "", "PowerShellPath": "", "AliasName": "", - "WorkloadIdentityFederationServiceConnectionId": "" + "WorkloadIdentityFederationServiceConnectionId": "", + "KerberosDomainPassword": "", + "KerberosDomainUser": "", + "IsManagedInstance": false } From 4afd45edb1b48144450f1ccf0a22f24015283257 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Fri, 5 Jun 2026 21:09:28 +0530 Subject: [PATCH 128/298] Add automatic idle connection pruning to ChannelDbConnectionPool (#4304) Introduce a PoolPruner that periodically samples the pool's idle connection count and, once a sampling window completes, prunes the median number of idle connections. This lets the pool shrink back toward MinPoolSize after load subsides while avoiding over-pruning during brief lulls. --- .../src/Microsoft/Data/Common/AdapterUtil.cs | 36 +- .../ConnectionPool/ChannelDbConnectionPool.cs | 133 ++-- .../SqlClient/ConnectionPool/PoolPruner.cs | 230 +++++++ .../ChannelDbConnectionPoolPruningTest.cs | 622 ++++++++++++++++++ 4 files changed, 960 insertions(+), 61 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/PoolPruner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs 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 c0e170b3f8..568592072d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -159,32 +159,22 @@ internal static Timer UnsafeCreateTimer( state, TimeSpan.FromMilliseconds(dueTimeMilliseconds), TimeSpan.FromMilliseconds(periodMilliseconds)); - + internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) { - // Don't capture the current ExecutionContext and its AsyncLocals onto + // Don't capture the current ExecutionContext and its AsyncLocals onto // a global timer causing them to live forever - bool restoreFlow = false; - try + if (ExecutionContext.IsFlowSuppressed()) { - if (!ExecutionContext.IsFlowSuppressed()) - { - ExecutionContext.SuppressFlow(); - restoreFlow = true; - } - return new Timer(callback, state, dueTime, period); } - finally + + using (ExecutionContext.SuppressFlow()) { - // Restore the current ExecutionContext - if (restoreFlow) - { - ExecutionContext.RestoreFlow(); - } + return new Timer(callback, state, dueTime, period); } } - + #region COM+ exceptions internal static ArgumentException Argument(string error) @@ -639,7 +629,7 @@ internal static string BuildMultiPartName(string[] strings) { StringBuilder bld = new(); // Assume we want to build a full multi-part name with all parts except trimming separators for - // leading empty names (null or empty strings, but not whitespace). Separators in the middle + // leading empty names (null or empty strings, but not whitespace). Separators in the middle // should be added, even if the name part is null/empty, to maintain proper location of the parts. for (int i = 0; i < strings.Length; i++) { @@ -839,14 +829,14 @@ internal static Version GetAssemblyVersion() /// 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 + /// 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 List s_azureSqlServerEndpoints = new() { 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. @@ -872,9 +862,9 @@ internal static Version GetAssemblyVersion() internal static bool IsAzureSynapseOnDemandEndpoint(string dataSource) { return IsEndpoint(dataSource, s_azureSynapseOnDemandEndpoints) - || dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0; + || dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0; } - + internal static bool IsAzureSqlServerEndpoint(string dataSource) { return IsEndpoint(dataSource, s_azureSqlServerEndpoints); @@ -1088,7 +1078,7 @@ internal enum InternalErrorCode SqlDependencyObtainProcessDispatcherFailureObjectHandle = 50, SqlDependencyProcessDispatcherFailureCreateInstance = 51, - + SqlDependencyCommandHashIsNotAssociatedWithNotification = 53, UnknownTransactionFailure = 60, 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 index 45e82eda91..3218bf461b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -23,10 +23,10 @@ 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. - /// + /// /// This implementation uses for managing idle connections, /// which offers several advantages over the traditional WaitHandleDbConnectionPool: - /// + /// /// /// /// Better async performance: Channels provide native async/await support without blocking @@ -45,11 +45,11 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// the potential for race conditions in connection lifecycle management. /// /// - /// + /// /// The trade-off is slightly higher memory overhead per pool instance due to the channel infrastructure, /// but this is generally offset by the performance benefits in async-heavy workloads. /// - internal sealed class ChannelDbConnectionPool : IDbConnectionPool + internal sealed class ChannelDbConnectionPool : IDbConnectionPool, IDisposable { #region Fields // Limits synchronous operations which depend on async operations on managed @@ -115,13 +115,20 @@ internal ChannelDbConnectionPool( _connectionSlots = new(MaxPoolSize); _idleChannel = new(); + // Pruning is only useful when the pool can grow beyond MinPoolSize. + // If min >= max, the pool is fixed-size and pruning would never activate. + if (MinPoolSize < MaxPoolSize) + { + Pruner = new PoolPruner(this, PoolGroupOptions.LoadBalanceTimeout); + } + State = Running; } #region Properties /// public ConcurrentDictionary< - DbConnectionPoolAuthenticationContextKey, + DbConnectionPoolAuthenticationContextKey, DbConnectionPoolAuthenticationContext> AuthenticationContexts { get; } /// @@ -168,6 +175,8 @@ public ConcurrentDictionary< public bool UseLoadBalancing => PoolGroupOptions.UseLoadBalancing; private uint MaxPoolSize { get; } + + private int MinPoolSize => PoolGroupOptions.MinPoolSize; #endregion #region Methods @@ -223,7 +232,7 @@ public void PutObjectFromTransactedPool(DbConnectionInternal connection) /// public DbConnectionInternal ReplaceConnection( - DbConnection owningObject, + DbConnection owningObject, DbConnectionInternal oldConnection, TimeoutTimer timeout) { @@ -242,13 +251,13 @@ public void ReturnInternalConnection(DbConnectionInternal connection, DbConnecti } SqlClientEventSource.Log.TryPoolerTraceEvent( - " {0}, Connection {1}, Deactivating.", - Id, + " {0}, Connection {1}, Deactivating.", + Id, connection.ObjectID); connection.DeactivateConnection(); - if (connection.IsConnectionDoomed || - !connection.CanBePooled || + if (connection.IsConnectionDoomed || + !connection.CanBePooled || State == ShuttingDown) { RemoveConnection(connection); @@ -263,9 +272,15 @@ public void ReturnInternalConnection(DbConnectionInternal connection, DbConnecti /// public void Shutdown() { - // No-op for now, warmup will be implemented later. + State = ShuttingDown; + Pruner?.Dispose(); } + /// + /// Disposes the pool by calling . Does not throw. + /// + public void Dispose() => Shutdown(); + /// public void Startup() { @@ -280,7 +295,7 @@ public void TransactionEnded(Transaction transaction, DbConnectionInternal trans /// public bool TryGetConnection( - DbConnection owningObject, + DbConnection owningObject, TaskCompletionSource? taskCompletionSource, TimeoutTimer timeout, out DbConnectionInternal? connection) @@ -307,19 +322,19 @@ public bool TryGetConnection( connection = null; return false; } - + // This is ugly, but async anti-patterns above and below us in the stack necessitate a fresh task to be - // created. Ideally we would just return the Task from GetInternalConnection and let the caller await + // created. Ideally we would just return the Task from GetInternalConnection and let the caller await // it as needed, but instead we need to signal to the provided TaskCompletionSource when the connection - // is established. This pattern has implications for connection open retry logic that are intricate - // enough to merit dedicated work. For now, callers that need to open many connections asynchronously - // and in parallel *must* pre-prevision threads in the managed thread pool to avoid exhaustion and + // is established. This pattern has implications for connection open retry logic that are intricate + // enough to merit dedicated work. For now, callers that need to open many connections asynchronously + // and in parallel *must* pre-prevision threads in the managed thread pool to avoid exhaustion and // timeouts. - // - // Also note that we don't have access to the cancellation token passed by the caller to the original + // + // Also note that we don't have access to the cancellation token passed by the caller to the original // OpenAsync call. This means that we cannot cancel the connection open operation if the caller's token - // is cancelled. We can only cancel based on our own timeout, which is set to the owningObject's - // ConnectionTimeout. + // is cancelled. We can only cancel based on our own timeout, which is set to the owningObject's + // ConnectionTimeout. Task.Run(async () => { if (taskCompletionSource.Task.IsCompleted) @@ -372,14 +387,14 @@ public bool TryGetConnection( /// /// The owning connection. /// The cancellation token to cancel the operation. - /// The overall timeout budget. Passed through to the physical connection + /// The overall timeout budget. Passed through to the physical connection /// so it uses the remaining budget rather than starting a fresh timeout. /// A task representing the asynchronous operation, with a result of the new internal connection. /// /// Thrown when the cancellation token is cancelled before the connection operation completes. /// private DbConnectionInternal? OpenNewInternalConnection( - DbConnection? owningConnection, + DbConnection? owningConnection, CancellationToken cancellationToken, TimeoutTimer timeout) { @@ -389,16 +404,16 @@ public bool TryGetConnection( // Instead, we reserve a connection slot prior to attempting to open a new connection and release the slot // in case of an exception. - return _connectionSlots.Add( + var result = _connectionSlots.Add( createCallback: () => { // https://github.com/dotnet/SqlClient/issues/3459 // TODO: This blocks the thread for several network calls! - // When running async, the blocked thread is one allocated from the managed thread pool (due to - // use of Task.Run in TryGetConnection). This is why it's critical for async callers to - // pre-provision threads in the managed thread pool. Our options are limited because + // When running async, the blocked thread is one allocated from the managed thread pool (due to + // use of Task.Run in TryGetConnection). This is why it's critical for async callers to + // pre-provision threads in the managed thread pool. Our options are limited because // DbConnectionInternal doesn't support an async open. It's better to block this thread and keep - // throughput high than to queue all of our opens onto a single worker thread. Add an async path + // throughput high than to queue all of our opens onto a single worker thread. Add an async path // when this support is added to DbConnectionInternal. // TODO: ultimately, the connection factory should also accept our cancellation token. var connection = ConnectionFactory.CreatePooledConnection( @@ -420,6 +435,15 @@ public bool TryGetConnection( _idleChannel?.TryWrite(null); newConnection?.Dispose(); }); + + if (result is not null) + { + // A new connection was added to the pool. If we've grown past MinPoolSize, + // start the pruning timer so idle connections can be reclaimed. + Pruner?.UpdateTimer(); + } + + return result; } /// @@ -457,13 +481,16 @@ private bool IsLiveConnection(DbConnectionInternal connection) private void RemoveConnection(DbConnectionInternal connection) { _connectionSlots.TryRemove(connection); - + // Removing a connection from the pool opens a free slot. // Write a null to the idle connection channel to wake up a waiter, who can now open a new // connection. Statement order is important since we have synchronous completions on the channel. _idleChannel.TryWrite(null); connection.Dispose(); + + // If this removal brought us back to MinPoolSize, disable the pruning timer. + Pruner?.UpdateTimer(); } /// @@ -475,7 +502,7 @@ private void RemoveConnection(DbConnectionInternal connection) // The channel may contain nulls. Read until we find a non-null connection or exhaust the channel. while (_idleChannel.TryRead(out DbConnectionInternal? connection)) { - if (connection is null) + if (connection is null) { continue; } @@ -501,16 +528,16 @@ private void RemoveConnection(DbConnectionInternal connection) /// in the pool is deducted from the budget available for physical connection creation. /// Returns a DbConnectionInternal that is retrieved from the pool. /// - /// Thrown when an OperationCanceledException is caught, indicating that the timeout period + /// Thrown when an OperationCanceledException is caught, indicating that the timeout period /// elapsed prior to obtaining a connection from the pool. /// /// - /// Thrown when a ChannelClosedException is caught, indicating that the connection pool + /// Thrown when a ChannelClosedException is caught, indicating that the connection pool /// has been shut down. /// private async Task GetInternalConnection( - DbConnection owningConnection, - bool async, + DbConnection owningConnection, + bool async, TimeoutTimer timeout) { DbConnectionInternal? connection = null; @@ -530,7 +557,7 @@ private async Task GetInternalConnection( connection ??= GetIdleConnection(); - // If we didn't find an idle connection, try to open a new one. + // If we didn't find an idle connection, try to open a new one. connection ??= OpenNewInternalConnection( owningConnection, cancellationToken, @@ -604,18 +631,18 @@ private async Task GetInternalConnection( } /// - /// Sets connection state and activates the connection for use. Should always be called after a connection is + /// Sets connection state and activates the connection for use. Should always be called after a connection is /// created or retrieved from the pool. /// /// The owning DbConnection instance. /// The DbConnectionInternal to be activated. /// - /// Thrown when any exception occurs during connection activation. + /// Thrown when any exception occurs during connection activation. /// private void PrepareConnection(DbConnection owningObject, DbConnectionInternal connection) { lock (connection) - { + { // Protect against Clear which calls IsEmancipated, which is affected by PrePush and PostPop connection.PostPop(owningObject); } @@ -653,5 +680,35 @@ private void ValidateOwnershipAndSetPoolingState(DbConnectionInternal connection } } #endregion + + #region Pruning + /// + /// Manages idle connection pruning. Null when the pool is fixed-size (MinPoolSize >= MaxPoolSize) + /// because pruning would never activate. + /// + internal PoolPruner? Pruner { get; } + + /// + /// Removes up to idle connections from the pool, respecting + /// the floor. Called by after computing + /// the median of collected samples. + /// + internal void PruneConnections(int count) + { + while (count > 0 + && IsRunning + && _connectionSlots.ReservationCount > MinPoolSize + && _idleChannel.TryRead(out var connection)) + { + if (connection is null) + { + continue; + } + + RemoveConnection(connection); + count--; + } + } + #endregion } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/PoolPruner.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/PoolPruner.cs new file mode 100644 index 0000000000..07899d26e3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/PoolPruner.cs @@ -0,0 +1,230 @@ +// 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.Threading; +using Microsoft.Data.Common; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + /// + /// Encapsulates idle connection pruning logic for . + /// Periodically samples the idle connection count and, once enough samples are collected, + /// computes the median and prunes that many idle connections from the pool. + /// + /// This type is only instantiated when the pool can shrink (MinPoolSize < MaxPoolSize). + /// When the pool is fixed-size, the pool holds no instance. + /// + /// + internal sealed class PoolPruner : IDisposable + { + /// + /// Maximum allowed sample buffer size to prevent excessive memory allocation + /// from very large lifetime values. With a 10-second interval this supports + /// idle lifetimes up to 3000 seconds (50 minutes). + /// + internal const int MaxSampleSize = 300; + + /// + /// Default sampling interval. + /// + private static readonly TimeSpan DefaultSamplingInterval = TimeSpan.FromSeconds(10); + + /// + /// Default lifetime window used for sample size calculation when + /// LoadBalanceTimeout (Connection Lifetime) is zero. + /// + private static readonly TimeSpan DefaultLifetimeWindow = TimeSpan.FromSeconds(300); + + /// + /// The owning connection pool whose idle connections are pruned. + /// + private readonly ChannelDbConnectionPool _pool; + + /// + /// One-shot timer that triggers pruning evaluation. Re-armed manually after each + /// pruning operation to prevent overlapping invocations. + /// + private readonly Timer _timer; + + /// + /// The interval between pruning samples/evaluations. + /// + private readonly TimeSpan _samplingInterval; + + /// + /// Number of idle count samples to collect before computing the median and pruning. + /// Equals lifetime / interval (rounded up), clamped to . + /// + private readonly int _sampleSize; + + /// + /// Buffer of idle count snapshots, one recorded per timer tick. + /// Sorted in-place when full to compute the median, then reset for the next window. + /// + private readonly int[] _samples; + + /// + /// The 0-based index into the sorted array that represents the median. + /// Always >= 0 because returns >= 1. + /// + private readonly int _medianIndex; + + /// + /// Whether the pruning timer is currently armed and firing. + /// Volatile because it is read outside the lock as a fast pre-check. + /// + private volatile bool _timerEnabled; + + /// + /// Current write position in the buffer. + /// Only accessed under the lock. + /// + private int _sampleIndex; + + /// + /// Creates a new pruner for the given pool. + /// + /// The owning connection pool. + /// + /// The connection lifetime window used to compute sample count. + /// When zero or negative, is used. + /// + internal PoolPruner(ChannelDbConnectionPool pool, TimeSpan lifetimeWindow) + { + _pool = pool; + _samplingInterval = DefaultSamplingInterval; + + int lifetimeSeconds = (int)lifetimeWindow.TotalSeconds; + if (lifetimeSeconds <= 0) + { + lifetimeSeconds = (int)DefaultLifetimeWindow.TotalSeconds; + } + + int intervalSeconds = (int)_samplingInterval.TotalSeconds; + _sampleSize = Math.Min( + DivideRoundingUp(lifetimeSeconds, intervalSeconds), + MaxSampleSize); + + // Subtract 1 to convert from length to 0-based index. Safe because + // DivideRoundingUp always returns >= 1, so _sampleSize >= 1. + _medianIndex = DivideRoundingUp(_sampleSize, 2) - 1; + _samples = new int[_sampleSize]; + + _timer = ADP.UnsafeCreateTimer(OnPruningCallback, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + } + + #region Internal test surface + + /// Whether the pruning timer is currently armed. Exposed for unit tests. + internal bool IsTimerEnabled => _timerEnabled; + + /// Current write position in the sample buffer. Exposed for unit tests. + internal int SampleIndex => _sampleIndex; + + /// Total number of samples collected per pruning window. Exposed for unit tests. + internal int SampleSize => _sampleSize; + + /// Read-only view of the sample buffer contents. Exposed for unit tests. + internal ReadOnlySpan Samples => _samples; + + #endregion + + /// + /// Enables or disables the pruning timer based on the current pool size relative to MinPoolSize. + /// Called by the pool after connections are opened or closed. + /// + internal void UpdateTimer() + { + if (!_pool.IsRunning) + { + return; + } + + lock (_timer) + { + // Re-check after acquiring lock — Shutdown() may have disposed the timer. + if (!_pool.IsRunning) + { + return; + } + + int numConnections = _pool.Count; + + if (numConnections > _pool.PoolGroupOptions.MinPoolSize && !_timerEnabled) + { + // Pool grew beyond min — start collecting samples. + _timerEnabled = true; + _timer.Change(_samplingInterval, Timeout.InfiniteTimeSpan); + } + else if (numConnections <= _pool.PoolGroupOptions.MinPoolSize && _timerEnabled) + { + // Pool shrunk back to min — stop pruning, reset sample buffer. + _timer.Change(Timeout.Infinite, Timeout.Infinite); + _sampleIndex = 0; + _timerEnabled = false; + } + } + } + + /// + /// Timer callback that samples the idle count and, once enough samples are collected, + /// prunes idle connections based on the median of recent samples. + /// + internal void OnPruningCallback(object? state) + { + int toPrune; + + lock (_timer) + { + // Guard against races with Dispose or UpdateTimer disabling the timer. + if (!_timerEnabled) + { + return; + } + + // Record the current idle count as a sample. + _samples[_sampleIndex] = _pool.IdleCount; + + if (_sampleIndex != _sampleSize - 1) + { + // Buffer not full yet — keep collecting, re-arm timer. + _sampleIndex++; + _timer.Change(_samplingInterval, Timeout.InfiniteTimeSpan); + return; + } + + // Buffer full — sort to find the median, then reset and re-arm. + // Array.Sort is O(n log n) but acceptable here since _sampleSize is capped at 300. + Array.Sort(_samples); + toPrune = _samples[_medianIndex]; + _sampleIndex = 0; + _timer.Change(_samplingInterval, Timeout.InfiniteTimeSpan); + } + + // Prune outside the lock to avoid holding it during I/O. + _pool.PruneConnections(toPrune); + } + + /// + /// Stops the pruning timer and releases resources. Safe to call multiple times; + /// is idempotent and does not throw. + /// + public void Dispose() + { + lock (_timer) + { + _timerEnabled = false; + _timer.Dispose(); + } + } + + /// + /// Returns the ceiling of / + /// using integer arithmetic. Always returns >= 1 for positive inputs. + /// + internal static int DivideRoundingUp(int value, int divisor) => 1 + (value - 1) / divisor; + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs new file mode 100644 index 0000000000..74d9758cde --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs @@ -0,0 +1,622 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Transactions; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; +using static Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolState; + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool +{ + /// + /// Unit tests for the pruning feature in . + /// + public class ChannelDbConnectionPoolPruningTest + { + private static readonly SqlConnectionFactory ConnectionFactory = new SuccessfulSqlConnectionFactory(); + + #region Helpers + + private static ChannelDbConnectionPool ConstructPool( + int minPoolSize = 0, + int maxPoolSize = 50, + int loadBalanceTimeout = 0) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: minPoolSize, + maxPoolSize: maxPoolSize, + creationTimeout: 15, + loadBalanceTimeout: loadBalanceTimeout, + hasTransactionAffinity: true + ); + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + poolGroupOptions + ); + return new ChannelDbConnectionPool( + ConnectionFactory, + dbConnectionPoolGroup, + DbConnectionPoolIdentity.NoIdentity, + new DbConnectionPoolProviderInfo() + ); + } + + /// + /// A checked-out connection, pairing the owning with its + /// internal connection so the two can never be mismatched. + /// + private record BusyConnection(SqlConnection Owner, DbConnectionInternal InternalDbConnection); + + /// + /// Checks out connections from the pool and leaves them in use. + /// Returns the checked-out connections so the caller can later return them via + /// . + /// + private static List CheckOutConnections( + ChannelDbConnectionPool pool, int count) + { + var connections = new List(count); + + for (int i = 0; i < count; i++) + { + var owner = new SqlConnection(); + var completed = pool.TryGetConnection(owner, null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out var connection); + Assert.True(completed); + Assert.NotNull(connection); + connections.Add(new BusyConnection(owner, connection)); + } + + return connections; + } + + /// + /// Returns previously checked-out connections to the pool, making them idle. + /// + private static void ReturnConnections( + ChannelDbConnectionPool pool, + List connections) + { + foreach (var connection in connections) + { + pool.ReturnInternalConnection(connection.InternalDbConnection, connection.Owner); + } + } + + /// + /// Opens connections and returns them to the pool so they are idle. + /// + private static void FillPoolWithIdleConnections(ChannelDbConnectionPool pool, int count) + { + var connections = CheckOutConnections(pool, count); + ReturnConnections(pool, connections); + } + + /// + /// Asserts that the pool has a non-null and returns it. + /// + private static PoolPruner GetPruner(ChannelDbConnectionPool pool) + { + PoolPruner? pruner = pool.Pruner; + Assert.NotNull(pruner); + return pruner; + } + + /// + /// Asserts the pruner state that matters for pruning progression: the timer-enabled flag, + /// the sample index, and the valid samples. + /// + /// + /// Only the samples in the range [0, SampleIndex) are meaningful and validated. + /// Callers must pass exactly the valid samples, i.e. + /// must have elements. + /// + private static void AssertPrunerState( + PoolPruner pruner, + bool isTimerEnabled, + int sampleIndex, + params int[] expectedSamples) + { + Assert.Equal(isTimerEnabled, pruner.IsTimerEnabled); + Assert.Equal(sampleIndex, pruner.SampleIndex); + + // Enforce that the caller validates every valid sample (and only valid samples). + Assert.Equal(sampleIndex, expectedSamples.Length); + + for (int i = 0; i < expectedSamples.Length; i++) + { + Assert.Equal(expectedSamples[i], pruner.Samples[i]); + } + } + + #endregion + + #region Timer Creation / Configuration Tests + + [Fact] + public void Constructor_MinPoolSizeLessThanMax_CreatesPruningTimer() + { + // When min < max, the pool can shrink so a pruning timer should be created. + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10); + + Assert.NotNull(pool.Pruner); + } + + [Fact] + public void Constructor_MinPoolSizeEqualsMax_DoesNotCreatePruningTimer() + { + // When min == max, the pool is fixed-size — pruning would never activate. + using var pool = ConstructPool(minPoolSize: 10, maxPoolSize: 10); + + Assert.Null(pool.Pruner); + } + + [Fact] + public void Constructor_PruningTimerStartsDisabled() + { + // Timer should be created but not armed (pool starts empty, below MinPoolSize threshold). + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10); + var pruner = GetPruner(pool); + + AssertPrunerState(pruner, false, 0); + } + + [Theory] + [InlineData(100, 10)] // 100 / 10 = 10 samples + [InlineData(300, 30)] // 300 / 10 = 30 samples + [InlineData(60, 6)] // 60 / 10 = 6 samples + [InlineData(15, 2)] // 15 / 10 = 2 samples (rounds up) + [InlineData(0, 30)] // 0 falls back to DefaultLifetimeWindow (300) / 10 = 30 samples + [InlineData(10000, 300)] // 10000 / 10 = 1000, clamped to MaxPruningSampleSize (300) + public void Constructor_CalculatesSampleSizeFromLoadBalanceTimeout( + int loadBalanceTimeout, int expectedSampleSize) + { + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, loadBalanceTimeout: loadBalanceTimeout); + var pruner = GetPruner(pool); + + Assert.Equal(expectedSampleSize, pruner.SampleSize); + } + + #endregion + + #region UpdatePruningTimer Tests + + [Fact] + public void UpdatePruningTimer_PoolGrowsBeyondMinPoolSize_EnablesTimer() + { + using var pool = ConstructPool(minPoolSize: 2, maxPoolSize: 10); + var pruner = GetPruner(pool); + + // Pool starts empty, timer should be disabled + AssertPrunerState(pruner, false, 0); + + // Add connections to grow beyond MinPoolSize + FillPoolWithIdleConnections(pool, 3); + + // After growing beyond min, UpdatePruningTimer is called internally + // and should enable the timer. + AssertPrunerState(pruner, true, 0); + } + + [Fact] + public void UpdatePruningTimer_PoolAtMinPoolSize_TimerRemainsDisabled() + { + using var pool = ConstructPool(minPoolSize: 2, maxPoolSize: 10); + var pruner = GetPruner(pool); + + // Add exactly MinPoolSize connections + FillPoolWithIdleConnections(pool, 2); + + // Timer should stay disabled since we're at (not above) MinPoolSize + AssertPrunerState(pruner, false, 0); + } + + [Fact] + public void UpdatePruningTimer_PoolShrinksBackToMin_DisablesTimerAndResetsSamples() + { + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10); + var pruner = GetPruner(pool); + + // Grow the pool to enable the timer + FillPoolWithIdleConnections(pool, 3); + AssertPrunerState(pruner, true, 0); + + // Now prune all connections back to 0 (MinPoolSize) by filling the sample buffer. + // Each callback before the buffer fills records the current idle count (3) and + // advances the sample index without pruning. + int sampleSize = pruner.SampleSize; + for (int i = 0; i < sampleSize - 1; i++) + { + pruner.OnPruningCallback(null); + + // After each non-final callback the timer stays enabled, the index advances, + // and every previously recorded sample remains unchanged. + int[] expectedSamples = new int[i + 1]; + for (int j = 0; j <= i; j++) + { + expectedSamples[j] = 3; + } + AssertPrunerState(pruner, true, i + 1, expectedSamples); + } + + // The final callback fills the buffer, takes the median (3), and prunes all idle + // connections back to MinPoolSize (0). That disables the timer and resets the index. + pruner.OnPruningCallback(null); + AssertPrunerState(pruner, false, 0); + Assert.Equal(0, pool.Count); + } + + #endregion + + #region PruneIdleConnections Tests + + [Fact] + public void PruneIdleConnections_BufferNotFull_CollectsSampleWithoutPruning() + { + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, loadBalanceTimeout: 30); + var pruner = GetPruner(pool); + // loadBalanceTimeout=30 → sample size = 30/10 = 3 + + // Fill the pool so pruning timer is active + FillPoolWithIdleConnections(pool, 5); + int initialCount = pool.Count; + + // First invocation: records idle count in sample[0], buffer not full + pruner.OnPruningCallback(null); + + // No connections should be pruned yet + Assert.Equal(initialCount, pool.Count); + AssertPrunerState(pruner, true, 1, 5); + } + + [Fact] + public void PruneIdleConnections_RespectsMinPoolSizeFloor() + { + // loadBalanceTimeout=20 → 2 samples + using var pool = ConstructPool(minPoolSize: 5, maxPoolSize: 20, loadBalanceTimeout: 20); + var pruner = GetPruner(pool); + + // Fill pool with 10 idle connections + FillPoolWithIdleConnections(pool, 10); + Assert.Equal(10, pool.Count); + + // Fill sample buffer and trigger pruning + pruner.OnPruningCallback(null); // sample 1 + AssertPrunerState(pruner, true, 1, 10); + + pruner.OnPruningCallback(null); // sample 2 → prune + // Pruning brings the pool down to MinPoolSize (5), so the pruner disables its timer. + AssertPrunerState(pruner, false, 0); + + // Pool should not drop below MinPoolSize (5). + // 10 idle connections, median of 2 samples (both 10) = 10, but floor is MinPoolSize=5. + Assert.Equal(5, pool.Count); + } + + [Fact] + public void PruneIdleConnections_TimerDisabled_ReturnsEarlyWithoutPruning() + { + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, loadBalanceTimeout: 20); + var pruner = GetPruner(pool); + + // Pool starts empty, timer is disabled. Calling prune should be a no-op. + pruner.OnPruningCallback(null); + + Assert.Equal(0, pool.Count); + AssertPrunerState(pruner, false, 0); + } + + [Fact] + public void PruneIdleConnections_DoesNotRemoveInUseConnections() + { + // loadBalanceTimeout=20 → 2 samples + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, loadBalanceTimeout: 20); + var pruner = GetPruner(pool); + + // Get 5 connections and KEEP them checked out (in use) + CheckOutConnections(pool, 5); + + // Also add 5 idle connections + FillPoolWithIdleConnections(pool, 5); + + // Total = 10 (5 in-use + 5 idle) + Assert.Equal(10, pool.Count); + + // Fill sample buffer and trigger pruning + pruner.OnPruningCallback(null); // sample 1 (idle count = 5) + AssertPrunerState(pruner, true, 1, 5); + + pruner.OnPruningCallback(null); // sample 2 → prune + AssertPrunerState(pruner, true, 0); + + // In-use connections must not be removed. + // 5 idle connections, median of 2 samples (both 5) = 5, all 5 idle pruned → 5 in-use remain. + Assert.Equal(5, pool.Count); + } + + #endregion + + #region Shutdown Tests + + [Fact] + public void Shutdown_DisposesPruningTimer() + { + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10); + var pruner = GetPruner(pool); + + // Enable the timer by growing the pool beyond MinPoolSize + FillPoolWithIdleConnections(pool, 3); + AssertPrunerState(pruner, true, 0); + + pool.Shutdown(); + + Assert.Equal(ShuttingDown, pool.State); + // After shutdown, the timer-enabled flag must be cleared. + AssertPrunerState(pruner, false, 0); + } + + [Fact] + public void Shutdown_NullTimer_DoesNotThrow() + { + // Fixed-size pool has no timer + using var pool = ConstructPool(minPoolSize: 5, maxPoolSize: 5); + Assert.Null(pool.Pruner); + + // Should not throw + pool.Shutdown(); + Assert.Equal(ShuttingDown, pool.State); + } + + [Fact] + public void Shutdown_CalledMultipleTimes_IsIdempotent() + { + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10); + var pruner = GetPruner(pool); + + // Enable the timer so there is real state to tear down. + FillPoolWithIdleConnections(pool, 3); + AssertPrunerState(pruner, true, 0); + + pool.Shutdown(); + AssertPrunerState(pruner, false, 0); + + // A repeated shutdown plus an explicit pruner Dispose must be safe no-ops. + // (The `using` block will also dispose the pool, calling Shutdown a third time.) + pool.Shutdown(); + pruner.Dispose(); + + Assert.Equal(ShuttingDown, pool.State); + AssertPrunerState(pruner, false, 0); + } + + #endregion + + #region DivideRoundingUp Tests + + [Theory] + [InlineData(10, 10, 1)] + [InlineData(11, 10, 2)] + [InlineData(300, 10, 30)] + [InlineData(1, 1, 1)] + [InlineData(7, 3, 3)] // ceil(7/3) = 3 + [InlineData(6, 3, 2)] // exact division + [InlineData(15, 10, 2)] // ceil(15/10) = 2 + public void DivideRoundingUp_ReturnsCorrectCeiling(int value, int divisor, int expected) + { + int result = PoolPruner.DivideRoundingUp(value, divisor); + + Assert.Equal(expected, result); + } + + #endregion + + #region Integration Tests + + [Fact] + public void PruneIdleConnections_AllIdleWithZeroMinPoolSize_PrunesEntirePool() + { + // Given: A pool with many idle connections and low recent usage. + // When: The pruning interval elapses (sample buffer fills). + // Then: The pool closes excess idle connections. + + // Use loadBalanceTimeout=20 for 2 samples (fast buffer fill) + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, loadBalanceTimeout: 20); + var pruner = GetPruner(pool); + + // Simulate high load: open 20 connections + FillPoolWithIdleConnections(pool, 20); + Assert.Equal(20, pool.Count); + + // Pruning samples will both record idle=20, so toPrune=20. + pruner.OnPruningCallback(null); // sample 1 + AssertPrunerState(pruner, true, 1, 20); + + pruner.OnPruningCallback(null); // sample 2 → prune + // Pruning empties the pool to MinPoolSize (0), so the pruner disables its timer. + AssertPrunerState(pruner, false, 0); + + // All 20 idle connections should be pruned since MinPoolSize is 0. + // Both samples record idle=20, median=20, all pruned. + Assert.Equal(0, pool.Count); + } + + [Fact] + public void PruneIdleConnections_VariedSamples_UsesMedianNotCurrentIdleCount() + { + // Given: A pool with high recent usage but currently many idle connections due to a brief lull. + // When: The pruning interval elapses. + // Then: Pruning uses sampled usage data (median) to avoid being too aggressive. + + // Use 3 samples (loadBalanceTimeout=30) + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, loadBalanceTimeout: 30); + var pruner = GetPruner(pool); + + // Simulate varied usage: + // Sample 1: 2 idle (high demand, most connections checked out) + FillPoolWithIdleConnections(pool, 10); + // Check out 8, leaving 2 idle + var busyConnections = CheckOutConnections(pool, 8); + pruner.OnPruningCallback(null); // sample[0] = 2 idle + AssertPrunerState(pruner, true, 1, 2); + Assert.Equal(10, pool.Count); + + // Sample 2: 2 idle (still high demand) + pruner.OnPruningCallback(null); // sample[1] = 2 idle + AssertPrunerState(pruner, true, 2, 2, 2); + Assert.Equal(10, pool.Count); + + // Now return all busy connections, creating a brief lull (10 idle) + ReturnConnections(pool, busyConnections); + + // Sample 3: 10 idle (lull). Buffer full → sorted=[2,2,10], median at index 1 = 2. + int countBefore = pool.Count; + pruner.OnPruningCallback(null); + // Only 2 are pruned (10 → 8), so the pool stays above MinPoolSize and the timer + // remains enabled; the index resets for the next window. + AssertPrunerState(pruner, true, 0); + + // Pruning should remove exactly 2 connections (the median), not 10 (the current idle count). + // This verifies that sampling prevents aggressive pruning during brief lulls. + int pruned = countBefore - pool.Count; + Assert.Equal(2, pruned); + } + + [Fact] + public void PruneIdleConnections_EvenSampleCountWithDistinctValues_UsesLowerMiddleSample() + { + // With an even number of samples the median index is the lower-middle element + // (_medianIndex = DivideRoundingUp(sampleSize, 2) - 1). For 2 samples that is index 0. + // Using distinct sample values proves the lower-middle is chosen rather than the + // upper-middle or an average. + + // loadBalanceTimeout=20 → 2 samples + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, loadBalanceTimeout: 20); + var pruner = GetPruner(pool); + + // Start with 8 idle connections. + FillPoolWithIdleConnections(pool, 8); + + // Check out 4 so only 4 remain idle for the first sample. + var busyConnections = CheckOutConnections(pool, 4); + + // Sample 1: 4 idle. + pruner.OnPruningCallback(null); + AssertPrunerState(pruner, true, 1, 4); + Assert.Equal(8, pool.Count); + + // Return the 4 busy connections so 8 are idle for the second sample. + ReturnConnections(pool, busyConnections); + + // Sample 2: 8 idle. Buffer full → sorted=[4,8], median at index 0 = 4. + int countBefore = pool.Count; + pruner.OnPruningCallback(null); + AssertPrunerState(pruner, true, 0); + + // Exactly 4 (the lower-middle sample) should be pruned, not 8. + int pruned = countBefore - pool.Count; + Assert.Equal(4, pruned); + Assert.Equal(4, pool.Count); + } + + [Fact] + public void PruneIdleConnections_SecondWindowAfterPrune_RecomputesMedianFromFreshSamples() + { + // After a window completes and prunes, the pruner re-arms and starts a fresh sampling + // window. This test proves that the second window samples and prunes independently of + // the first: it computes its own median from newly recorded samples and prunes the + // correct amount, confirming the collect → prune → reset → collect cycle is repeatable. + + // loadBalanceTimeout=20 → 2 samples per window + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, loadBalanceTimeout: 20); + var pruner = GetPruner(pool); + + // --- Window 1: produces a median of 4 --- + FillPoolWithIdleConnections(pool, 12); + // Check out 8, leaving 4 idle. + var busyConnections = CheckOutConnections(pool, 8); + + pruner.OnPruningCallback(null); // sample[0] = 4 idle + AssertPrunerState(pruner, true, 1, 4); + + pruner.OnPruningCallback(null); // buffer full [4,4], median 4, prune 4 idle + // Window 1 done: timer stays enabled (pool still above min), index reset for window 2. + AssertPrunerState(pruner, true, 0); + Assert.Equal(8, pool.Count); // 8 busy remain, 0 idle + + // Return the 8 busy connections so they are idle for window 2. + ReturnConnections(pool, busyConnections); + Assert.Equal(8, pool.Count); + + // --- Window 2: 8 idle, produces its own median of 8 --- + pruner.OnPruningCallback(null); // sample[0] = 8 + // The new window records a fresh sample (8), independent of window 1's median (4). + AssertPrunerState(pruner, true, 1, 8); + + int countBefore = pool.Count; + pruner.OnPruningCallback(null); // buffer full [8,8], median 8, prune 8 + AssertPrunerState(pruner, false, 0); + + // Window 2's median was 8 (its own samples), so all 8 idle connections are pruned. + Assert.Equal(8, countBefore - pool.Count); + Assert.Equal(0, pool.Count); + } + + #endregion + + #region Test classes + + internal class SuccessfulSqlConnectionFactory : SqlConnectionFactory + { + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + return new StubDbConnectionInternal(); + } + } + + internal class StubDbConnectionInternal : DbConnectionInternal + { + public override string ServerVersion => throw new NotImplementedException(); + + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) + { + throw new NotImplementedException(); + } + + public override void EnlistTransaction(Transaction transaction) + { + return; + } + + protected override void Activate(Transaction transaction) + { + return; + } + + protected override void Deactivate() + { + return; + } + + internal override void ResetConnection() + { + return; + } + } + + #endregion + } +} From 3488b364e2ebddfa53d12887ac1d835ac2e7f3b5 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:37:18 -0700 Subject: [PATCH 129/298] Fix fetching signature verification result from cache (#4339) * Fix fetching signature verification result from cache * Introduce Tri-State * Remove stale content, clean ups --- .../SqlClient/SignatureVerificationCache.cs | 188 +++++++++++------- .../Data/SqlClient/SqlSecurityUtility.cs | 18 +- .../SignatureVerificationCacheTests.cs | 49 +++++ 3 files changed, 175 insertions(+), 80 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SignatureVerificationCacheTests.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs index 8d1ce98df1..dc086d4610 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs @@ -9,6 +9,29 @@ namespace Microsoft.Data.SqlClient { + /// + /// Tri-state result returned by . + /// Distinguishes a cache miss from a cached negative result so callers cannot conflate the two. + /// + internal enum SignatureVerificationResult + { + /// + /// No cached entry exists for the requested CMK metadata. + /// The caller must verify the signature with the key store provider. + /// + NotFound, + + /// + /// A cached entry exists and indicates that signature verification previously failed. + /// + False, + + /// + /// A cached entry exists and indicates that signature verification previously succeeded. + /// + True, + } + /// /// Cache for storing result of signature verification of CMK Metadata /// @@ -16,138 +39,159 @@ internal class ColumnMasterKeyMetadataSignatureVerificationCache { private const int CacheSize = 2000; // Cache size in number of entries. private const int CacheTrimThreshold = 300; // Threshold above the cache size when we start trimming. - - private const string _className = "ColumnMasterKeyMetadataSignatureVerificationCache"; - private const string _getSignatureVerificationResultMethodName = "GetSignatureVerificationResult"; - private const string _addSignatureVerificationResultMethodName = "AddSignatureVerificationResult"; - private const string _masterkeypathArgumentName = "masterKeyPath"; - private const string _keyStoreNameArgumentName = "keyStoreName"; - private const string _signatureName = "signature"; private const string _cacheLookupKeySeparator = ":"; - private static readonly ColumnMasterKeyMetadataSignatureVerificationCache _signatureVerificationCache = new ColumnMasterKeyMetadataSignatureVerificationCache(); private static readonly TimeSpan s_verificationCacheTimeout = TimeSpan.FromDays(10); - //singleton instance - internal static ColumnMasterKeyMetadataSignatureVerificationCache Instance { get { return _signatureVerificationCache; } } + /// + /// Gets the process-wide singleton instance of the signature verification cache. + /// + internal static ColumnMasterKeyMetadataSignatureVerificationCache Instance { get; } = new(); private readonly MemoryCache _cache; - private int _inTrim = 0; + private int _inTrim; private ColumnMasterKeyMetadataSignatureVerificationCache() { _cache = new MemoryCache(new MemoryCacheOptions()); - _inTrim = 0; } /// - /// Get signature verification result for given CMK metadata (KeystoreName, MasterKeyPath, allowEnclaveComputations) and a given signature + /// Get signature verification result for given CMK metadata + /// (KeystoreName, MasterKeyPath, allowEnclaveComputations) and a given signature /// /// Key Store name for CMK /// Key Path for CMK /// boolean indicating whether the key can be sent to enclave /// Signature for the CMK metadata - internal bool GetSignatureVerificationResult(string keyStoreName, string masterKeyPath, bool allowEnclaveComputations, byte[] signature) + /// Tri-state result indicating whether signature verification succeeded, failed, or was not found in cache + /// + /// Thrown when , , + /// or is . + /// + /// + /// Thrown when or + /// is empty or whitespace, or when has length zero. + /// + internal SignatureVerificationResult GetSignatureVerificationResult(string keyStoreName, string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { - ValidateStringArgumentNotNullOrEmpty(masterKeyPath, _masterkeypathArgumentName, _getSignatureVerificationResultMethodName); - ValidateStringArgumentNotNullOrEmpty(keyStoreName, _keyStoreNameArgumentName, _getSignatureVerificationResultMethodName); - ValidateSignatureNotNullOrEmpty(signature, _getSignatureVerificationResultMethodName); + ValidateStringArgumentNotNullOrEmpty(masterKeyPath, nameof(masterKeyPath), nameof(GetSignatureVerificationResult)); + ValidateStringArgumentNotNullOrEmpty(keyStoreName, nameof(keyStoreName), nameof(GetSignatureVerificationResult)); + ValidateSignatureNotNullOrEmpty(signature, nameof(GetSignatureVerificationResult)); string cacheLookupKey = GetCacheLookupKey(masterKeyPath, allowEnclaveComputations, signature, keyStoreName); - return _cache.TryGetValue(cacheLookupKey, out bool value); + if (!_cache.TryGetValue(cacheLookupKey, out bool value)) + { + return SignatureVerificationResult.NotFound; + } + + return value ? SignatureVerificationResult.True : SignatureVerificationResult.False; } /// - /// Add signature verification result for given CMK metadata (KeystoreName, MasterKeyPath, allowEnclaveComputations) and a given signature in the cache + /// Add signature verification result for given CMK metadata (KeystoreName, + /// MasterKeyPath, allowEnclaveComputations) and a given signature in the cache /// /// Key Store name for CMK /// Key Path for CMK /// boolean indicating whether the key can be sent to enclave /// Signature for the CMK metadata /// result indicating signature verification success/failure + /// + /// Thrown when , , + /// or is . + /// + /// + /// Thrown when or is empty or whitespace, + /// or when has length zero. + /// internal void AddSignatureVerificationResult(string keyStoreName, string masterKeyPath, bool allowEnclaveComputations, byte[] signature, bool result) { - ValidateStringArgumentNotNullOrEmpty(masterKeyPath, _masterkeypathArgumentName, _addSignatureVerificationResultMethodName); - ValidateStringArgumentNotNullOrEmpty(keyStoreName, _keyStoreNameArgumentName, _addSignatureVerificationResultMethodName); - ValidateSignatureNotNullOrEmpty(signature, _addSignatureVerificationResultMethodName); + ValidateStringArgumentNotNullOrEmpty(masterKeyPath, nameof(masterKeyPath), nameof(AddSignatureVerificationResult)); + ValidateStringArgumentNotNullOrEmpty(keyStoreName, nameof(keyStoreName), nameof(AddSignatureVerificationResult)); + ValidateSignatureNotNullOrEmpty(signature, nameof(AddSignatureVerificationResult)); string cacheLookupKey = GetCacheLookupKey(masterKeyPath, allowEnclaveComputations, signature, keyStoreName); TrimCacheIfNeeded(); // By default evict after 10 days. - _cache.Set(cacheLookupKey, result, absoluteExpirationRelativeToNow: s_verificationCacheTimeout); + _cache.Set(cacheLookupKey, result, absoluteExpirationRelativeToNow: s_verificationCacheTimeout); } - private void ValidateSignatureNotNullOrEmpty(byte[] signature, string methodName) + private static void ValidateSignatureNotNullOrEmpty(byte[] signature, string methodName) { - if (signature == null || signature.Length == 0) + if (signature is null) + { + throw SQL.NullArgumentInternal(nameof(signature), nameof(ColumnMasterKeyMetadataSignatureVerificationCache), methodName); + } + if (signature.Length == 0) { - if (signature == null) - { - throw SQL.NullArgumentInternal(_signatureName, _className, methodName); - } - else - { - throw SQL.EmptyArgumentInternal(_signatureName, _className, methodName); - } + throw SQL.EmptyArgumentInternal(nameof(signature), nameof(ColumnMasterKeyMetadataSignatureVerificationCache), methodName); } } - private void ValidateStringArgumentNotNullOrEmpty(string stringArgValue, string stringArgName, string methodName) + private static void ValidateStringArgumentNotNullOrEmpty(string value, string argumentName, string methodName) { - if (string.IsNullOrWhiteSpace(stringArgValue)) + if (value is null) + { + throw SQL.NullArgumentInternal(argumentName, nameof(ColumnMasterKeyMetadataSignatureVerificationCache), methodName); + } + if (string.IsNullOrWhiteSpace(value)) { - if (stringArgValue == null) - { - throw SQL.NullArgumentInternal(stringArgName, _className, methodName); - } - else - { - throw SQL.EmptyArgumentInternal(stringArgName, _className, methodName); - } + throw SQL.EmptyArgumentInternal(argumentName, nameof(ColumnMasterKeyMetadataSignatureVerificationCache), methodName); } } + private void TrimCacheIfNeeded() { // If the size of the cache exceeds the threshold, set that we are in trimming and trim the cache accordingly. long currentCacheSize = _cache.Count; - if ((currentCacheSize > CacheSize + CacheTrimThreshold) && (0 == Interlocked.CompareExchange(ref _inTrim, 1, 0))) + if (currentCacheSize <= CacheSize + CacheTrimThreshold || Interlocked.CompareExchange(ref _inTrim, 1, 0) != 0) { - try - { - // Example: 2301 - 2000 = 301; 301 / 2301 = 0.1308 * 100 = 13% compacting - _cache.Compact((((double)(currentCacheSize - CacheSize) / (double)currentCacheSize) * 100)); - } - finally - { - // Reset _inTrim flag - Interlocked.CompareExchange(ref _inTrim, 0, 1); - } + return; + } + + try + { + // Example: 2301 - 2000 = 301; 301 / 2301 = 0.1308 * 100 = 13% compacting + _cache.Compact((double)(currentCacheSize - CacheSize) / currentCacheSize * 100); + } + finally + { + Interlocked.Exchange(ref _inTrim, 0); } } - private string GetCacheLookupKey(string masterKeyPath, bool allowEnclaveComputations, byte[] signature, string keyStoreName) + /// + /// Generates a cache key for the given CMK metadata and signature. The key is a + /// concatenation of the key store name, master key path, allowEnclaveComputations value, and signature, separated by a delimiter. + /// + /// The master key path. + /// Whether enclave computations are allowed. + /// The signature. + /// The key store name. + /// A string that can be used as a cache key. + private static string GetCacheLookupKey(string masterKeyPath, bool allowEnclaveComputations, byte[] signature, string keyStoreName) { - StringBuilder cacheLookupKeyBuilder = new StringBuilder(keyStoreName, - capacity: - keyStoreName.Length + - masterKeyPath.Length + - SqlSecurityUtility.GetBase64LengthFromByteLength(signature.Length) + - 3 /*separators*/ + - 10 /*boolean value + somebuffer*/); - - cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator); - cacheLookupKeyBuilder.Append(masterKeyPath); - cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator); - cacheLookupKeyBuilder.Append(allowEnclaveComputations); - cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator); - cacheLookupKeyBuilder.Append(Convert.ToBase64String(signature)); - cacheLookupKeyBuilder.Append(_cacheLookupKeySeparator); - string cacheLookupKey = cacheLookupKeyBuilder.ToString(); - return cacheLookupKey; + int cacheCapacity = + keyStoreName.Length + + masterKeyPath.Length + + SqlSecurityUtility.GetBase64LengthFromByteLength(signature.Length) + + 4 * _cacheLookupKeySeparator.Length + + 10 /* boolean value + buffer */; + + return new StringBuilder(keyStoreName, capacity: cacheCapacity) + .Append(_cacheLookupKeySeparator) + .Append(masterKeyPath) + .Append(_cacheLookupKeySeparator) + .Append(allowEnclaveComputations) + .Append(_cacheLookupKeySeparator) + .Append(Convert.ToBase64String(signature)) + .Append(_cacheLookupKeySeparator) + .ToString(); } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs index 253ab92db8..79ebc60d27 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs @@ -332,18 +332,20 @@ internal static void VerifyColumnMasterKeySignature(string keyStoreName, string } else { - bool signatureVerificationResult = ColumnMasterKeyMetadataSignatureVerificationCache.GetSignatureVerificationResult(keyStoreName, keyPath, isEnclaveEnabled, CMKSignature); - if (signatureVerificationResult == false) - { - // We will simply bubble up the exception from VerifyColumnMasterKeyMetadata function. - isValidSignature = provider.VerifyColumnMasterKeyMetadata(keyPath, isEnclaveEnabled, - CMKSignature); + SignatureVerificationResult cachedResult = ColumnMasterKeyMetadataSignatureVerificationCache.Instance + .GetSignatureVerificationResult(keyStoreName, keyPath, isEnclaveEnabled, CMKSignature); - ColumnMasterKeyMetadataSignatureVerificationCache.AddSignatureVerificationResult(keyStoreName, keyPath, isEnclaveEnabled, CMKSignature, isValidSignature); + if (cachedResult == SignatureVerificationResult.NotFound) + { + // Cache miss: verify with the provider and cache the result. + // Exceptions from VerifyColumnMasterKeyMetadata bubble up to the outer catch. + isValidSignature = provider.VerifyColumnMasterKeyMetadata(keyPath, isEnclaveEnabled, CMKSignature); + ColumnMasterKeyMetadataSignatureVerificationCache.Instance + .AddSignatureVerificationResult(keyStoreName, keyPath, isEnclaveEnabled, CMKSignature, isValidSignature); } else { - isValidSignature = signatureVerificationResult; + isValidSignature = cachedResult == SignatureVerificationResult.True; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SignatureVerificationCacheTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SignatureVerificationCacheTests.cs new file mode 100644 index 0000000000..a03dd8910e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SignatureVerificationCacheTests.cs @@ -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. + +using System; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests +{ + public class SignatureVerificationCacheTests + { + [Fact] + public void GetSignatureVerificationResult_ReturnsFalseForCachedFailure() + { + ColumnMasterKeyMetadataSignatureVerificationCache cache = ColumnMasterKeyMetadataSignatureVerificationCache.Instance; + string keyStoreName = $"TEST_PROVIDER_{Guid.NewGuid():N}"; + string masterKeyPath = $"https://unit-test/{Guid.NewGuid():N}"; + byte[] signature = [1, 2, 3, 4]; + + cache.AddSignatureVerificationResult(keyStoreName, masterKeyPath, allowEnclaveComputations: true, signature, result: false); + + Assert.Equal(SignatureVerificationResult.False, cache.GetSignatureVerificationResult(keyStoreName, masterKeyPath, allowEnclaveComputations: true, signature)); + } + + [Fact] + public void GetSignatureVerificationResult_ReturnsTrueForCachedSuccess() + { + ColumnMasterKeyMetadataSignatureVerificationCache cache = ColumnMasterKeyMetadataSignatureVerificationCache.Instance; + string keyStoreName = $"TEST_PROVIDER_{Guid.NewGuid():N}"; + string masterKeyPath = $"https://unit-test/{Guid.NewGuid():N}"; + byte[] signature = [4, 3, 2, 1]; + + cache.AddSignatureVerificationResult(keyStoreName, masterKeyPath, allowEnclaveComputations: true, signature, result: true); + + Assert.Equal(SignatureVerificationResult.True, cache.GetSignatureVerificationResult(keyStoreName, masterKeyPath, allowEnclaveComputations: true, signature)); + } + + [Fact] + public void GetSignatureVerificationResult_ReturnsNotFoundForCacheMiss() + { + ColumnMasterKeyMetadataSignatureVerificationCache cache = ColumnMasterKeyMetadataSignatureVerificationCache.Instance; + string keyStoreName = $"TEST_PROVIDER_{Guid.NewGuid():N}"; + string masterKeyPath = $"https://unit-test/{Guid.NewGuid():N}"; + byte[] signature = [9, 9, 9, 9]; + + Assert.Equal(SignatureVerificationResult.NotFound, cache.GetSignatureVerificationResult(keyStoreName, masterKeyPath, allowEnclaveComputations: true, signature)); + } + } +} \ No newline at end of file From 7721c98e8900291dd879e6ef7d8d42f4c2092842 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Fri, 5 Jun 2026 14:20:05 -0700 Subject: [PATCH 130/298] Actions | Introduce auto-assign-pr workflow (#4275) --- .github/scripts/auto-assign-pr.js | 133 +++++++++++++++++++++++++++ .github/workflows/auto-assign-pr.yml | 38 ++++++++ 2 files changed, 171 insertions(+) create mode 100644 .github/scripts/auto-assign-pr.js create mode 100644 .github/workflows/auto-assign-pr.yml diff --git a/.github/scripts/auto-assign-pr.js b/.github/scripts/auto-assign-pr.js new file mode 100644 index 0000000000..0f2e77d3c1 --- /dev/null +++ b/.github/scripts/auto-assign-pr.js @@ -0,0 +1,133 @@ +// Auto-assign PR load balancer. +// +// Selects up to 2 assignees for a qualifying PR from a configurable pool, +// balancing by current open-PR assignment count. Invoked by the +// `auto-assign-pr.yml` workflow via `actions/github-script`. +module.exports = async ({ github, context, core }) => { + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = context.issue.number; + const author = context.payload.pull_request.user.login; + const normalizeLogin = login => login.toLowerCase(); + const parseCsvLogins = value => (value ?? '') + .split(',') + .map(entry => entry.trim()) + .filter(entry => entry.length > 0); + + // Fallback pool keeps behavior unchanged when no repo variable is configured. + const defaultPool = ['cheenamalhotra', 'paulmedynski', 'priyankatiwari08', 'benrr101', 'mdaigle', 'apoorvdeshmukh']; + const configuredPool = parseCsvLogins(process.env.PR_REVIEWER_POOL); + const rawPool = configuredPool.length > 0 ? configuredPool : defaultPool; + const seenPoolUsers = new Set(); + const pool = []; + for (const user of rawPool) { + const normalized = normalizeLogin(user); + if (!seenPoolUsers.has(normalized)) { + seenPoolUsers.add(normalized); + pool.push(user); + } + } + + let latestPr; + try { + const response = await github.rest.pulls.get({ + owner, + repo, + pull_number: prNumber + }); + latestPr = response.data; + } catch (error) { + throw new Error(`Failed to fetch latest PR details: ${error.message}`); + } + + if (latestPr.state !== 'open' || latestPr.draft || !latestPr.milestone) { + console.log('PR is no longer assignment-eligible (not open, draft, or missing milestone).'); + return; + } + + const currentAssignees = (latestPr.assignees ?? []).map(a => a.login); + + console.log(`PR Author: ${author}`); + console.log(`Event Name: ${context.eventName}; Is Fork PR: ${context.payload.pull_request.head.repo.fork === true}`); + console.log(`Current Assignees: ${currentAssignees.join(', ')}`); + + if (currentAssignees.length >= 2) { + console.log('PR already has 2 or more assignees. No action needed.'); + return; + } + + const neededAssigneesCount = 2 - currentAssignees.length; + + const candidates = pool.filter(user => + normalizeLogin(user) !== normalizeLogin(author) && + !currentAssignees.some(a => normalizeLogin(a) === normalizeLogin(user)) + ); + + if (candidates.length === 0) { + console.log('No valid candidates left in the pool.'); + return; + } + + const workloads = {}; + const canonicalCandidateByNormalized = {}; + candidates.forEach(user => { + const normalized = normalizeLogin(user); + workloads[normalized] = 0; + canonicalCandidateByNormalized[normalized] = user; + }); + + try { + // Rank candidates by current assignment count across all open PRs. + const iterator = github.paginate.iterator(github.rest.pulls.list, { + owner, + repo, + state: 'open', + per_page: 100 + }); + + for await (const response of iterator) { + for (const pr of response.data) { + if (pr.draft) continue; + if (pr.assignees) { + for (const assignee of pr.assignees) { + const login = normalizeLogin(assignee.login); + if (workloads[login] !== undefined) { + workloads[login]++; + } + } + } + } + } + } catch (error) { + throw new Error(`Failed to fetch open PRs for auto-assignment: ${error.message}`); + } + + const workloadArray = candidates.map(user => { + const normalized = normalizeLogin(user); + return { user: canonicalCandidateByNormalized[normalized], count: workloads[normalized] }; + }); + console.log('Current Workloads:', workloadArray); + + // Shuffle before sorting so ties are broken fairly instead of favoring pool order. + for (let i = workloadArray.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [workloadArray[i], workloadArray[j]] = [workloadArray[j], workloadArray[i]]; + } + + workloadArray.sort((a, b) => a.count - b.count); + + const selectedAssignees = workloadArray.slice(0, neededAssigneesCount).map(w => w.user); + console.log(`Selected candidates: ${selectedAssignees.join(', ')}`); + + if (selectedAssignees.length === 0) { + console.log('No assignees selected. No action needed.'); + return; + } + + await github.rest.issues.addAssignees({ + owner, + repo, + issue_number: prNumber, + assignees: selectedAssignees + }); +}; diff --git a/.github/workflows/auto-assign-pr.yml b/.github/workflows/auto-assign-pr.yml new file mode 100644 index 0000000000..ba5654969f --- /dev/null +++ b/.github/workflows/auto-assign-pr.yml @@ -0,0 +1,38 @@ +name: Auto Assign PR Load Balancer + +on: + pull_request_target: + types: [opened, reopened, ready_for_review, milestoned] + +concurrency: + group: auto-assign-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + load-balance-assignees: + # Only run for assignment-eligible PRs in the upstream repository. Use + # pull_request_target so the workflow only runs once per PR event. + # Exclude PRs from forks as GitHub Actions does not have permissions to assign users on PRs from forks. + if: github.repository == 'dotnet/SqlClient' && github.event.pull_request.state == 'open' && github.event.pull_request.draft == false && github.event.pull_request.milestone != null && github.event.pull_request.head.repo.fork == false + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + env: + PR_REVIEWER_POOL: ${{ vars.PR_REVIEWER_POOL }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + sparse-checkout: | + .github/scripts/auto-assign-pr.js + sparse-checkout-cone-mode: false + persist-credentials: false + + - name: Calculate Workload and Apply + uses: actions/github-script@v9 + with: + script: | + const script = require('./.github/scripts/auto-assign-pr.js'); + await script({ github, context, core }); From 793d5ad1c6f32f5bc6b9869e20d9110bf9adeec5 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Mon, 8 Jun 2026 04:26:11 -0500 Subject: [PATCH 131/298] Move Test Config to Jsonc (#4297) --- .devcontainer/setup-sqlserver.sh | 6 +- .github/instructions/testing.instructions.md | 10 +- .gitignore | 1 + TESTGUIDE.md | 22 ++- .../templates/jobs/ci-run-tests-job.yml | 2 +- .../steps/update-config-file-step.yml | 12 +- eng/pipelines/dotnet-sqlclient-ci-core.yml | 2 +- .../jobs/test-azure-package-ci-job.yml | 2 +- eng/pipelines/kerberos/sqlclient-kerberos.yml | 12 +- eng/pipelines/stress/stress-tests-job.yml | 6 +- .../Azure/test/Config.cs | 16 +-- ...icrosoft.Data.SqlClient.ManualTests.csproj | 2 + .../SqlDbManager.cs | 6 +- .../Config.cs | 131 +++++++++++------- ...rosoft.Data.SqlClient.TestUtilities.csproj | 13 +- ...nfig.default.json => config.default.jsonc} | 0 16 files changed, 139 insertions(+), 104 deletions(-) rename src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/{config.default.json => config.default.jsonc} (100%) diff --git a/.devcontainer/setup-sqlserver.sh b/.devcontainer/setup-sqlserver.sh index 6b4ee480ed..fc962c3053 100755 --- a/.devcontainer/setup-sqlserver.sh +++ b/.devcontainer/setup-sqlserver.sh @@ -29,11 +29,11 @@ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" # Write test config file so the test suite can find the connection string. CONFIG_DIR="${REPO_ROOT}/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities" -CONFIG_DEFAULT_FILE="${CONFIG_DIR}/config.default.json" -CONFIG_FILE="${CONFIG_DIR}/config.json" +CONFIG_DEFAULT_FILE="${CONFIG_DIR}/config.default.jsonc" +CONFIG_FILE="${CONFIG_DIR}/config.jsonc" echo "Writing test config to ${CONFIG_FILE} (based on ${CONFIG_DEFAULT_FILE})..." TCP_CONN_STR="Data Source=tcp:${SQL_HOST},${SQL_PORT};Database=Northwind;User Id=sa;Password=${SA_PASSWORD};Encrypt=false;TrustServerCertificate=true" -# config.default.json contains JS-style comments (// ...) which are not valid JSON. +# config.default.jsonc contains JS-style comments (// ...) which are not valid JSON. # Strip single-line comments before feeding to jq. sed 's|//.*||' "${CONFIG_DEFAULT_FILE}" \ | jq --arg cs "${TCP_CONN_STR}" \ diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index 20fe03b1e8..bd9db72c4a 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -14,8 +14,8 @@ src/Microsoft.Data.SqlClient/tests/ ├── UnitTests/ # Unit tests with minimal dependencies └── tools/ └── Microsoft.Data.SqlClient.TestUtilities/ - ├── config.default.json # Template configuration - └── config.json # Local test configuration (git-ignored) + ├── config.default.jsonc # Template configuration + └── config.jsonc # Local test configuration (git-ignored) ``` ## Test Categories @@ -34,14 +34,14 @@ src/Microsoft.Data.SqlClient/tests/ ### Manual Tests (`ManualTests/`) - Full integration tests with SQL Server -- Require `config.json` setup +- Require `config.jsonc` setup - Test real database operations - Include Always Encrypted, Entra ID tests ## Test Configuration -### Setting Up `config.json` -Copy `config.default.json` to `config.json` and configure: +### Setting Up `config.jsonc` +Copy `config.default.jsonc` to `config.jsonc` and configure: ```json { diff --git a/.gitignore b/.gitignore index 37b3509b0d..06d3a36078 100644 --- a/.gitignore +++ b/.gitignore @@ -136,6 +136,7 @@ node_modules/ # Config Json file **/config.json +**/config.jsonc # Generated Milestone PR metadata files .milestone-prs/ diff --git a/TESTGUIDE.md b/TESTGUIDE.md index aab773a080..791d4a7bcc 100644 --- a/TESTGUIDE.md +++ b/TESTGUIDE.md @@ -195,22 +195,22 @@ conditional tests are skipped. ## Manual Test Configuration Edit the source configuration file at `src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/ -config.json`. The test utilities project copies that file to the test output directory, where the manual tests load it +config.jsonc`. The test utilities project copies that file to the test output directory, where the manual tests load it by default. The template file is: -[src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json](src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json) +[src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.jsonc](src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.jsonc) -`config.json` is git-ignored. If it does not exist, the test utilities project copies `config.default.json` to -`config.json` before compile. You can also create it manually: +`config.jsonc` is git-ignored. If it does not exist, the test utilities project copies `config.default.jsonc` to +`config.jsonc` before compile. You can also create it manually: ```bash -cp src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json \ - src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.json +cp src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.jsonc \ + src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.jsonc ``` -Update `config.json` for your environment before running manual tests. The most important values for a basic run are `TCPConnectionString` and `NPConnectionString`. +Update `config.jsonc` for your environment before running manual tests. The most important values for a basic run are `TCPConnectionString` and `NPConnectionString`. ```jsonc { @@ -230,16 +230,16 @@ For SQL Server in a Linux container, WSL, or another host where SQL authenticati } ``` -You can override the config file path with the `MDS_TEST_CONFIG` environment variable: +You can override the config file path with the `TEST_MDS_CONFIG` environment variable: ```bash -MDS_TEST_CONFIG=/path/to/config.json dotnet build -t:TestSqlClientManual -p:TestSet=2 +TEST_MDS_CONFIG=/path/to/config.jsonc dotnet build -t:TestSqlClientManual -p:TestSet=2 ``` On PowerShell: ```powershell -$env:MDS_TEST_CONFIG = "C:\path\to\config.json" +$env:TEST_MDS_CONFIG = "C:\path\to\config.jsonc" dotnet build -t:TestSqlClientManual -p:TestSet=2 ``` @@ -263,7 +263,6 @@ dotnet build -t:TestSqlClientManual -p:TestSet=2 | `SupportsIntegratedSecurity` | Whether the user running tests has integrated-security access to the target SQL Server. | `true` or `false`. | | `LocalDbAppName` | Optional LocalDB instance name. Empty disables LocalDB testing. | `MSSQLLocalDB` or another local instance. | | `LocalDbSharedInstanceName` | Optional shared LocalDB instance name. | Used only when testing shared LocalDB. | -| `SupportsFileStream` | Whether FileStream tests are supported by the target. | `true` or `false`. | | `FileStreamDirectory` | Directory used for FileStream database setup. | Use an escaped absolute path in JSON. | | `UseManagedSNIOnWindows` | Enables Managed SNI on Windows test coverage. | `true` or `false`. | | `DNSCachingConnString` | Optional connection string for DNS caching tests. | Used with DNS caching server settings. | @@ -271,7 +270,6 @@ dotnet build -t:TestSqlClientManual -p:TestSet=2 | `DNSCachingServerTR` | Optional DNS caching tenant-ring server. | Feature-specific tests only. | | `IsDNSCachingSupportedCR` | Enables DNS caching control-ring tests. | `true` or `false`. | | `IsDNSCachingSupportedTR` | Enables DNS caching tenant-ring tests. | `true` or `false`. | -| `IsAzureSynapse` | Marks the target as Azure Synapse. | Some SQL Server-specific tests are skipped when `true`. | | `EnclaveAzureDatabaseConnString` | Optional Azure SQL database connection string for enclave tests. | Feature-specific tests only. | | `ManagedIdentitySupported` | Whether managed identity tests should run. | Defaults to `true`. Set `false` if unavailable. | | `UserManagedIdentityClientId` | Optional client ID for user-assigned managed identity tests. | Feature-specific tests only. | 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 5eed840236..1b1ea7a2e7 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -234,7 +234,7 @@ jobs: referenceType: Project - ${{ if ne(parameters.configProperties, '{}') }}: - - template: /eng/pipelines/common/templates/steps/update-config-file-step.yml@self # update config.json file + - template: /eng/pipelines/common/templates/steps/update-config-file-step.yml@self # update config.jsonc file parameters: debug: ${{ parameters.debug }} saPassword: ${{ parameters.saPassword }} 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 b0612fa248..fd57828df0 100644 --- a/eng/pipelines/common/templates/steps/update-config-file-step.yml +++ b/eng/pipelines/common/templates/steps/update-config-file-step.yml @@ -143,9 +143,9 @@ steps: Write-Host "##vso[task.setvariable variable=Password;isSecret=true]$password" displayName: Set Connection String Password - # All properties should be added here, and this template should be used for any manipulation of the config.json file. + # All properties should be added here, and this template should be used for any manipulation of the config.jsonc file. - pwsh: | - $jdata = Get-Content -Raw "config.default.json" | ConvertFrom-Json + $jdata = Get-Content -Raw "config.default.jsonc" | ConvertFrom-Json foreach ($p in $jdata) { $p.TCPConnectionString="${{parameters.TCPConnectionString }}" @@ -197,13 +197,13 @@ steps: $p.EnclaveEnabled=[System.Convert]::ToBoolean("${{parameters.EnclaveEnabled }}") $p.WorkloadIdentityFederationServiceConnectionId="${{parameters.WorkloadIdentityFederationServiceConnectionId }}" } - $jdata | ConvertTo-Json | Set-Content "config.json" + $jdata | ConvertTo-Json | Set-Content "config.jsonc" workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities - displayName: 'Update config.json' + displayName: 'Update config.jsonc' - ${{ if eq(parameters.debug, true) }}: - pwsh: | - $jdata = Get-Content -Raw "config.json" | ConvertFrom-Json + $jdata = Get-Content -Raw "config.jsonc" | ConvertFrom-Json foreach ($p in $jdata) { foreach ($prop in $p.PSObject.Properties) @@ -212,4 +212,4 @@ steps: } } workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities - displayName: '[Debug] Emit config.json' + displayName: '[Debug] Emit config.jsonc' diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index f5f2269494..6c4399c9cb 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -371,7 +371,7 @@ stages: useManagedSNI: ${{parameters.useManagedSNI }} configSqlFor: local operatingSystem: Windows - # config.json properties + # config.jsonc properties configProperties: TCPConnectionString: $(SQL_TCP_CONN_STRING) NPConnectionString: $(SQL_NP_CONN_STRING) diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 42097daa42..eb9e36152a 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -261,7 +261,7 @@ jobs: debug: ${{ parameters.debug }} saPassword: ${{ parameters.saPassword }} - # The config.json file has many options, but only some of them are + # The config.jsonc file has many options, but only some of them are # used by the Azure package tests. We only specify the ones that are # necessary here. diff --git a/eng/pipelines/kerberos/sqlclient-kerberos.yml b/eng/pipelines/kerberos/sqlclient-kerberos.yml index f43ee0a280..a8c4463c13 100644 --- a/eng/pipelines/kerberos/sqlclient-kerberos.yml +++ b/eng/pipelines/kerberos/sqlclient-kerberos.yml @@ -141,16 +141,16 @@ stages: # requires compile-time parameters. - pwsh: | $managedSni = [System.Convert]::ToBoolean($env:MANAGED_SNI) - $jdata = Get-Content -Raw "config.default.json" | ConvertFrom-Json + $jdata = Get-Content -Raw "config.default.jsonc" | ConvertFrom-Json foreach ($p in $jdata) { $p.TCPConnectionString = $env:REMOTE_TCP_CONN_STRING $p.NPConnectionString = $env:REMOTE_NP_CONN_STRING $p.SupportsIntegratedSecurity = $true $p.UseManagedSNIOnWindows = $managedSni } - $jdata | ConvertTo-Json | Set-Content "config.json" + $jdata | ConvertTo-Json | Set-Content "config.jsonc" workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities - displayName: Update test config.json + displayName: Update test config.jsonc env: REMOTE_TCP_CONN_STRING: $(REMOTE_TCP_CONN_STRING) REMOTE_NP_CONN_STRING: $(REMOTE_NP_CONN_STRING) @@ -234,7 +234,7 @@ stages: # --- Update test configuration (with Kerberos credentials) --- - pwsh: | - $jdata = Get-Content -Raw "config.default.json" | ConvertFrom-Json + $jdata = Get-Content -Raw "config.default.jsonc" | ConvertFrom-Json foreach ($p in $jdata) { $p.TCPConnectionString = $env:REMOTE_TCP_CONN_STRING $p.NPConnectionString = $env:REMOTE_NP_CONN_STRING @@ -242,9 +242,9 @@ stages: } $jdata | Add-Member -NotePropertyName "KerberosDomainUser" -NotePropertyValue $env:KERBEROS_DOMAIN_USER -Force $jdata | Add-Member -NotePropertyName "KerberosDomainPassword" -NotePropertyValue $env:KERBEROS_DOMAIN_PASSWORD -Force - $jdata | ConvertTo-Json | Set-Content "config.json" + $jdata | ConvertTo-Json | Set-Content "config.jsonc" workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities - displayName: Update test config.json (Kerberos) + displayName: Update test config.jsonc (Kerberos) env: REMOTE_TCP_CONN_STRING: $(REMOTE_TCP_CONN_STRING) REMOTE_NP_CONN_STRING: $(REMOTE_NP_CONN_STRING) diff --git a/eng/pipelines/stress/stress-tests-job.yml b/eng/pipelines/stress/stress-tests-job.yml index ac82002ece..2633445214 100644 --- a/eng/pipelines/stress/stress-tests-job.yml +++ b/eng/pipelines/stress/stress-tests-job.yml @@ -164,7 +164,7 @@ jobs: "@ # Write the JSON content to the config file. - $content | Out-File -FilePath "config.json" + $content | Out-File -FilePath "config.jsonc" # Authenticate with NuGet feeds so that upstream packages (e.g. runtime host packs) can be # fetched through the ADO Artifacts feed. This is required on hosted pool agents (macOS) @@ -208,7 +208,7 @@ jobs: inputs: command: run projects: $(project) - arguments: $(runArguments) --no-build -f ${{ runtime }} -e STRESS_CONFIG_FILE=config.json -- $(testArguments) + arguments: $(runArguments) --no-build -f ${{ runtime }} -e STRESS_CONFIG_FILE=config.jsonc -- $(testArguments) # Run the stress tests for each .NET Framework runtime. - ${{ each runtime in parameters.netFrameworkTestRuntimes }}: @@ -219,4 +219,4 @@ jobs: inputs: command: run projects: $(project) - arguments: $(runArguments) --no-build -f ${{ runtime }} -e STRESS_CONFIG_FILE=config.json -- $(testArguments) + arguments: $(runArguments) --no-build -f ${{ runtime }} -e STRESS_CONFIG_FILE=config.jsonc -- $(testArguments) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs index f153096d9f..cec4721b95 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs @@ -10,9 +10,9 @@ namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; /// /// This class reads configuration information from environment variables and -/// the config.json file for use by our tests. +/// the config.jsonc file for use by our tests. /// -/// Environment variables take precedence over config.json settings. Note that +/// Environment variables take precedence over config.jsonc settings. Note that /// variable names are case-sensitive on non-Windows platforms. /// /// The following variables are supported: @@ -30,7 +30,7 @@ namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; /// TEST_MDS_CONFIG: /// The path to the config file to use instead of the default. If not /// supplied, the config file is assumed to be located next to the test -/// assembly and is named config.json. +/// assembly and is named config.jsonc. /// internal static class Config { @@ -86,13 +86,13 @@ internal static bool IsAzureSqlServer() => /// static Config() { - // Read from the config.json file. If the TEST_MDS_CONFIG environment + // Read from the config.jsonc file. If the TEST_MDS_CONFIG environment // variable is set, use it. Otherwise, assume the config file is in the - // working directory and named config.json. + // working directory and named config.jsonc. string configPath = GetEnvVar("TEST_MDS_CONFIG"); if (configPath.IsEmpty()) { - configPath = "config.json"; + configPath = "config.jsonc"; } try @@ -109,10 +109,10 @@ static Config() JsonElement root = doc.RootElement; // See the sample config file for information about these settings: // - // src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json + // src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.jsonc // // The sample file is copied to the build output directory as - // config.json by the TestUtilities project file. + // config.jsonc by the TestUtilities project file. // IntegratedSecuritySupported = GetBool(root, "SupportsIntegratedSecurity"); ManagedIdentitySupported = GetBool(root, "ManagedIdentitySupported"); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 83dd4a8351..d47ec611de 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -361,6 +361,7 @@ + @@ -388,6 +389,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs index 52bf5c30f4..6b229d5bc0 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/SqlDbManager.cs @@ -19,7 +19,7 @@ public static class SqlDbManager private const string DB_Northwind = "Northwind"; private const string DB_Master = "master"; private const string NorthWindScriptPath = @"../../../../../tools/testsql/createNorthwindDb.sql"; - private const string ConfigPath = @"../Microsoft.Data.SqlClient.TestUtilities/config.json"; + private const string ConfigPath = @"../Microsoft.Data.SqlClient.TestUtilities/config.jsonc"; private const string TCPConnectionString = "TCPConnectionString"; private const string NPConnectionString = "NPConnectionString"; @@ -68,7 +68,7 @@ public static void Run(string[] args) CreateDatabase(dbName, context); Console.WriteLine($"Database [{dbName}] created successfully in {builder.DataSource}"); } - // Update Config.json accordingly + // Update config.jsonc accordingly builder.InitialCatalog = dbName; UpdateConfig(activeConnString.Key, builder); } @@ -96,7 +96,7 @@ public static void Run(string[] args) } if (args[0] == "CreateDatabase") { - // Update config.json with Initial Catalog = for "Active Connection Strings" + // Update config.jsonc with Initial Catalog = for "Active Connection Strings" Config.UpdateConfig(s_configJson, ConfigPath); } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs index 24c9c4f884..6b1dda8ae0 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs @@ -4,71 +4,73 @@ using System; using System.IO; -using Newtonsoft.Json; +using System.Text.Json; + +#nullable enable namespace Microsoft.Data.SqlClient.TestUtilities { public class Config { - public string TCPConnectionString = null; - public string NPConnectionString = null; - public string TCPConnectionStringHGSVBS = null; - public string TCPConnectionStringNoneVBS = null; - public string TCPConnectionStringAASSGX = null; + private static readonly JsonSerializerOptions JsonSerializerOptions = new() + { + AllowTrailingCommas = true, + IncludeFields = true, + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip, + }; + + public string? TCPConnectionString = null; + public string? NPConnectionString = null; + public string? TCPConnectionStringHGSVBS = null; + public string? TCPConnectionStringNoneVBS = null; + public string? TCPConnectionStringAASSGX = null; public bool EnclaveEnabled = false; public bool TracingEnabled = false; - public string AADAuthorityURL = null; - public string AADPasswordConnectionString = null; - public string AADServicePrincipalId = null; - public string AADServicePrincipalSecret = null; - public string AzureKeyVaultURL = null; - public string AzureKeyVaultTenantId = null; + public string? AADAuthorityURL = null; + public string? AADPasswordConnectionString = null; + public string? AADServicePrincipalId = null; + public string? AADServicePrincipalSecret = null; + public string? AzureKeyVaultURL = null; + public string? AzureKeyVaultTenantId = null; public bool SupportsIntegratedSecurity = false; - public string LocalDbAppName = null; - public string LocalDbSharedInstanceName = null; - public string FileStreamDirectory = null; + public string? LocalDbAppName = null; + public string? LocalDbSharedInstanceName = null; + public string? FileStreamDirectory = null; public bool UseManagedSNIOnWindows = false; - public string DNSCachingConnString = null; - public string DNSCachingServerCR = null; // this is for the control ring - public string DNSCachingServerTR = null; // this is for the tenant ring + public string? DNSCachingConnString = null; + public string? DNSCachingServerCR = null; // this is for the control ring + public string? DNSCachingServerTR = null; // this is for the tenant ring public bool IsDNSCachingSupportedCR = false; // this is for the control ring public bool IsDNSCachingSupportedTR = false; // this is for the tenant ring - public string EnclaveAzureDatabaseConnString = null; + public string? EnclaveAzureDatabaseConnString = null; public bool ManagedIdentitySupported = true; - public string UserManagedIdentityClientId = null; - public string PowerShellPath = null; - public string AliasName = null; - public string KerberosDomainPassword = null; - public string KerberosDomainUser = null; + public string? UserManagedIdentityClientId = null; + public string? PowerShellPath = null; + public string? AliasName = null; + public string? KerberosDomainPassword = null; + public string? KerberosDomainUser = null; public bool IsManagedInstance = false; - public static Config Load(string configPath = @"config.json") + public static Config Load(string configPath) { - // Allow an override of the config path via an environment variable. - configPath = Environment.GetEnvironmentVariable("TEST_MDS_CONFIG") ?? configPath; + Config config = LoadInternal(Environment.GetEnvironmentVariable("TEST_MDS_CONFIG")) ?? + LoadInternal(configPath) ?? + throw new FileNotFoundException("Could not find test configuration file."); - Config config; - try - { - using (StreamReader r = new StreamReader(configPath)) - { - config = JsonConvert.DeserializeObject(r.ReadToEnd()) - ?? throw new InvalidOperationException($"Failed to deserialize config from '{configPath}'."); - } - } - catch - { - throw; - } + // Allow environment variables to override individual config values. + SetFromEnv("MDS_TCPConnectionString", ref config.TCPConnectionString); - static void SetFromEnv(string envVar, ref string configValue) - { - string envValue = Environment.GetEnvironmentVariable(envVar); - if (!string.IsNullOrEmpty(envValue)) - { - configValue = envValue; - } - } + return config; + } + + public static Config Load() + { + // Load config from environment variable first, jsonc file second, json file last. + Config config = LoadInternal(Environment.GetEnvironmentVariable("TEST_MDS_CONFIG")) ?? + LoadInternal("config.jsonc") ?? + LoadInternal("config.json") ?? + throw new FileNotFoundException("Could not find test configuration file."); // Allow environment variables to override individual config values. SetFromEnv("MDS_TCPConnectionString", ref config.TCPConnectionString); @@ -76,10 +78,39 @@ static void SetFromEnv(string envVar, ref string configValue) return config; } - public static void UpdateConfig(Config updatedConfig, string configPath = @"config.json") + public static void UpdateConfig(Config updatedConfig, string configPath = @"config.jsonc") { - string config = JsonConvert.SerializeObject(updatedConfig); + string config = JsonSerializer.Serialize(updatedConfig, JsonSerializerOptions); File.WriteAllText(configPath, config); } + + private static Config? LoadInternal(string? configPath) + { + if (configPath is null) + { + return null; + } + + try + { + using StreamReader sr = new StreamReader(configPath); + return JsonSerializer.Deserialize(sr.ReadToEnd(), JsonSerializerOptions) ?? + throw new InvalidOperationException($"Failed to deserialize config from '{configPath}'"); + } + catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException) + { + // File did not exist at the path given. We will try a different location. + return null; + } + } + + private static void SetFromEnv(string envVar, ref string? configValue) + { + string? envValue = Environment.GetEnvironmentVariable(envVar); + if (!string.IsNullOrEmpty(envValue)) + { + configValue = envValue; + } + } } } 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 c50a251e4f..05444fd022 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 @@ -10,13 +10,16 @@ $(TestSigningKeyPath) + - + - - PreserveNewest - - + + + + + + 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.jsonc similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json rename to src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.jsonc From 29a9b2a87db0417b6eddb5d490894dcbf0735417 Mon Sep 17 00:00:00 2001 From: priyankatiwari08 Date: Mon, 8 Jun 2026 14:57:57 +0530 Subject: [PATCH 132/298] Enforce `gh aw compile` + lock-file regeneration for agentic workflows (#4298) --- .../agentic-workflows.instructions.md | 49 +++++++++++++++++++ .github/workflows/verify-aw-lock.yml | 32 ++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 .github/instructions/agentic-workflows.instructions.md create mode 100644 .github/workflows/verify-aw-lock.yml diff --git a/.github/instructions/agentic-workflows.instructions.md b/.github/instructions/agentic-workflows.instructions.md new file mode 100644 index 0000000000..6a42c0dce3 --- /dev/null +++ b/.github/instructions/agentic-workflows.instructions.md @@ -0,0 +1,49 @@ +--- +applyTo: ".github/workflows/**/*.md" +description: Rules for editing gh-aw agentic workflow Markdown files. +--- + +# Agentic Workflow Edit Rules (`gh aw`) + +This repository authors GitHub Actions agentic workflows in Markdown using +[`gh aw`](https://github.com/githubnext/gh-aw). Each workflow `.md` file under +`.github/workflows/` compiles to a sibling `.lock.yml`, and **only the +`.lock.yml` is executed by GitHub Actions at runtime.** + +## Mandatory rule + +Whenever you create, edit, rename, or delete a file matching +`.github/workflows/**/*.md`, you **MUST**, in the **same commit / PR**: + +1. Run `gh aw compile` from the repository root. +2. Stage and commit the regenerated sibling `.lock.yml`. +3. If you deleted a workflow `.md`, also delete its `.lock.yml`. + +If the `.lock.yml` is stale or missing, the workflow fails at runtime +(see PR #4279 for the exact failure mode). The +`Verify gh aw lock files` CI check will block the PR in that case. + +## How to verify locally + +```bash +gh aw compile +git status # both the .md and .lock.yml should appear +gh aw compile # second run must be a no-op (clean diff) +``` + +## Code-review checklist + +When reviewing a PR that touches `.github/workflows/**/*.md`: + +- [ ] A matching `.lock.yml` is updated in the same PR. +- [ ] `gh aw compile` produces no further diff on top of the PR. +- [ ] If new tools, network endpoints, or permissions are added in the `.md`, + they are present in the regenerated `.lock.yml`. + +## Out of scope + +- Do **not** hand-edit `.lock.yml` files. They are generated; edit the `.md` + source and recompile. +- For deeper authoring guidance (creating, debugging, upgrading workflows), + invoke the `agentic-workflows` agent at + `.github/agents/agentic-workflows.agent.md`. diff --git a/.github/workflows/verify-aw-lock.yml b/.github/workflows/verify-aw-lock.yml new file mode 100644 index 0000000000..1d37e610e8 --- /dev/null +++ b/.github/workflows/verify-aw-lock.yml @@ -0,0 +1,32 @@ +name: Verify gh aw lock files + +on: + pull_request: + paths: + - '.github/workflows/**/*.md' + - '.github/workflows/**/*.lock.yml' + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install gh-aw extension + uses: github/gh-aw-actions/setup-cli@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + with: + version: v0.72.1 + + - name: Recompile agentic workflows + run: gh aw compile + + - name: Fail if any .lock.yml is out of date + run: | + if ! git diff --exit-code -- '.github/workflows/**/*.lock.yml'; then + echo "::error::One or more .github/workflows/**/*.lock.yml files are out of date relative to their .md source (running 'gh aw compile' produced a diff)." + echo "::error::Run 'gh aw compile' locally and commit the regenerated .lock.yml files in this PR." + exit 1 + fi From 323aba913a1937acd25dc9cf4cd65b5a4f9af307 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 8 Jun 2026 07:47:01 -0300 Subject: [PATCH 133/298] Agents | Add Azure DevOps work item markdown formatting guide & PR feedback prompt (#4253) --- .../ado-work-items-markdown.instructions.md | 139 ++++++++++++++++++ .github/prompts/review-pr-feedback.prompt.md | 123 ++++++++++++++++ AGENTS.md | 10 ++ 3 files changed, 272 insertions(+) create mode 100644 .github/instructions/ado-work-items-markdown.instructions.md create mode 100644 .github/prompts/review-pr-feedback.prompt.md diff --git a/.github/instructions/ado-work-items-markdown.instructions.md b/.github/instructions/ado-work-items-markdown.instructions.md new file mode 100644 index 0000000000..2a70fd124a --- /dev/null +++ b/.github/instructions/ado-work-items-markdown.instructions.md @@ -0,0 +1,139 @@ +--- +applyTo: "**" +--- +# Azure DevOps Work Items: Markdown Description Rules + +Use this guide whenever creating or updating Azure DevOps work items that include rich text in `System.Description`. + +## Goals + +- Ensure descriptions render as Markdown (not HTML/plain text) +- Preserve newline characters and list structure +- Verify work items after every batch update + +## Required Behavior + +1. Always use `az rest` for description content-type changes. +2. Use `application/json-patch+json` for PATCH requests. +3. Set `multilineFieldsFormat.System.Description` to `markdown`. +4. Preserve exact newlines in the Markdown body. +5. Verify both format and newline integrity after updates. + +## Authentication and Resource + +Use Azure DevOps resource audience when calling `az rest`: + +- Resource: `499b84ac-1321-427f-aa17-267ca6975798` + +Example auth check: + +```bash +az rest \ + --method GET \ + --resource 499b84ac-1321-427f-aa17-267ca6975798 \ + --url "https://dev.azure.com//_apis/projects?api-version=7.1-preview.4" +``` + +Note: API versions can differ by endpoint. The examples below use `7.1-preview.3` for work item PATCH calls, while this auth check uses `7.1-preview.4`. + +## Safe Update Pattern (Prevents Type/Value Errors) + +Some work items reject a direct type switch unless a valid value is provided. Use this two-step process: + +### Step 1: Capture current description + +```bash +az boards work-item show --id | jq -j '.fields["System.Description"] // ""' > ./desc-backup.txt +``` + +> **NOTE**: Writing to a file preserves all characters including trailing newlines. +> Command substitution (`$(...)`) silently strips trailing newlines, which would +> corrupt multi-line Markdown content. + +### Step 2: Force markdown type with temporary empty value + +```bash +PATCH_STEP1_FILE="${PATCH_STEP1_FILE:-./patch-step1.json}" + +jq -n '[ + {"op":"replace","path":"/fields/System.Description","value":""}, + {"op":"replace","path":"/multilineFieldsFormat/System.Description","value":"markdown"} +]' >"$PATCH_STEP1_FILE" + +az rest \ + --method PATCH \ + --resource 499b84ac-1321-427f-aa17-267ca6975798 \ + --url "https://dev.azure.com///_apis/wit/workitems/?api-version=7.1-preview.3" \ + --headers "Content-Type=application/json-patch+json" \ + --body @"$PATCH_STEP1_FILE" +``` + +### Step 3: Restore exact Markdown text + +```bash +PATCH_STEP2_FILE="${PATCH_STEP2_FILE:-./patch-step2.json}" + +jq -n --rawfile d ./desc-backup.txt '[ + {"op":"replace","path":"/fields/System.Description","value":$d} +]' >"$PATCH_STEP2_FILE" + +az rest \ + --method PATCH \ + --resource 499b84ac-1321-427f-aa17-267ca6975798 \ + --url "https://dev.azure.com///_apis/wit/workitems/?api-version=7.1-preview.3" \ + --headers "Content-Type=application/json-patch+json" \ + --body @"$PATCH_STEP2_FILE" +``` + +## Newline Integrity Checks + +After updates, confirm newline characters are still present and structure was not flattened. + +### Check format and description sample + +```bash +az boards work-item show --id | jq '.multilineFieldsFormat, .fields["System.Description"][0:200]' +``` + +Expected: + +- `multilineFieldsFormat.System.Description == "markdown"` +- Description text contains `\n` where line breaks are expected + +### Check line count did not collapse + +```bash +az boards work-item show --id \ +| jq -r '.fields["System.Description"]' \ +| awk 'END { print NR }' +``` + +If a multi-line description unexpectedly returns `1`, newline content was likely lost. + +## Batch Verification Script + +Use this after bulk updates: + +```bash +python3 - <<'PY' +import json, subprocess +ids = [12345, 12346] # replace with your target IDs +bad = [] +for i in ids: + out = subprocess.check_output(["az", "boards", "work-item", "show", "--id", str(i)], text=True) + j = json.loads(out) + fmt = (j.get("multilineFieldsFormat") or {}).get("System.Description") + desc = j.get("fields", {}).get("System.Description") or "" + if fmt != "markdown" or "\n" not in desc: + bad.append((i, fmt, "has_newlines" if "\n" in desc else "missing_newlines")) +print("noncompliant:", len(bad)) +for row in bad: + print(row) +PY +``` + +## Common Failure Modes + +- `401` or `TF400813`: wrong token audience or insufficient auth context +- `Content-Type ... not supported`: must use `application/json-patch+json` +- `type changed without a value`: use two-step pattern (empty + markdown type, then restore text) diff --git a/.github/prompts/review-pr-feedback.prompt.md b/.github/prompts/review-pr-feedback.prompt.md new file mode 100644 index 0000000000..c14a7b96cb --- /dev/null +++ b/.github/prompts/review-pr-feedback.prompt.md @@ -0,0 +1,123 @@ +--- +name: review-pr-feedback +description: Uses gh CLI to collect unresolved PR review feedback, optionally includes discussion comments, applies fixes, and reports status. +argument-hint: pr= repo= includeDiscussionComments= authorFilter= testScope= +tools: ['edit/editFiles', 'edit/createFile', 'read/readFile', 'read/problems', 'search/codebase', 'search/textSearch', 'search/fileSearch', 'execute/runInTerminal', 'execute/getTerminalOutput'] +--- +You are an expert software maintenance agent focused on resolving pull request feedback quickly, safely, and with clear traceability. + +## Context +- Workspace root: ${workspaceFolder} +- Target PR: ${input:pr} +- Optional repository override: ${input:repo} +- Include non-review discussion comments: ${input:includeDiscussionComments} +- Optional author filter: ${input:authorFilter} +- Optional focused testing hint: ${input:testScope} +- Optional selected context: ${selection} + +## Skills +#skill:generate-mstest-filter + +Use this skill when building a dotnet test filter: +- [generate-mstest-filter](.github/skills/generate-mstest-filter/SKILL.md) + +Follow the referenced skill instructions before producing any custom filter. + +## Task +1. Validate prerequisites +- Confirm gh CLI is installed and authenticated. +- Resolve repository from ${input:repo}, or infer from git remote. +- Resolve PR number from ${input:pr} (accept number or URL). +- Discover the correct git remote name from the current repository and store it for later commands. +- Use that discovered remote name for push and any other git operations that require a remote; do not assume `origin`. + +2. Gather actionable review feedback +- Query PR review threads with gh api GraphQL. +- Keep only unresolved threads where isResolved is false. +- Extract thread id, file path, line/startLine, comment url, author login, and body. +- If ${input:authorFilter} is provided, apply it case-insensitively. + +3. Optionally gather non-review discussion comments +- If ${input:includeDiscussionComments} is true, fetch PR issue comments. +- Mark these as Informational because they do not have open/resolved state. +- Apply ${input:authorFilter} if provided. + +4. Build an implementation plan +- Group unresolved review feedback by file and risk. +- Determine minimal safe edits needed. +- Identify comments that are non-actionable or ambiguous. +- Ask the user to confirm the plan before proceeding, showing a concise summary of proposed changes and rationale. + +5. Implement and verify +- Apply required code or test updates with smallest safe change set. +- Run targeted checks first. +- If ${input:testScope} is provided, generate and use a focused MSTest filter via the skill. +- Collect diagnostics when tests cannot run. + +6. Classify each item +- Fixed: change implemented and validated. +- Needs Clarification: ambiguous, conflicting, or insufficiently specified. +- Blocked: external dependency, permission, or missing context. +- Informational: non-review discussion comment captured only. + +7. Produce a final report +- Keep review-thread outcomes and discussion outcomes in separate sections. +- Include evidence for each item: file location, change summary, validation result. +- Draft a distinct reply for each comment item that addresses that exact comment's request, context, and outcome. + +8. Commit changes +- If any changes were made, create a commit with a clear message referencing the PR and summarizing the resolution. +- Prompt the user to review and confirm the commit message before finalizing. +- When suggesting or performing a push, use the discovered git remote name. +- Prompt the user to push the commit if they have permissions, or provide instructions if they do not. +- Prompt the user to reply to each original PR comment with a comment-specific response and link to the relevant commit or code location, if appropriate. +- Prompt the user to mark review threads as resolved in GitHub if they have permissions, or provide instructions if they do not. + +## Output Format +1. PR Scope +- Repo +- PR number +- Unresolved review threads found +- Discussion comments found (if enabled) + +2. Unresolved Review Feedback (Actionable) +- Item: +- Location: : +- Author: +- Request summary: +- Action taken: +- Status: Fixed | Needs Clarification | Blocked +- Evidence: +- Suggested reply: + +3. Discussion Comments (Informational, optional) +- Item: +- Author: +- Summary: +- Notes: +- Suggested reply: + +4. Validation +- Commands run +- Filters used +- Pass/fail summary +- Remaining warnings/errors + +5. Final Summary +- Files changed +- Number fixed +- Number needing clarification +- Number blocked +- Number informational +- Recommended next step + +## Rules +- Do not invent comments; only act on data fetched from gh. +- Review-thread resolution tracking is authoritative for unresolved state. +- Keep behavior-compatible edits unless feedback explicitly requires change. +- If no unresolved review threads exist, report that explicitly. +- If auth or permission fails, report exact failure and minimum required user action. +- Do not use `set -e` in bash commands or scripts. +- After each terminal step, verify the bash session is still alive; if it died, report it immediately, start a new session, and continue from the last confirmed checkpoint. +- Use the discovered git remote name consistently anywhere a remote is required. +- Do not post generic batch replies; each reply must be tailored to the specific comment content and its exact resolution status. diff --git a/AGENTS.md b/AGENTS.md index 06bc0edfd1..c461eec189 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,7 @@ This document provides guidance for AI coding agents working with the Microsoft. ## Quick Start ### Essential Context Files + Before making changes, agents should be aware of: | File | Purpose | @@ -15,6 +16,7 @@ Before making changes, agents should be aware of: | [.github/copilot-instructions.md](.github/copilot-instructions.md) | Copilot-specific instructions | ### Detailed Technical Instructions + The `.github/instructions/` directory contains comprehensive guides: | Guide | Coverage | @@ -29,6 +31,7 @@ The `.github/instructions/` directory contains comprehensive guides: | [features.instructions.md](.github/instructions/features.instructions.md) | Feature reference, keywords | | [documentation.instructions.md](.github/instructions/documentation.instructions.md) | Documentation and samples | | [external-resources.instructions.md](.github/instructions/external-resources.instructions.md) | Docs links, version matrix, external references | +| [ado-work-items-markdown.instructions.md](.github/instructions/ado-work-items-markdown.instructions.md) | Ensure Azure DevOps work item descriptions are Markdown and preserve newlines | ## Workflow Prompts @@ -77,6 +80,7 @@ Do **not** create branches directly under `main`, `dev/`, or any other top-level ## Common Tasks ### Bug Fix Workflow + 1. Understand the issue from the bug report 2. Locate relevant code in `src/Microsoft.Data.SqlClient/src/` (do NOT modify legacy `netcore/src/` or `netfx/src/`) 3. Check `.github/instructions/features.instructions.md` for existing AppContext switches (including failover compatibility switches) before introducing behavior changes @@ -86,6 +90,7 @@ Do **not** create branches directly under `main`, `dev/`, or any other top-level 7. Update documentation if behavior changes ### Feature Implementation + 1. Review the feature specification 2. Plan the implementation (see `implement-feature` prompt) 3. Update reference assemblies if adding public APIs @@ -94,6 +99,7 @@ Do **not** create branches directly under `main`, `dev/`, or any other top-level 6. Do not edit `CHANGELOG.md` directly; instead, add a suggested release-note entry (per `.github/copilot-instructions.md`) in the PR description or via the release-notes workflow/prompt. ### Adding Connection String Keywords + 1. Add to `SqlConnectionStringBuilder` 2. Update connection string parser 3. Default to backward-compatible value @@ -101,6 +107,7 @@ Do **not** create branches directly under `main`, `dev/`, or any other top-level 5. Document in feature reference ### Protocol Changes + 1. Reference MS-TDS specification 2. Update `TdsEnums.cs` for new constants 3. Implement in `TdsParser.cs` and related files @@ -108,6 +115,7 @@ Do **not** create branches directly under `main`, `dev/`, or any other top-level 5. Consider backward compatibility ### Performance Optimization + 1. Profile the issue using benchmarks or traces 2. Identify allocation hotspots (see `perf-optimization` prompt) 3. Apply patterns: `ArrayPool`, `Span`, static/cached instances, source generation @@ -117,6 +125,7 @@ Do **not** create branches directly under `main`, `dev/`, or any other top-level ### Key Documentation Links + - [Microsoft.Data.SqlClient on Microsoft Learn](https://learn.microsoft.com/sql/connect/ado-net/introduction-microsoft-data-sqlclient-namespace) - [MS-TDS Protocol Specification](https://learn.microsoft.com/openspecs/windows_protocols/ms-tds) - [SQL Server Documentation](https://learn.microsoft.com/sql/sql-server/) @@ -124,6 +133,7 @@ Do **not** create branches directly under `main`, `dev/`, or any other top-level ## Repository Policies See the `policy/` directory for: + - [coding-best-practices.md](policy/coding-best-practices.md) - Programming standards - [coding-style.md](policy/coding-style.md) - Code formatting guidelines - [review-process.md](policy/review-process.md) - PR review requirements From 5ac26c96708478d06efd7afd044d24a025200da8 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 8 Jun 2026 07:55:53 -0300 Subject: [PATCH 134/298] Move package compatibility app to tools and add robust test coverage (#4242) --- .github/instructions/testing.instructions.md | 39 ++ .../Directory.Packages.props | 32 -- doc/apps/AzureAuthentication/README.md | 165 ------- src/Microsoft.Data.SqlClient.slnx | 6 +- .../Directory.Build.props | 0 .../Directory.Packages.props | 44 ++ .../PackageCompatibility}/NuGet.config | 0 tools/PackageCompatibility/README.md | 266 ++++++++++++ tools/PackageCompatibility/global.json | 15 + .../PackageCompatibility}/packages/.gitkeep | 0 .../PackageCompatibility/src}/App.cs | 11 +- .../PackageCompatibility/src}/EntryPoint.cs | 12 +- .../src}/GeneratePackageVersions.targets | 0 .../src/PackageCompatibility.csproj | 18 +- .../src}/PackageVersions.Partial.cs | 4 +- .../src}/SqlClientEventListener.cs | 2 +- tools/PackageCompatibility/test/AppTests.cs | 126 ++++++ .../test/BuildVersionOverrideTests.cs | 408 ++++++++++++++++++ .../test/ConsoleCollection.cs | 27 ++ .../test/EntryPointTests.cs | 103 +++++ .../test/IsExternalInit.cs | 13 + .../test/PackageCompatibility.Test.csproj | 25 ++ 22 files changed, 1100 insertions(+), 216 deletions(-) delete mode 100644 doc/apps/AzureAuthentication/Directory.Packages.props delete mode 100644 doc/apps/AzureAuthentication/README.md rename {doc/apps/AzureAuthentication => tools/PackageCompatibility}/Directory.Build.props (100%) create mode 100644 tools/PackageCompatibility/Directory.Packages.props rename {doc/apps/AzureAuthentication => tools/PackageCompatibility}/NuGet.config (100%) create mode 100644 tools/PackageCompatibility/README.md create mode 100644 tools/PackageCompatibility/global.json rename {doc/apps/AzureAuthentication => tools/PackageCompatibility}/packages/.gitkeep (100%) rename {doc/apps/AzureAuthentication => tools/PackageCompatibility/src}/App.cs (92%) rename {doc/apps/AzureAuthentication => tools/PackageCompatibility/src}/EntryPoint.cs (84%) rename {doc/apps/AzureAuthentication => tools/PackageCompatibility/src}/GeneratePackageVersions.targets (100%) rename doc/apps/AzureAuthentication/AzureAuthentication.csproj => tools/PackageCompatibility/src/PackageCompatibility.csproj (73%) rename {doc/apps/AzureAuthentication => tools/PackageCompatibility/src}/PackageVersions.Partial.cs (85%) rename {doc/apps/AzureAuthentication => tools/PackageCompatibility/src}/SqlClientEventListener.cs (97%) create mode 100644 tools/PackageCompatibility/test/AppTests.cs create mode 100644 tools/PackageCompatibility/test/BuildVersionOverrideTests.cs create mode 100644 tools/PackageCompatibility/test/ConsoleCollection.cs create mode 100644 tools/PackageCompatibility/test/EntryPointTests.cs create mode 100644 tools/PackageCompatibility/test/IsExternalInit.cs create mode 100644 tools/PackageCompatibility/test/PackageCompatibility.Test.csproj diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index bd9db72c4a..df6b909f8a 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -173,6 +173,45 @@ dotnet test ... --filter "FullyQualifiedName=Namespace.ClassName.MethodName" ## Writing Tests +### Test Documentation Requirements + +To keep tests maintainable for contributors and AI agents, test intent must be documented at +class and method level. + +#### Required XML Documentation +- Add XML `` comments to every test class. +- Add XML `` comments to every test method (`[Fact]`, `[Theory]`, conditional variants). +- For helper methods used by tests, add XML `` comments and XML `` / `` + where applicable. +- For fixture and collection types, add XML `` comments describing why the fixture exists + (for example, serialization of console-mutating tests). + +#### What the Comments Must Explain +- The behavior/contract being tested (not just restating the method name). +- Why the scenario matters (for example: regression guard, parsing contract, sync/async parity, + isolation requirement). +- For helper methods, what side effects occur (for example console redirection, file system + copying, process execution) and why they are needed. + +#### Style Guidance +- Keep comments concise and factual. +- Prefer behavior-focused wording over implementation trivia. +- Avoid comments that merely repeat obvious code. +- Use inline comments inside test methods only for non-obvious setup/act/assert details. + +#### Example +```csharp +/// +/// Ensures malformed connection strings return a non-zero exit code and emit a parse error +/// without verbose exception details. +/// +[Fact] +public void AppRunWithMalformedConnectionStringReturnsOneAndWritesParseError() +{ + // Arrange / Act / Assert +} +``` + ### Test Structure ```csharp public class FeatureNameTests diff --git a/doc/apps/AzureAuthentication/Directory.Packages.props b/doc/apps/AzureAuthentication/Directory.Packages.props deleted file mode 100644 index b512683765..0000000000 --- a/doc/apps/AzureAuthentication/Directory.Packages.props +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - 7.0.0-preview4.26064.3 - - - 7.0.0-preview1.26064.3 - - - - - - - - - - - - - - - - - - diff --git a/doc/apps/AzureAuthentication/README.md b/doc/apps/AzureAuthentication/README.md deleted file mode 100644 index 967a728007..0000000000 --- a/doc/apps/AzureAuthentication/README.md +++ /dev/null @@ -1,165 +0,0 @@ -# AzureAuthentication Sample App - -A minimal console application that verifies **SqlClient** can connect to a SQL Server using Entra ID -authentication (formerly Azure Active Directory authentication) via the **Azure** package. It also -references the **Azure Key Vault Provider** package to confirm there are no transitive dependency -conflicts between the packages. - -The following SqlClient packages are used, either directly or transitively: - -- `Microsoft.Data.SqlClient` -- `Microsoft.SqlServer.Server` -- `Microsoft.Data.SqlClient.Internal.Logging` -- `Microsoft.Data.SqlClient.Extensions.Abstractions` -- `Microsoft.Data.SqlClient.Extensions.Azure` -- `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` - -## Purpose - -This app serves as a smoke test for package compatibility. It: - -1. Instantiates `SqlColumnEncryptionAzureKeyVaultProvider` to ensure the AKV provider assembly loads - without conflicts. -2. Opens a `SqlConnection` using a connection string you provide, validating that authentication and - connectivity work end-to-end. - -The app is designed to run against both **published NuGet packages** and **locally-built packages** -(via the `packages/` directory configured in `NuGet.config`). - -## Build Parameters - -Package versions are controlled through build properties. Pass them on the command line with `-p:` -(or `/p:`) to override the defaults defined in `Directory.Packages.props`. - -| Property | Default | Description | -| --- | --- | --- | -| `SqlClientVersion` | `7.0.0-preview4.26064.3` | Version of `Microsoft.Data.SqlClient` to reference. | -| `AkvProviderVersion` | `7.0.0-preview1.26064.3` | Version of `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` to reference. | -| `AzureVersion` | None | Version of `Microsoft.Data.SqlClient.Extensions.Azure` to reference. When omitted, the `Azure` package will not be referenced. | - -## Local Package Source - -The `NuGet.config` adds a `packages/` directory as a local package source. To test against packages -that haven't been published to NuGet yet, copy the `.nupkg` files into this folder and specify the -matching version via the build properties above. - -NuGet will cache copies of the packages it finds in `packages/` after a successful restore. If you -update the `.nupkg` files in `packages/` without incrementing their version numbers (and referencing -those new version numbers) you will have to clear the NuGet caches in order for the next restore -operation to pick them up: - -```bash -dotnet nuget locals all --clear -``` - -## Running the App - -The app has built-in help: - -```bash -dotnet run -- --help - -Description: - Azure Authentication Tester - --------------------------- - - Validates SqlClient connectivity using EntraID (formerly Azure Active Directory) authentication. - Connects to SQL Server using the supplied connection string, which must specify the authentication method. - - Supply specific package versions when building to test different versions of the SqlClient suite, for example: - - -p:SqlClientVersion=7.0.0.preview4 - -p:AkvProviderVersion=7.0.1-preview2 - -p:AzureVersion=1.0.0-preview1 - -Usage: - AzureAuthentication [options] - -Options: - -c, --connection-string (REQUIRED) The ADO.NET connection string used to connect to SQL Server. - Supports SQL, Entra ID, and integrated authentication modes. - -l, --log-events Enable SqlClient event emission to the console. - -t, --trace Pauses execution to allow dotnet-trace to be attached. - -v, --verbose Enable verbose output with detailed error information. - -?, -h, --help Show help and usage information - --version Show version information -``` - -The app expects a single argument: a full connection string. - -```bash -dotnet run -- -c "" -``` - -For Entra ID authentication, use an `Authentication` keyword in the connection string. For example: - -```bash -dotnet run -- -c "Server=myserver.database.windows.net;Database=mydb;Authentication=ActiveDirectoryDefault" -``` - -On success the app emits to standard out: - -```bash -Azure Authentication Tester ---------------------------- - -Packages used: - SqlClient: 7.0.0-preview4.26055.1 - AKV Provider: 6.1.2 - Azure: 1.0.0-preview1.26055.1 - -Connection details: - Data Source: adotest.database.windows.net - Initial Catalog: Northwind - Authentication: ActiveDirectoryPassword - -Testing connectivity... -Connected successfully! - Server version: 12.00.1017 -``` - -Errors will be emitted to standard error: - -```bash -Testing connectivity... -Connection failed: - Cannot find an authentication provider for 'ActiveDirectoryPassword'. -``` - -### Examples - -Run with the default (published) package versions, and no `Azure` package: - -```bash -dotnet run -- -c "" -``` - -If the connection string specifies one of the Entra ID authentication methods, -`SqlClient` will fail with an error indicating that no authentication provider has been registered. -This is because the `Azure` package was not referenced, and the app did not provide its own custom -authentication provider. - -Run against locally-built packages (drop `.nupkg` files into the `packages/` folder first): - -```bash -dotnet run -p:SqlClientVersion=7.0.0-preview4 -- -c "" -``` - -Run including the `Azure` extensions package: - -```bash -dotnet run -p:AzureVersion=1.0.0-preview1 -- -c "" -``` - -Override all three versions at once: - -```bash -dotnet run -p:SqlClientVersion=7.0.0-preview1 -p:AkvProviderVersion=7.0.0-preview1 -p:AzureVersion=1.0.0-preview1 -- -c "" -``` - -## Prerequisites - -- [.NET 10.0 SDK](https://dotnet.microsoft.com/download) and .NET Framework 4.8.1 or later. -- A SQL Server or Azure SQL instance accessible with Entra ID credentials. -- Azure credentials available to `DefaultAzureCredential` (e.g. Azure CLI login, environment - variables, or managed identity). diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index d65c3edc90..c45bbdf485 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -10,9 +10,6 @@ - - - @@ -170,7 +167,6 @@ - @@ -215,4 +211,6 @@ + + diff --git a/doc/apps/AzureAuthentication/Directory.Build.props b/tools/PackageCompatibility/Directory.Build.props similarity index 100% rename from doc/apps/AzureAuthentication/Directory.Build.props rename to tools/PackageCompatibility/Directory.Build.props diff --git a/tools/PackageCompatibility/Directory.Packages.props b/tools/PackageCompatibility/Directory.Packages.props new file mode 100644 index 0000000000..161a606355 --- /dev/null +++ b/tools/PackageCompatibility/Directory.Packages.props @@ -0,0 +1,44 @@ + + + + + + + + 1.0.0 + 7.0.0 + 1.0.0 + 7.0.1 + 1.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/apps/AzureAuthentication/NuGet.config b/tools/PackageCompatibility/NuGet.config similarity index 100% rename from doc/apps/AzureAuthentication/NuGet.config rename to tools/PackageCompatibility/NuGet.config diff --git a/tools/PackageCompatibility/README.md b/tools/PackageCompatibility/README.md new file mode 100644 index 0000000000..a629156afc --- /dev/null +++ b/tools/PackageCompatibility/README.md @@ -0,0 +1,266 @@ +# PackageCompatibility Tool + +A minimal console application that verifies that a set of SqlClient packages can coexist without +transitive dependency conflicts, API surface mismatches, or broken runtime functionality. It forces +the AKV provider type to load (pulling in that assembly and its transitive dependencies) and then +opens a `SqlConnection` to confirm that the resolved package graph works end-to-end against a real +SQL Server instance. + +The following SqlClient packages are verified, either directly or transitively: + +- `Microsoft.Data.SqlClient` +- `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` +- `Microsoft.Data.SqlClient.Internal.Logging` +- `Microsoft.Data.SqlClient.Extensions.Abstractions` +- `Microsoft.Data.SqlClient.Extensions.Azure` *(optional — included when `AzureVersion` is set)* +- `Microsoft.SqlServer.Server` + +## Purpose + +This tool is a smoke test for inter-package compatibility across the SqlClient suite. It catches +problems such as: + +- Assembly binding conflicts caused by mismatched transitive dependencies. +- API surface mismatches between independently-versioned packages. +- Runtime failures that only appear when multiple packages are loaded together. + +Specifically, it: + +1. Instantiates `SqlColumnEncryptionAzureKeyVaultProvider` to force the AKV provider assembly and + all its transitive dependencies to load alongside SqlClient. +2. Opens a `SqlConnection` using a connection string you provide, exercising the authentication + and network code paths end-to-end. + +The app is designed to run against both **published NuGet packages** and **locally-built packages** +(via the `packages/` directory configured in `NuGet.config`). + +## How This Differs From the Existing Test Suite + +The SqlClient repository has a large suite of unit, functional, and manual tests. This tool +complements — but does not replace — those tests. The key differences are: + +### Heterogeneous package versions + +The test projects reference sibling packages via project references or a shared `Directory.Packages. +props` file. Every test run uses a **single, uniform version set** derived from whatever is +currently checked out. You cannot ask the test suite to run `SqlClient 7.0.1` against +`AkvProvider 7.1.0-preview1` without editing project files. + +This tool accepts any combination of independent version numbers — including versions that have not +been published yet — at the command line: + +```bash +dotnet run \ + -p:SqlClientVersion=7.0.1 \ + -p:AkvProviderVersion=7.1.0-preview1 \ + -- -c "" +``` + +This makes it straightforward to answer questions like *"does the new AKV provider build work +against the last published SqlClient release?"* without modifying any source files. + +### Pre-release and locally-built packages + +Because NuGet resolves packages from the `packages/` local feed before falling back to NuGet.org, +you can drop pre-release `.nupkg` files in that folder and reference them immediately — even before +they have been published. The existing tests have no equivalent mechanism; they can only reference +packages that are either checked out as source or already published to a configured feed. + +### End-to-end runtime coverage across the full package graph + +The test suite exercises individual classes and APIs in isolation. Functional and manual tests do +open real connections, but they always run against the packages as built from source in the current +branch. + +This tool loads every package in the dependency graph simultaneously in a single process and then +opens a live `SqlConnection`. This catches a class of failures that isolated tests miss: + +- **Binding redirect conflicts**: two packages pulling in incompatible versions of a shared + dependency (`Azure.Core`, `Microsoft.Identity.*`, etc.) that only manifest when all packages are + present in the same AppDomain. +- **Transitive version mismatches**: a package expecting an internal API surface that has changed in + a sibling package across a version boundary. +- **Registration side-effects**: authentication providers or other singleton registrations that + interfere when packages are composed in an unexpected order or version combination. + +### Diagnostic console output + +When a connection fails, the tool can emit structured TDS-level and authentication trace output via +the `--log` and `--trace` flags. This output is written directly to the console and requires no +test harness or log configuration — useful for quickly diagnosing authentication failures in CI +environments or on developer machines where full test infrastructure is unavailable. + +The test suite's diagnostics are routed through `EventSource`/`DiagnosticListener` and are only +visible if a listener is attached (e.g. via `dotnet-trace` or a custom test initializer). + +### What this tool does NOT do + +- It does not assert on individual API behaviours, query results, or error messages. For that, use + the existing unit and functional tests. +- It cannot run without a real SQL Server instance. The manual test suite has the same constraint + for connectivity tests, but unit and functional tests run without a server. +- It does not cover every authentication mode automatically. You must provide a suitable connection + string for each mode you want to validate. + +## Project Layout + +- `src/` contains the tool source files and project file. +- `test/` is reserved for tests. + +## Authentication Modes + +The authentication mode embedded in the connection string controls which code paths and packages are +exercised during the connectivity test. Use different modes to broaden coverage: + +| Authentication mode | `Authentication=` value | Packages exercised | +| --- | --- | --- | +| SQL Server auth | *(omit or `SqlPassword`)* | `Microsoft.Data.SqlClient` only | +| Entra ID — integrated | `ActiveDirectoryIntegrated` | `Microsoft.Data.SqlClient` + `Extensions.Azure` (requires `AzureVersion`) | +| Entra ID — default chain | `ActiveDirectoryDefault` | `Microsoft.Data.SqlClient` + `Extensions.Azure` (requires `AzureVersion`) | +| Entra ID — password | `ActiveDirectoryPassword` | `Microsoft.Data.SqlClient` + `Extensions.Azure` (requires `AzureVersion`) | +| Entra ID — interactive | `ActiveDirectoryInteractive` | `Microsoft.Data.SqlClient` + `Extensions.Azure` (requires `AzureVersion`) | +| Entra ID — service principal | `ActiveDirectoryServicePrincipal` | `Microsoft.Data.SqlClient` + `Extensions.Azure` (requires `AzureVersion`) | +| Entra ID — managed identity | `ActiveDirectoryManagedIdentity` | `Microsoft.Data.SqlClient` + `Extensions.Azure` (requires `AzureVersion`) | + +> **Note:** All Entra ID modes require the `Extensions.Azure` package to be referenced (pass +> `-p:AzureVersion=`). Without it, SqlClient will throw at runtime because no +> authentication provider is registered for those modes. + +## Build Parameters + +Package versions are controlled through build properties. Pass them on the command line with `-p:` +(or `/p:`) to override the defaults defined in `Directory.Packages.props`. + +| Property | Default | Description | +| --- | --- | --- | +| `AbstractionsVersion` | `1.0.0` | Version of `Microsoft.Data.SqlClient.Extensions.Abstractions` to reference. | +| `AkvProviderVersion` | `7.0.0` | Version of `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` to reference. | +| `AzureVersion` | None | Version of `Microsoft.Data.SqlClient.Extensions.Azure` to reference. When omitted, the `Azure` package will not be referenced. | +| `LoggingVersion` | `1.0.0` | Version of `Microsoft.Data.SqlClient.Internal.Logging` to reference. | +| `SqlClientVersion` | `7.0.1` | Version of `Microsoft.Data.SqlClient` to reference. | +| `SqlServerVersion` | `1.0.0` | Version of `Microsoft.SqlServer.Server` to reference. | + +## Local Package Source + +The `NuGet.config` adds a `packages/` directory as a local package source. To test against packages +that haven't been published to NuGet yet, copy the `.nupkg` files into this folder and specify the +matching version via the build properties above. + +NuGet will cache copies of the packages it finds in `packages/` after a successful restore. If you +update the `.nupkg` files in `packages/` without incrementing their version numbers (and referencing +those new version numbers) you will have to clear the NuGet caches in order for the next restore +operation to pick them up: + +```bash +dotnet nuget locals all --clear +``` + +## Running the App + +The app has built-in help: + +```bash +dotnet run -- --help + +Description: + Package Compatibility Tester + ---------------------------- + + Validates SqlClient connectivity using EntraID (formerly Azure Active Directory) authentication. + Connects to SQL Server using the supplied connection string, which must specify the authentication method. + + ... +``` + +The app requires a connection string. Use SQL authentication for a basic connectivity check: + +```bash +dotnet run -- -c "Server=myserver;Database=mydb;User ID=sa;Password=;Encrypt=Mandatory;TrustServerCertificate=true" +``` + +To exercise Entra ID flows, include an `Authentication` keyword and reference the `Extensions.Azure` +package: + +```bash +dotnet run -p:AzureVersion=1.0.0 -- -c "Server=myserver.database.windows.net;Database=mydb;Authentication=ActiveDirectoryDefault" +``` + +On success the app emits to standard out: + +```bash +Package Compatibility Tester +---------------------------- + +Packages used: + Abstractions: 1.0.1 + AKV Provider: 7.0.0 + Azure: 1.1.0-preview1 + Logging: 1.0.1 + SqlClient: 7.1.0.preview1 + SqlServer: 1.0.0 + +Connection details: + Data Source: adotest.database.windows.net + Initial Catalog: Northwind + Authentication: ActiveDirectoryPassword + +Testing connectivity... +Connected successfully! + Server version: 12.00.1017 +``` + +Errors will be emitted to standard error: + +```bash +Testing connectivity... +Connection failed: + Cannot find an authentication provider for 'ActiveDirectoryPassword'. +``` + +### Examples + +Run a basic SQL authentication check using the default package versions: + +```bash +dotnet run -- -c "Server=myserver;Database=mydb;User ID=sa;Password=;Encrypt=Mandatory;TrustServerCertificate=true" +``` + +Run with no `Azure` package — Entra ID modes will fail at runtime if specified in the connection +string, which is itself useful for confirming the error path: + +```bash +dotnet run -- -c "" +``` + +Include the `Azure` package to enable Entra ID authentication flows: + +```bash +dotnet run -p:AzureVersion=1.0.0-preview1 -- -c "" +``` + +Run against locally-built packages (drop `.nupkg` files into `packages/` first): + +```bash +dotnet run -p:SqlClientVersion=7.1.0-preview1 -- -c "" +``` + +Override all five package versions at once: + +```bash +dotnet run \ + -p:AbstractionsVersion=1.0.1 \ + -p:AkvProviderVersion=7.1.0-preview1 \ + -p:AzureVersion=1.0.0 \ + -p:LoggingVersion=1.0.1 \ + -p:SqlClientVersion=7.1.0-preview1 \ + -p:SqlServerVersion=1.0.0 \ + -- -c "" +``` + +## Prerequisites + +- [.NET 10.0 SDK](https://dotnet.microsoft.com/download) and .NET Framework 4.8.1 or later. +- A SQL Server or Azure SQL instance reachable from the machine running the tool. +- For Entra ID authentication modes: Azure credentials available to `DefaultAzureCredential` + (e.g. Azure CLI login, environment variables, or managed identity), and the `AzureVersion` + build property set so the `Extensions.Azure` package is included. diff --git a/tools/PackageCompatibility/global.json b/tools/PackageCompatibility/global.json new file mode 100644 index 0000000000..7c7fca5caa --- /dev/null +++ b/tools/PackageCompatibility/global.json @@ -0,0 +1,15 @@ +{ + "test": { + // xUnit v3 runs on Microsoft.Testing.Platform rather than the older VSTest runner. + // On .NET 10, dotnet test must therefore be opted into MTP mode for this test project. + "runner": "Microsoft.Testing.Platform" + }, + "sdk": { + // global.json lookup stops at the nearest file and does not merge with the repo-root file. + // This subtree therefore needs to repeat the root SDK settings so commands run from + // tools/PackageCompatibility keep using the same pinned SDK behavior. + "version": "10.0.300", + "rollForward": "disable", + "allowPrerelease": false + } +} diff --git a/doc/apps/AzureAuthentication/packages/.gitkeep b/tools/PackageCompatibility/packages/.gitkeep similarity index 100% rename from doc/apps/AzureAuthentication/packages/.gitkeep rename to tools/PackageCompatibility/packages/.gitkeep diff --git a/doc/apps/AzureAuthentication/App.cs b/tools/PackageCompatibility/src/App.cs similarity index 92% rename from doc/apps/AzureAuthentication/App.cs rename to tools/PackageCompatibility/src/App.cs index 3447e5489f..2864ad0158 100644 --- a/doc/apps/AzureAuthentication/App.cs +++ b/tools/PackageCompatibility/src/App.cs @@ -3,7 +3,7 @@ using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider; -namespace Microsoft.Data.SqlClient.Samples.AzureAuthentication; +namespace Microsoft.Data.SqlClient.Tools.PackageCompatibility; /// /// Console application that validates SqlClient connectivity using Entra ID (formerly Azure Active @@ -63,9 +63,12 @@ internal int Run(RunOptions options) --------------------------- Packages used: - SqlClient: {PackageVersions.MicrosoftDataSqlClient} + Abstractions: {PackageVersions.MicrosoftDataSqlClientExtensionsAbstractions} AKV Provider: {PackageVersions.MicrosoftDataSqlClientAlwaysEncryptedAzureKeyVaultProvider} - Azure: {PackageVersions.AzureExtensionsVersion} + Azure: {PackageVersions.MicrosoftDataSqlClientExtensionsAzureVersion} + Logging: {PackageVersions.MicrosoftDataSqlClientInternalLogging} + SqlClient: {PackageVersions.MicrosoftDataSqlClient} + SqlServer: {PackageVersions.MicrosoftSqlServerServer} """); @@ -192,7 +195,7 @@ internal static void Err(string message) /// /// The display name of the application. /// - internal const string AppName = "Azure Authentication Tester"; + internal const string AppName = "Package Compatibility Tester"; /// /// The optional event listener used to capture SqlClient diagnostic events. diff --git a/doc/apps/AzureAuthentication/EntryPoint.cs b/tools/PackageCompatibility/src/EntryPoint.cs similarity index 84% rename from doc/apps/AzureAuthentication/EntryPoint.cs rename to tools/PackageCompatibility/src/EntryPoint.cs index 20885ae1af..8729352024 100644 --- a/doc/apps/AzureAuthentication/EntryPoint.cs +++ b/tools/PackageCompatibility/src/EntryPoint.cs @@ -1,6 +1,6 @@ using System.CommandLine; -namespace Microsoft.Data.SqlClient.Samples.AzureAuthentication; +namespace Microsoft.Data.SqlClient.Tools.PackageCompatibility; /// /// Contains the application entry point responsible for parsing command-line arguments and @@ -50,14 +50,20 @@ which must specify the authentication method. Supply specific package versions when building to test different versions of the SqlClient suite, for example: - -p:SqlClientVersion=7.0.0-preview4 + -p:AbstractionsVersion=1.0.1 -p:AkvProviderVersion=7.0.1-preview2 -p:AzureVersion=1.0.0-preview1 + -p:LoggingVersion=1.0.2 + -p:SqlClientVersion=7.0.0-preview4 + -p:SqlServerVersion=1.0.0 Current package versions: + Logging: {PackageVersions.MicrosoftDataSqlClientInternalLogging} + Abstractions: {PackageVersions.MicrosoftDataSqlClientExtensionsAbstractions} SqlClient: {PackageVersions.MicrosoftDataSqlClient} + SqlServer: {PackageVersions.MicrosoftSqlServerServer} AKV Provider: {PackageVersions.MicrosoftDataSqlClientAlwaysEncryptedAzureKeyVaultProvider} - Azure: {PackageVersions.AzureExtensionsVersion} + Azure: {PackageVersions.MicrosoftDataSqlClientExtensionsAzureVersion} """) { connectionStringOption, diff --git a/doc/apps/AzureAuthentication/GeneratePackageVersions.targets b/tools/PackageCompatibility/src/GeneratePackageVersions.targets similarity index 100% rename from doc/apps/AzureAuthentication/GeneratePackageVersions.targets rename to tools/PackageCompatibility/src/GeneratePackageVersions.targets diff --git a/doc/apps/AzureAuthentication/AzureAuthentication.csproj b/tools/PackageCompatibility/src/PackageCompatibility.csproj similarity index 73% rename from doc/apps/AzureAuthentication/AzureAuthentication.csproj rename to tools/PackageCompatibility/src/PackageCompatibility.csproj index 8b7c6f5ee9..d33eff8e52 100644 --- a/doc/apps/AzureAuthentication/AzureAuthentication.csproj +++ b/tools/PackageCompatibility/src/PackageCompatibility.csproj @@ -2,8 +2,11 @@ Exe + net481;net10.0 - Microsoft.Data.SqlClient.Samples.AzureAuthentication + + Microsoft.Data.SqlClient.Tools.PackageCompatibility + enable enable latest @@ -12,11 +15,10 @@ - + + + + + + <_Parameter1>PackageCompatibility.Test + + + diff --git a/doc/apps/AzureAuthentication/PackageVersions.Partial.cs b/tools/PackageCompatibility/src/PackageVersions.Partial.cs similarity index 85% rename from doc/apps/AzureAuthentication/PackageVersions.Partial.cs rename to tools/PackageCompatibility/src/PackageVersions.Partial.cs index 3c88781daa..0e34418e85 100644 --- a/doc/apps/AzureAuthentication/PackageVersions.Partial.cs +++ b/tools/PackageCompatibility/src/PackageVersions.Partial.cs @@ -1,4 +1,4 @@ -namespace Microsoft.Data.SqlClient.Samples.AzureAuthentication; +namespace Microsoft.Data.SqlClient.Tools.PackageCompatibility; /// /// Hand-written companion to the auto-generated PackageVersions.g.cs file @@ -14,7 +14,7 @@ internal static partial class PackageVersions /// /// Version of the Azure extensions package, or "N/A" when not referenced. /// - public const string AzureExtensionsVersion = + public const string MicrosoftDataSqlClientExtensionsAzureVersion = #if AZURE_EXTENSIONS MicrosoftDataSqlClientExtensionsAzure; #else diff --git a/doc/apps/AzureAuthentication/SqlClientEventListener.cs b/tools/PackageCompatibility/src/SqlClientEventListener.cs similarity index 97% rename from doc/apps/AzureAuthentication/SqlClientEventListener.cs rename to tools/PackageCompatibility/src/SqlClientEventListener.cs index 5e861f4796..686a4f2c9a 100644 --- a/doc/apps/AzureAuthentication/SqlClientEventListener.cs +++ b/tools/PackageCompatibility/src/SqlClientEventListener.cs @@ -1,6 +1,6 @@ using System.Diagnostics.Tracing; -namespace Microsoft.Data.SqlClient.Samples.AzureAuthentication; +namespace Microsoft.Data.SqlClient.Tools.PackageCompatibility; /// /// Listens for events from Microsoft.Data.SqlClient.EventSource and emits them via the diff --git a/tools/PackageCompatibility/test/AppTests.cs b/tools/PackageCompatibility/test/AppTests.cs new file mode 100644 index 0000000000..3a80de1eaf --- /dev/null +++ b/tools/PackageCompatibility/test/AppTests.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; + +using Microsoft.Data.SqlClient.Tools.PackageCompatibility; + +using Xunit; + +namespace Microsoft.Data.SqlClient.Tools.PackageCompatibility.Tests; + +/// +/// Verifies control flow and diagnostics for common failure +/// and instrumentation paths without requiring a live SQL Server. +/// +[Collection(ConsoleCollection.Name)] +public class AppTests +{ + /// + /// Ensures malformed connection strings produce a parse error and a non-zero exit code in + /// non-verbose mode, without leaking full exception details. + /// + [Fact] + public void AppRunWithMalformedConnectionStringReturnsOneAndWritesParseError() + { + // Use an intentionally invalid connection string to force parse failure branch. + CommandResult result = ExecuteApp( + connectionString: "Server", + verbose: false); + + // Non-verbose mode should report a friendly error without full exception type details. + Assert.Equal(1, result.ExitCode); + Assert.Contains("Failed to parse connection string:", result.StandardError, StringComparison.Ordinal); + Assert.DoesNotContain("System.", result.StandardError, StringComparison.Ordinal); + } + + /// + /// Ensures malformed connection strings produce detailed exception output when verbose mode is + /// enabled. + /// + [Fact] + public void AppRunWithMalformedConnectionStringAndVerboseIncludesExceptionDetails() + { + // Same malformed input, but verbose mode should include full exception diagnostics. + CommandResult result = ExecuteApp( + connectionString: "Server", + verbose: true); + + // Verbose mode intentionally includes type/stack-friendly exception text. + Assert.Equal(1, result.ExitCode); + Assert.Contains("Failed to parse connection string:", result.StandardError, StringComparison.Ordinal); + Assert.Contains("System.", result.StandardError, StringComparison.Ordinal); + } + + /// + /// Ensures enabling event logging emits the expected logging banner before the eventual + /// connection failure for an unreachable endpoint. + /// + [Fact] + public void AppRunWithLogEventsEmitsLoggingMessageBeforeConnectionFailure() + { + // Use an unreachable endpoint to keep this deterministic while exercising log-events path. + CommandResult result = ExecuteApp( + connectionString: "Server=127.0.0.1,1;Database=master;User ID=sa;Password=invalid;Connect Timeout=1;Encrypt=False", + logEvents: true, + verbose: false); + + // App should announce event logging before eventually failing the connection attempt. + Assert.Equal(1, result.ExitCode); + Assert.Contains("SqlClient event logging enabled; events will be prefixed with [EVENT]", result.StandardOutput, StringComparison.Ordinal); + Assert.Contains("Connection failed:", result.StandardError, StringComparison.Ordinal); + } + + /// + /// Executes with redirected console streams and returns + /// captured output and exit code for assertions. + /// + /// Connection string supplied to . + /// Whether to enable event listener wiring during the run. + /// Whether to enable trace-attach pause behavior. + /// Whether to emit verbose diagnostic details. + /// Captured stdout, stderr, and process-equivalent exit code. + private static CommandResult ExecuteApp(string connectionString, bool logEvents = false, bool trace = false, bool verbose = false) + { + // Redirect process-wide console streams so assertions can inspect output. + TextWriter originalOut = Console.Out; + TextWriter originalError = Console.Error; + using StringWriter standardOutput = new(); + using StringWriter standardError = new(); + + // Construct the same internal options object EntryPoint passes to App.Run. + using App app = new(); + App.RunOptions runOptions = new() + { + ConnectionString = connectionString, + LogEvents = logEvents, + Trace = trace, + Verbose = verbose + }; + + try + { + Console.SetOut(standardOutput); + Console.SetError(standardError); + + // Act: execute one App.Run invocation for the provided scenario. + int exitCode = app.Run(runOptions); + + // Capture both streams because App writes info and errors separately. + return new CommandResult(exitCode, standardOutput.ToString(), standardError.ToString()); + } + finally + { + // Restore global console streams to prevent cross-test interference. + Console.SetOut(originalOut); + Console.SetError(originalError); + } + } + + /// + /// Represents the observed result of a single in-process + /// invocation. + /// + /// Return code from . + /// Captured standard output stream contents. + /// Captured standard error stream contents. + private sealed record CommandResult(int ExitCode, string StandardOutput, string StandardError); +} diff --git a/tools/PackageCompatibility/test/BuildVersionOverrideTests.cs b/tools/PackageCompatibility/test/BuildVersionOverrideTests.cs new file mode 100644 index 0000000000..493e1d1209 --- /dev/null +++ b/tools/PackageCompatibility/test/BuildVersionOverrideTests.cs @@ -0,0 +1,408 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +using Xunit; + +namespace Microsoft.Data.SqlClient.Tools.PackageCompatibility.Tests; + +/// +/// Build-focused tests that verify MSBuild property overrides (for package versions) are reflected +/// in generated package-version constants. +/// +public class BuildVersionOverrideTests +{ + /// + /// Ensures building with explicit -p: overrides produces matching version constants in + /// the generated PackageVersions.g.cs file. + /// + /// MSBuild properties to override during build. + /// Expected package label-to-version mappings. + [Theory] + [MemberData(nameof(GetVersionPropertyTestCases))] + public void BuildGeneratesExpectedPackageVersionsWhenCustomPropertiesAreSupplied( + Dictionary buildProperties, + Dictionary expectedVersions) + { + // Build in an isolated copy so per-test overrides cannot leak into repo obj/bin state. + BuildArtifacts artifacts = BuildAppWithProperties(buildProperties); + + try + { + // The generated source file is the authoritative output of GeneratePackageVersions.targets. + string generatedVersions = File.ReadAllText(artifacts.GeneratedVersionsFile); + + // Assert each expected package label maps to the generated constant/version pair. + foreach (var kvp in expectedVersions) + { + string constantName = GetPackageVersionsConstantName(kvp.Key); + string expected = $"public const string {constantName} = \"{kvp.Value}\";"; + Assert.Contains(expected, generatedVersions, StringComparison.Ordinal); + } + } + finally + { + if (!ShouldPreserveTempBuildDirectories()) + { + TryDeleteDirectory(artifacts.ProjectDirectory); + TryDeleteDirectory(artifacts.OutputDirectory); + } + } + } + + /// + /// Provides test vectors for single, partial, and full package-version override scenarios. + /// + /// + /// Theory data containing MSBuild override properties and expected generated version values. + /// + public static IEnumerable GetVersionPropertyTestCases() + { + // Override only SqlClient to prove a single-property override flows to generated code. + yield return new object[] + { + new Dictionary { { "SqlClientVersion", "7.0.0" } }, + new Dictionary + { + { "SqlClient", "7.0.0" }, + { "Abstractions", "1.0.0" }, + { "Logging", "1.0.0" }, + { "SqlServer", "1.0.0" } + } + }; + + // Override a subset of properties to verify mixed default/override behavior. + yield return new object[] + { + new Dictionary + { + { "SqlClientVersion", "7.0.0" }, + { "LoggingVersion", "1.0.0" }, + { "AbstractionsVersion", "1.0.0" } + }, + new Dictionary + { + { "SqlClient", "7.0.0" }, + { "Logging", "1.0.0" }, + { "Abstractions", "1.0.0" }, + { "AKV Provider", "7.0.0" } + } + }; + + // Override all supported package properties to validate full replacement behavior. + yield return new object[] + { + new Dictionary + { + { "AbstractionsVersion", "1.0.0" }, + { "AkvProviderVersion", "7.0.0" }, + { "LoggingVersion", "1.0.0" }, + { "SqlClientVersion", "7.0.0" }, + { "SqlServerVersion", "1.0.0" } + }, + new Dictionary + { + { "Abstractions", "1.0.0" }, + { "AKV Provider", "7.0.0" }, + { "Logging", "1.0.0" }, + { "SqlClient", "7.0.0" }, + { "SqlServer", "1.0.0" } + } + }; + } + + /// + /// Builds an isolated copy of the PackageCompatibility project with optional version override + /// properties and returns paths to relevant build artifacts. + /// + /// MSBuild properties passed via -p: arguments. + /// Paths to the isolated workspace, output folder, and generated versions file. + private static bool ShouldPreserveTempBuildDirectories() + { + string? preserve = Environment.GetEnvironmentVariable("PACKAGECOMPATIBILITY_PRESERVE_TEMP"); + return string.Equals(preserve, "1", StringComparison.OrdinalIgnoreCase) + || string.Equals(preserve, "true", StringComparison.OrdinalIgnoreCase); + } + + private static void TryDeleteDirectory(string path) + { + if (string.IsNullOrEmpty(path) || !Directory.Exists(path)) + { + return; + } + + try + { + Directory.Delete(path, recursive: true); + } + catch (IOException) + { + } + catch (UnauthorizedAccessException) + { + } + } + + private static BuildArtifacts BuildAppWithProperties(Dictionary properties) + { + // Locate and clone the package-compatibility subtree so test builds are hermetic. + string packageCompatibilityDir = GetPackageCompatibilityDirectory(); + string tempProjectDir = Path.Combine(Path.GetTempPath(), $"PackageCompatibilityProject_{Guid.NewGuid():N}"); + string tempOutputDir = Path.Combine(Path.GetTempPath(), $"PackageCompatibility_{Guid.NewGuid():N}"); + + CopyDirectory(packageCompatibilityDir, tempProjectDir); + + // Create an empty packages/ directory so NuGet.config source path remains valid. + Directory.CreateDirectory(Path.Combine(tempProjectDir, "packages")); + Directory.CreateDirectory(tempOutputDir); + + string copiedProjectFile = Path.Combine(tempProjectDir, "src", "PackageCompatibility.csproj"); + string generatedVersionsFile = Path.Combine(tempProjectDir, "src", "obj", "Release", "net10.0", "PackageVersions.g.cs"); + + // Build with explicit property overrides; MSBuild treats these as highest-priority values. + var buildArgs = new List + { + "build", + copiedProjectFile, + "-c", "Release", + "-f", "net10.0", + "-o", tempOutputDir + }; + + // Add requested test case overrides as -p:= arguments. + foreach (var kvp in properties) + { + buildArgs.Add($"-p:{kvp.Key}={kvp.Value}"); + } + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + +#if NET + foreach (string buildArg in buildArgs) + { + psi.ArgumentList.Add(buildArg); + } +#else + psi.Arguments = string.Join(" ", buildArgs.Select(QuoteArgument)); +#endif + + using (var process = Process.Start(psi)) + { + if (process == null) + { + throw new InvalidOperationException("Failed to start build process"); + } + + string stdout = string.Empty; + string stderr = string.Empty; + +#if NET + bool completed = process.WaitForExit(TimeSpan.FromSeconds(60)); + if (!completed) + { + process.Kill(); + throw new InvalidOperationException("Build timed out after 60 seconds"); + } + + stdout = process.StandardOutput.ReadToEnd(); + stderr = process.StandardError.ReadToEnd(); +#else + Task stdoutTask = process.StandardOutput.ReadToEndAsync(); + Task stderrTask = process.StandardError.ReadToEndAsync(); + bool completed = process.WaitForExit(60000); + stdout = stdoutTask.GetAwaiter().GetResult(); + stderr = stderrTask.GetAwaiter().GetResult(); + if (!completed) + { + process.Kill(); + throw new InvalidOperationException("Build timed out after 60 seconds"); + } +#endif + + // Fail fast with full command/stdout/stderr context for easy diagnosis. + if (process.ExitCode != 0) + { + throw new InvalidOperationException( + $"Build failed with exit code {process.ExitCode}.\n" + + $"Command: dotnet {string.Join(" ", buildArgs)}\n" + + $"Output:\n{stdout}\n" + + $"Errors:\n{stderr}"); + } + } + + // Validate that the generator actually produced its output file. + Assert.True(File.Exists(generatedVersionsFile), $"Generated versions file not found at {generatedVersionsFile}"); + + return new BuildArtifacts(tempProjectDir, tempOutputDir, generatedVersionsFile); + } + + /// + /// Maps human-readable package labels used in test vectors to generated constant names in + /// PackageVersions.g.cs. + /// + /// Package label used by test case data. + /// Generated constant name expected in PackageVersions.g.cs. + private static string GetPackageVersionsConstantName(string packageLabel) + { + // Keep human-readable labels in test cases while asserting exact generated symbol names. + return packageLabel switch + { + "Abstractions" => "MicrosoftDataSqlClientExtensionsAbstractions", + "AKV Provider" => "MicrosoftDataSqlClientAlwaysEncryptedAzureKeyVaultProvider", + "Logging" => "MicrosoftDataSqlClientInternalLogging", + "SqlClient" => "MicrosoftDataSqlClient", + "SqlServer" => "MicrosoftSqlServerServer", + _ => throw new InvalidOperationException($"Unsupported package label '{packageLabel}'.") + }; + } + + /// + /// Recursively copies the source tree into an isolated workspace, excluding generated artifacts + /// and test folders that can destabilize isolated build behavior. + /// + /// Source directory to copy. + /// Destination directory for the isolated copy. + private static void CopyDirectory(string sourceDirectory, string destinationDirectory) + { + // Copy source tree into temp workspace while excluding volatile build/test folders. + Directory.CreateDirectory(destinationDirectory); + + foreach (string directory in Directory.GetDirectories(sourceDirectory, "*", SearchOption.AllDirectories)) + { + string relativePath = GetRelativePath(sourceDirectory, directory); + if (ShouldSkip(relativePath)) + { + continue; + } + + Directory.CreateDirectory(Path.Combine(destinationDirectory, relativePath)); + } + + foreach (string file in Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories)) + { + string relativePath = GetRelativePath(sourceDirectory, file); + if (ShouldSkip(relativePath)) + { + continue; + } + + string destinationFile = Path.Combine(destinationDirectory, relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(destinationFile)!); + File.Copy(file, destinationFile, overwrite: true); + } + } + + /// + /// Determines whether a relative path should be excluded from the isolated workspace copy. + /// + /// Path relative to the copy source root. + /// when the path should be skipped; otherwise . + private static bool ShouldSkip(string relativePath) + { + // Ignore generated artifacts and the test project itself to avoid recursive/self-copy issues. + string normalizedPath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + string[] segments = normalizedPath.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string segment in segments) + { + if (segment.Equals("bin", StringComparison.Ordinal) + || segment.Equals("obj", StringComparison.Ordinal) + || segment.Equals("packages", StringComparison.Ordinal) + || segment.Equals("test", StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + /// + /// Resolves and validates the repository path for the PackageCompatibility subtree that contains + /// the source project and central package-management files. + /// + /// Absolute path to the PackageCompatibility subtree root. + private static string GetPackageCompatibilityDirectory() + { + // Resolve the PackageCompatibility folder relative to the running test assembly. + string testDir = Path.GetDirectoryName(typeof(BuildVersionOverrideTests).Assembly.Location) + ?? throw new InvalidOperationException("Cannot determine test assembly directory"); + + string packageCompatibilityDir = Path.Combine(testDir, "..", "..", "..", ".."); + + packageCompatibilityDir = Path.GetFullPath(packageCompatibilityDir); + + if (!Directory.Exists(packageCompatibilityDir)) + { + throw new InvalidOperationException( + $"PackageCompatibility directory not found at {packageCompatibilityDir}.\n" + + $"Test assembly location: {testDir}\n" + + $"Calculated package compat dir: {Path.GetFullPath(packageCompatibilityDir)}"); + } + + // Ensure the expected project file exists before attempting to build. + string csprojFile = Path.Combine(packageCompatibilityDir, "src", "PackageCompatibility.csproj"); + if (!File.Exists(csprojFile)) + { + throw new InvalidOperationException( + $"Project file not found at {csprojFile}.\n" + + $"Expected to find PackageCompatibility.csproj in {packageCompatibilityDir}/src"); + } + + return packageCompatibilityDir; + } + + /// + /// Paths to the isolated build workspace and generated version artifacts for a single test run. + /// + /// Root of the isolated copied project tree. + /// Build output directory for binaries. + /// Path to generated PackageVersions.g.cs. + private sealed record BuildArtifacts(string ProjectDirectory, string OutputDirectory, string GeneratedVersionsFile); + +#if !NET + /// + /// Quotes a single command-line argument for use with + /// on .NET Framework, where ArgumentList is unavailable. + /// Wraps the value in double-quotes and escapes any embedded double-quotes. + /// + /// The argument to quote. + /// The argument, quoted if it contains whitespace or double-quote characters. + private static string QuoteArgument(string arg) + { + if (arg.IndexOfAny(new[] { ' ', '\t', '"' }) < 0) + { + return arg; + } + + return "\"" + arg.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; + } +#endif + + /// + /// Returns the relative path from to . + /// Polyfills which is unavailable on .NET Framework. + /// + private static string GetRelativePath(string relativeTo, string path) + { +#if NET + return Path.GetRelativePath(relativeTo, path); +#else + // Ensure the base URI ends with a separator so MakeRelativeUri treats it as a directory. + string baseStr = relativeTo.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + + Path.DirectorySeparatorChar; + Uri baseUri = new Uri(baseStr); + Uri targetUri = new Uri(path); + return Uri.UnescapeDataString(baseUri.MakeRelativeUri(targetUri).ToString()) + .Replace('/', Path.DirectorySeparatorChar); +#endif + } +} diff --git a/tools/PackageCompatibility/test/ConsoleCollection.cs b/tools/PackageCompatibility/test/ConsoleCollection.cs new file mode 100644 index 0000000000..1072c85279 --- /dev/null +++ b/tools/PackageCompatibility/test/ConsoleCollection.cs @@ -0,0 +1,27 @@ +using Xunit; + +namespace Microsoft.Data.SqlClient.Tools.PackageCompatibility.Tests; + +/// +/// xUnit collection definition used by tests that temporarily redirect global console streams. +/// Parallelization is disabled so tests that mutate and +/// cannot interfere with one another. +/// +[CollectionDefinition(Name, DisableParallelization = true)] +public sealed class ConsoleCollection : ICollectionFixture +{ + /// + /// Stable collection name referenced by console-mutating test classes. + /// + public const string Name = "Console"; +} + +/// +/// Fixture type associated with . +/// Present to satisfy xUnit collection fixture wiring; individual test classes in this collection +/// capture and restore console streams in their own constructor/Dispose lifecycle to keep each +/// test isolated. +/// +public sealed class ConsoleCollectionFixture +{ +} \ No newline at end of file diff --git a/tools/PackageCompatibility/test/EntryPointTests.cs b/tools/PackageCompatibility/test/EntryPointTests.cs new file mode 100644 index 0000000000..a068a5e8f8 --- /dev/null +++ b/tools/PackageCompatibility/test/EntryPointTests.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using System.Threading; + +using Microsoft.Data.SqlClient.Tools.PackageCompatibility; + +using Xunit; + +namespace Microsoft.Data.SqlClient.Tools.PackageCompatibility.Tests; + +/// +/// Verifies command-line entry-point behavior, including required argument handling and help-text +/// content that documents default package versions and override examples. +/// +[Collection(ConsoleCollection.Name)] +public class EntryPointTests : IDisposable +{ + private readonly TextWriter _originalOut; + private readonly TextWriter _originalError; + private readonly StringWriter _output; + + public EntryPointTests(ConsoleCollectionFixture _) + { + _originalOut = Console.Out; + _originalError = Console.Error; + _output = new StringWriter(); + Console.SetOut(_output); + Console.SetError(_output); + } + + public void Dispose() + { + Console.SetOut(_originalOut); + Console.SetError(_originalError); + _output.Dispose(); + } + + /// + /// Ensures invoking the entry point without the required connection-string argument fails and + /// reports the missing option to the caller. + /// + [Fact] + public void EntryPointWithoutConnectionStringReturnsNonZero() + { + // Act: invoke with no arguments; --connection-string is required. + int exitCode = EntryPoint.Main(Array.Empty()); + + // Wait for command help/validation text to flush before asserting. + string commandOutput = WaitForCapturedOutput(_output, "--connection-string"); + + // Assert: parser should reject the command and mention the missing option. + Assert.NotEqual(0, exitCode); + Assert.Contains("--connection-string", commandOutput, StringComparison.Ordinal); + } + + /// + /// Ensures help output includes both version-override examples and currently resolved default + /// package versions so users can understand and troubleshoot package selection. + /// + [Fact] + public void HelpOutputContainsDefaultVersions() + { + // Act: request help; this should never fail. + int exitCode = EntryPoint.Main(new[] { "--help" }); + Assert.Equal(0, exitCode); + + // Ensure final help footer is present so all formatted content has been emitted. + string helpOutput = WaitForCapturedOutput(_output, "--version"); + + // Assert: sample property overrides remain documented in help text. + Assert.Contains("-p:AbstractionsVersion=1.0.1", helpOutput, StringComparison.Ordinal); + Assert.Contains("-p:AkvProviderVersion=7.0.1-preview2", helpOutput, StringComparison.Ordinal); + Assert.Contains("-p:AzureVersion=1.0.0-preview1", helpOutput, StringComparison.Ordinal); + Assert.Contains("-p:LoggingVersion=1.0.2", helpOutput, StringComparison.Ordinal); + Assert.Contains("-p:SqlClientVersion=7.0.0-preview4", helpOutput, StringComparison.Ordinal); + Assert.Contains("-p:SqlServerVersion=1.0.0", helpOutput, StringComparison.Ordinal); + + // Assert: currently resolved package defaults are visible to aid troubleshooting. + Assert.Contains($"Abstractions: {PackageVersions.MicrosoftDataSqlClientExtensionsAbstractions}", helpOutput, StringComparison.Ordinal); + Assert.Contains($"AKV Provider: {PackageVersions.MicrosoftDataSqlClientAlwaysEncryptedAzureKeyVaultProvider}", helpOutput, StringComparison.Ordinal); + Assert.Contains("Azure: N/A", helpOutput, StringComparison.Ordinal); + Assert.Contains($"Logging: {PackageVersions.MicrosoftDataSqlClientInternalLogging}", helpOutput, StringComparison.Ordinal); + Assert.Contains($"SqlClient: {PackageVersions.MicrosoftDataSqlClient}", helpOutput, StringComparison.Ordinal); + Assert.Contains($"SqlServer: {PackageVersions.MicrosoftSqlServerServer}", helpOutput, StringComparison.Ordinal); + } + + /// + /// Waits briefly for asynchronously emitted command output to include a marker substring before + /// assertions run, reducing flakiness in console-capture tests. + /// + /// Captured console output writer. + /// Marker text that indicates output emission has completed. + /// The current captured output text. + private static string WaitForCapturedOutput(StringWriter output, string expectedSubstring) + { + // CommandLine writes asynchronously; wait briefly for expected marker text. + SpinWait.SpinUntil( + () => output.ToString().Contains(expectedSubstring, StringComparison.Ordinal), + TimeSpan.FromSeconds(1)); + + return output.ToString(); + } +} diff --git a/tools/PackageCompatibility/test/IsExternalInit.cs b/tools/PackageCompatibility/test/IsExternalInit.cs new file mode 100644 index 0000000000..d108129a8e --- /dev/null +++ b/tools/PackageCompatibility/test/IsExternalInit.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. + +// Polyfill required for 'record' types and 'init' setters on .NET Framework. +// The compiler emits a reference to this type when using these C# features; .NET 5+ +// includes it in the BCL, but .NET Framework does not. +#if NETFRAMEWORK +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit { } +} +#endif diff --git a/tools/PackageCompatibility/test/PackageCompatibility.Test.csproj b/tools/PackageCompatibility/test/PackageCompatibility.Test.csproj new file mode 100644 index 0000000000..4cf6dc685a --- /dev/null +++ b/tools/PackageCompatibility/test/PackageCompatibility.Test.csproj @@ -0,0 +1,25 @@ + + + + Exe + + net481;net10.0 + + Microsoft.Data.SqlClient.Tools.PackageCompatibility.Test + + enable + enable + latest + + true + + + + + + + + + + + From 1d44a2e08003b8f9c9d9d0552354d89e5f53df99 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Mon, 8 Jun 2026 12:46:31 -0500 Subject: [PATCH 135/298] Tests | Manual Test "Set" Trait (#4071) Co-authored-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> --- build.proj | 21 +- src/Microsoft.Data.SqlClient.slnx | 10 +- ...soft.Data.SqlClient.FunctionalTests.csproj | 4 +- .../ManualTests/AlwaysEncrypted/AKVTests.cs | 13 +- .../AlwaysEncrypted/AKVUnitTests.cs | 1 + .../ManualTests/AlwaysEncrypted/ApiShould.cs | 3 +- .../ManualTests/AlwaysEncrypted/BulkCopyAE.cs | 1 + .../AlwaysEncrypted/BulkCopyAEErrorMessage.cs | 1 + .../ColumnDecryptErrorTests.cs | 11 +- .../AlwaysEncrypted/ConversionTests.cs | 7 +- .../AlwaysEncrypted/CspProviderExt.cs | 3 +- .../DateOnlyReadTests.netcore.cs | 11 +- .../EnclaveAzureDatabaseTests.cs | 3 +- .../AlwaysEncrypted/End2EndSmokeTests.cs | 47 +-- .../AlwaysEncrypted/ExceptionTestAKVStore.cs | 1 + .../AlwaysEncrypted/ExceptionsGenericError.cs | 3 +- .../AlwaysEncrypted/SqlBulkCopyTruncation.cs | 5 +- .../AlwaysEncrypted/SqlNullValues.cs | 3 +- .../TestTrustedMasterKeyPaths.cs | 25 +- .../BulkCopy/AdjustPrecScaleForBulkCopy.cs | 3 +- .../BulkCopy/AzureDistributedTransaction.cs | 1 + .../tests/ManualTests/BulkCopy/Bug84548.cs | 1 + .../tests/ManualTests/BulkCopy/Bug85007.cs | 1 + .../tests/ManualTests/BulkCopy/Bug903514.cs | 1 + .../tests/ManualTests/BulkCopy/Bug98182.cs | 1 + .../ManualTests/BulkCopy/CacheMetadata.cs | 9 + .../ManualTests/BulkCopy/CheckConstraints.cs | 1 + .../ManualTests/BulkCopy/ColumnCollation.cs | 1 + .../ManualTests/BulkCopy/CopyAllFromReader.cs | 1 + .../BulkCopy/CopyAllFromReader1.cs | 1 + .../BulkCopy/CopyAllFromReaderAsync.cs | 1 + .../BulkCopy/CopyAllFromReaderCancelAsync.cs | 1 + .../CopyAllFromReaderConnectionCloseAsync.cs | 1 + ...llFromReaderConnectionCloseOnEventAsync.cs | 1 + .../BulkCopy/CopyMultipleReaders.cs | 1 + .../BulkCopy/CopySomeFromDatatable.cs | 1 + .../BulkCopy/CopySomeFromDatatableAsync.cs | 1 + .../BulkCopy/CopySomeFromReader.cs | 1 + .../BulkCopy/CopySomeFromRowArray.cs | 1 + .../BulkCopy/CopySomeFromRowArrayAsync.cs | 1 + .../ManualTests/BulkCopy/CopyVariants.cs | 1 + .../BulkCopy/CopyWidenNullInexactNumerics.cs | 1 + .../ManualTests/BulkCopy/CopyWithEvent.cs | 1 + .../ManualTests/BulkCopy/CopyWithEvent1.cs | 1 + .../BulkCopy/CopyWithEventAsync.cs | 1 + .../DataConversionErrorMessageTest.cs | 1 + .../DestinationTableNameWithSpecialChar.cs | 1 + .../BulkCopy/ErrorOnRowsMarkedAsDeleted.cs | 1 + .../tests/ManualTests/BulkCopy/FireTrigger.cs | 1 + .../BulkCopy/HiddenTargetColumn.cs | 3 +- .../BulkCopy/InvalidAccessFromEvent.cs | 1 + .../tests/ManualTests/BulkCopy/KeepNulls.cs | 1 + .../BulkCopy/MissingTargetColumn.cs | 1 + .../BulkCopy/MissingTargetColumns.cs | 1 + .../BulkCopy/MissingTargetTable.cs | 1 + .../tests/ManualTests/BulkCopy/OrderHint.cs | 1 + .../ManualTests/BulkCopy/OrderHintAsync.cs | 1 + .../BulkCopy/OrderHintDuplicateColumn.cs | 1 + .../BulkCopy/OrderHintIdentityColumn.cs | 1 + .../BulkCopy/OrderHintMissingTargetColumn.cs | 1 + .../BulkCopy/OrderHintTransaction.cs | 1 + .../BulkCopy/SpecialCharacterNames.cs | 1 + .../ManualTests/BulkCopy/SqlGraphTables.cs | 3 +- .../tests/ManualTests/BulkCopy/TableLock.cs | 1 + .../BulkCopy/TestBulkCopyWithUTF8.cs | 1 + .../tests/ManualTests/BulkCopy/Transaction.cs | 1 + .../ManualTests/BulkCopy/Transaction1.cs | 1 + .../ManualTests/BulkCopy/Transaction2.cs | 1 + .../ManualTests/BulkCopy/Transaction3.cs | 1 + .../ManualTests/BulkCopy/Transaction4.cs | 1 + .../BulkCopy/TransactionTestAsync.cs | 1 + .../ManualTests/BulkCopy/WriteToServerTest.cs | 1 + .../DDBasics/DDAsyncTest/DDAsyncTest.cs | 3 +- .../DDDataTypesTest/DDDataTypesTest.cs | 3 +- .../DDBasics/DDMARSTest/DDMARSTest.cs | 3 +- ...icrosoft.Data.SqlClient.ManualTests.csproj | 325 ++---------------- .../MultipleResultsTest.cs | 3 +- .../ProviderAgnostic/ReaderTest/ReaderTest.cs | 3 +- .../AADFedAuthTokenRefreshTest.cs | 5 +- .../SQL/AdapterTest/AdapterTest.cs | 3 +- .../AsyncCancelledConnectionsTest.cs | 3 +- .../SQL/AsyncTest/AsyncTimeoutTest.cs | 1 + .../SQL/AsyncTest/BeginExecAsyncTest.cs | 1 + .../SQL/AsyncTest/BeginExecReaderAsyncTest.cs | 1 + .../SQL/AsyncTest/XmlReaderAsyncTest.cs | 1 + .../tests/ManualTests/SQL/Batch/BatchTests.cs | 5 +- .../ConfigurableIpPreferenceTest.cs | 1 + .../ConnectionPoolStressTest.cs | 3 +- .../ConnectionPoolTest.Debug.cs | 1 + .../ConnectionPoolTest/ConnectionPoolTest.cs | 5 +- .../ConnectionPoolTest/PoolBlockPeriodTest.cs | 1 + .../ConnectionPoolTest/TransactionPoolTest.cs | 3 +- .../CertificateTest.cs | 1 + .../CertificateTestWithTdsServer.cs | 1 + .../ConnectivityTests/AADConnectionTest.cs | 1 + .../ConnectionBehaviorTest.cs | 1 + .../SQL/ConnectivityTests/ConnectivityTest.cs | 3 +- .../TcpDefaultForAzureTest.cs | 3 +- .../SQL/DNSCachingTest/DNSCachingTest.cs | 15 +- .../ConnectionSchemaTest.cs | 3 +- .../DataClassificationTest.cs | 1 + .../DataReaderCancellationTest.cs | 3 +- .../DataReaderTest/DataReaderStreamsTest.cs | 1 + .../SQL/DataReaderTest/DataReaderTest.cs | 3 +- .../DataSourceParserTest.cs | 1 + .../SQL/DataStreamTest/DataStreamTest.cs | 3 +- .../SQL/DateTimeTest/DateTimeTest.cs | 3 +- .../ExceptionTest/ConnectionExceptionTest.cs | 3 +- .../SQL/ExceptionTest/ExceptionTest.cs | 3 +- .../SQL/InstanceNameTest/InstanceNameTest.cs | 3 +- .../IntegratedAuthenticationTest.cs | 1 + .../SQL/JsonTest/JsonBulkCopyTest.cs | 1 + .../SQL/JsonTest/JsonStreamTest.cs | 1 + .../ManualTests/SQL/JsonTest/JsonTest.cs | 5 +- .../SQL/KerberosTests/KerberosTest.cs | 1 + .../SQL/LocalDBTest/LocalDBTest.cs | 3 +- .../MarsSessionPoolingTest.cs | 3 +- .../ManualTests/SQL/MARSTest/MARSTest.cs | 3 +- .../ConnectionOnMirroringTest.cs | 1 + .../ParallelTransactionsTest.cs | 3 +- .../SQL/ParameterTest/DateTimeVariantTests.cs | 269 +++++++-------- .../SQL/ParameterTest/OutputParameterTests.cs | 3 +- .../SQL/ParameterTest/ParametersTest.cs | 3 +- .../ParameterTest/SqlAdapterUpdateBatch.cs | 1 + .../ParameterTest/SqlVariantParameterTests.cs | 5 +- .../StreamInputParameterTests.cs | 3 +- .../ParameterTest/TvpColumnBoundariesTests.cs | 3 +- .../SQL/ParameterTest/TvpQueryHintsTests.cs | 3 +- .../ManualTests/SQL/ParameterTest/TvpTest.cs | 5 +- .../SQL/RandomStressTest/RandomStressTest.cs | 1 + .../SQL/RetryLogic/RetryLogicCounterTest.cs | 1 + .../SQL/RetryLogic/RetryLogicTestHelper.cs | 1 + .../RetryLogic/SqlCommandReliabilityTest.cs | 1 + .../SqlConfigurationManagerReliabilityTest.cs | 1 + .../SqlConnectionReliabilityTest.cs | 1 + .../SQL/SplitPacketTest/SplitPacketTest.cs | 3 +- .../SQL/SqlCommand/SqlCommandCancelTest.cs | 3 +- .../SQL/SqlCommand/SqlCommandCompletedTest.cs | 1 + .../SqlCommand/SqlCommandExecuteScalarTest.cs | 3 +- .../SQL/SqlCommand/SqlCommandSetTest.cs | 1 + .../SqlCommand/SqlCommandStoredProcTest.cs | 3 +- .../SqlCredentialTest/SqlCredentialTest.cs | 1 + .../SqlDataSourceEnumeratorTest.cs | 3 +- .../SqlDependencyTest/SqlDependencyTest.cs | 1 + .../SqlFileStreamTest/SqlFileStreamTest.cs | 13 +- .../SqlNamedPipesTest/SqlNamedPipesTest.cs | 3 +- .../SqlNotificationTest.cs | 3 +- .../SqlSchemaInfoTest/SqlSchemaInfoTest.cs | 1 + .../SqlStatisticsTest/SqlStatisticsTest.cs | 1 + .../DistributedTransactionTest.cs | 1 + .../TransactionEnlistmentTest.cs | 1 + .../SQL/TransactionTest/TransactionTest.cs | 3 +- .../SQL/UdtTest/SqlServerTypesTest.cs | 3 +- .../SQL/UdtTest/UdtBulkCopyTest.cs | 3 +- .../SQL/UdtTest/UdtDateTimeOffsetTest.cs | 1 + .../tests/ManualTests/SQL/UdtTest/UdtTest.cs | 3 +- .../tests/ManualTests/SQL/UdtTest/UdtTest2.cs | 3 +- .../SQL/Utf8SupportTest/Utf8SupportTest.cs | 1 + .../VectorTest/NativeVectorFloat32Tests.cs | 3 +- .../SQL/VectorTest/VectorAPIValidationTest.cs | 1 + .../VectorTypeBackwardCompatibilityTests.cs | 3 +- .../SQL/WeakRefTest/WeakRefTest.cs | 3 +- .../WeakRefTestYukonSpecific.cs | 3 +- .../TracingTests/DiagnosticTest.cs | 3 + .../TracingTests/EventSourceTest.cs | 3 +- .../ManualTests/TracingTests/MetricsTest.cs | 1 + .../TracingTests/XEventsTracingTest.cs | 2 +- .../UDTs => TestUdts}/Address/Address.cs | 0 .../UDTs => TestUdts}/Address/Address.csproj | 0 .../UDTs => TestUdts}/Circle/Circle.cs | 0 .../UDTs => TestUdts}/Circle/Circle.csproj | 0 .../UDTs => TestUdts}/Circle/Point1.cs | 0 .../UdtTest/UDTs => TestUdts}/Shapes/Line.cs | 0 .../UdtTest/UDTs => TestUdts}/Shapes/Point.cs | 0 .../UDTs => TestUdts}/Shapes/Shapes.csproj | 0 .../Utf8String/Utf8String.cs | 0 .../Utf8String/Utf8String.csproj | 0 177 files changed, 495 insertions(+), 575 deletions(-) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Address/Address.cs (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Address/Address.csproj (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Circle/Circle.cs (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Circle/Circle.csproj (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Circle/Point1.cs (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Shapes/Line.cs (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Shapes/Point.cs (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Shapes/Shapes.csproj (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Utf8String/Utf8String.cs (100%) rename src/Microsoft.Data.SqlClient/tests/{ManualTests/SQL/UdtTest/UDTs => TestUdts}/Utf8String/Utf8String.csproj (100%) diff --git a/build.proj b/build.proj index c13b40e246..4ff29a08df 100644 --- a/build.proj +++ b/build.proj @@ -353,13 +353,13 @@ TestSet Applies to: TestSqlClientManual Description: Used to select a set of tests to run from the SqlClient manual tests project. If not - specified, all tests will be executed. + specified, all manual tests will be executed. In the manual test target, this + value is converted into a Set trait filter and combined with any TestFilters. Default value: [blank] Allowed values: ([1][2][3][AE]) Examples: "1", "12", "AE", "12AE" --> - -p:TestSet="$(TestSet)" @@ -664,14 +664,27 @@ $(LogFilePrefix)-$(TestFramework) $(LogFilePrefix)-$(TestSet) + + $(TestSet.ToUpper()) + Set=1 + $(TestSetFilter)|Set=2 + $(TestSetFilter)|Set=3 + $(TestSetFilter)|Set=AE + $(TestSetFilter.Trim('|')) + + + $(TestFilters)&($(TestSetFilter)) + $(TestFilters) + $(TestSetFilter) + --filter "$(ManualTestFilters)" + "$(DotnetPath)dotnet" test "$(SqlClientManualTestProjectPath)" -p:Configuration=$(Configuration) $(TestBlameArgument) $(TestCodeCoverageArgument) - $(TestFiltersArgument) + $(ManualTestFiltersArgument) $(TestFrameworkArgument) - $(TestSetArgument) --results-directory "$(TestResultsFolderPath)" --logger:"trx;LogFilePrefix=$(LogFilePrefix)" diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index c45bbdf485..a578a82db6 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -205,11 +205,11 @@ - - - - - + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 8fe4c2be23..13673697db 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -77,7 +77,7 @@ - + @@ -101,7 +101,7 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs index 8a56a7690c..53232b1431 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public class AKVTest : IClassFixture { private readonly SQLSetupStrategyAzureKeyVault _fixture; @@ -59,12 +60,12 @@ public void TestEncryptDecryptWithAKV() } /* - This unit test is going to assess an issue where a failed decryption leaves a connection in a bad state + This unit test is going to assess an issue where a failed decryption leaves a connection in a bad state when it is returned to the connection pool. If a subsequent connection is retried it will result in an "Internal connection fatal error", - which causes that connection to be doomed, preventing it from being returned to the pool. + which causes that connection to be doomed, preventing it from being returned to the pool. Consequently, retrying a third connection will encounter the same decryption error, leading to a repetitive failure cycle. - The purpose of this unit test is to simulate a decryption error and verify that the connection remains usable when returned to the pool. + The purpose of this unit test is to simulate a decryption error and verify that the connection remains usable when returned to the pool. It aims to confirm that three consecutive connections will consistently fail with the "Failed to decrypt column" error. */ [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] @@ -100,10 +101,10 @@ public void ForcedColumnDecryptErrorTestShouldFail() // does not leave the connection in a bad state. // In each try, when a "Failed to decrypt error" is thrown, the connection's TDS Parser state object buffer is drained of any // pending data so it does not interfere with future operations. In addition, the TDS parser state object's reader.DataReady flag - // is set to false so that the calling function that catches the exception will not continue to use the reader. Otherwise, it will + // is set to false so that the calling function that catches the exception will not continue to use the reader. Otherwise, it will // timeout waiting to read data that doesn't exist. Also, the TDS Parser state object HasPendingData flag is also set to false // to indicate that the buffer has been cleared and to avoid it getting cleared again in SqlDataReader.TryCloseInternal function. - // Finally, after successfully handling the decryption error, the connection is then returned back to the connection pool without + // Finally, after successfully handling the decryption error, the connection is then returned back to the connection pool without // an error. A proof that the connection's state object is clean is in the second connection being able to throw the same error. // The third connection is for making sure we test 3 times as the minimum number of connections to reproduce the issue previously. for (int i = 0; i < 3; i++) @@ -182,7 +183,7 @@ public void TestLocalCekCacheIsScopedToProvider() sqlDataReader2.Close(); // global cek cache and local cek cache are populated above - // when using a new per-command provider, it will only use its local cek cache + // when using a new per-command provider, it will only use its local cek cache // the following query should fail due to an empty cek cache and invalid credentials customProvider[SqlColumnEncryptionAzureKeyVaultProvider.ProviderName] = new SqlColumnEncryptionAzureKeyVaultProvider(new ClientSecretCredential("tenant", "client", "secret")); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs index 54c0c12192..c8c3820978 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public class AKVUnitTests : IClassFixture { const string EncryptionAlgorithm = "RSA_OAEP"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index 620d7bd99a..feefe77adb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -24,6 +24,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted /// /// Always Encrypted public API Manual tests. /// + [Trait("Set", "AE")] // @TODO: These tests are known to not run reliably in DEBUG mode. [ConditionalClass(typeof(DataTestUtility), nameof(DataTestUtility.IsNotDebugBuild))] public sealed class ApiShould : IClassFixture, IDisposable diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAE.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAE.cs index c0b80fe977..eee3a71ebb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAE.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAE.cs @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted /// /// Always Encrypted public API Manual tests. /// + [Trait("Set", "AE")] public sealed class BulkCopyAE : IClassFixture, IDisposable { private SQLSetupStrategy fixture; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAEErrorMessage.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAEErrorMessage.cs index a824575f6f..b08b8eb325 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAEErrorMessage.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/BulkCopyAEErrorMessage.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted /// /// Always Encrypted public API Manual tests. /// + [Trait("Set", "AE")] public class BulkCopyAEErrorMessage : IClassFixture { private SQLSetupStrategy _fixture; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ColumnDecryptErrorTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ColumnDecryptErrorTests.cs index d7435b95af..7ed66d6708 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ColumnDecryptErrorTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ColumnDecryptErrorTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public sealed class ColumnDecryptErrorTests : IClassFixture, IDisposable { private SQLSetupStrategyAzureKeyVault fixture; @@ -28,13 +29,13 @@ public ColumnDecryptErrorTests(SQLSetupStrategyAzureKeyVault context) * properly reset before being returned to the pool. If this doesn't happen, then random bytes * may be left in the connection's state. These can interfere with the next operation that utilizes * the connection. - * + * * We test that state is properly reset by triggering the same error condition twice. Routing column key discovery - * away from AKV toward a dummy key store achieves this. Each connection pulls from a pool of max - * size one to ensure we are using the same internal connection/socket both times. We expect to + * away from AKV toward a dummy key store achieves this. Each connection pulls from a pool of max + * size one to ensure we are using the same internal connection/socket both times. We expect to * receive the "Failed to decrypt column" exception twice. If the state were not cleaned properly, * the second error would be different because the TDS stream would be unintelligible. - * + * * Finally, we assert that restoring the connection to AKV allows a successful query. */ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore), nameof(DataTestUtility.IsAKVSetupAvailable))] @@ -75,7 +76,7 @@ public void TestCleanConnectionAfterDecryptFail(string connString, string select using SqlCommand sqlCommand = new SqlCommand(string.Format(selectQuery, tableName), sqlConnection, null, SqlCommandColumnEncryptionSetting.Enabled); - + using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); Assert.True(sqlDataReader.HasRows, "FAILED: Select statement did not return any rows."); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs index f54609e2c0..5574b83c45 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -18,6 +18,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public sealed class ConversionTests : IDisposable, IClassFixture { @@ -621,7 +622,7 @@ private void AdjustSizePrecisionAndScale(ref ColumnMetaData largeColumnMeta, ref if (TypeHasSize(largeColumnMeta.ColumnType)) { - // 20% of the time use (max) as the length. + // 20% of the time use (max) as the length. largeColumnMeta.UseMax = (largeColumnMeta.ColumnType is SqlDbType.VarChar || largeColumnMeta.ColumnType is SqlDbType.NVarChar || largeColumnMeta.ColumnType is SqlDbType.VarBinary) && @@ -683,7 +684,7 @@ largeColumnMeta.ColumnType is SqlDbType.NVarChar || { smallColumnMeta.Precision = 0; - // For Time / DateTime2 / DateTimeOffset types, actual scale is set to 7 when parameter.scale is zero. + // For Time / DateTime2 / DateTimeOffset types, actual scale is set to 7 when parameter.scale is zero. // Active Issue in SQLParameter.cs when user wants to specify zero as the actual scale. smallColumnMeta.Scale = random.Next(minScale, largeColumnMeta.Scale); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index acb2e9278c..27b78acd10 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -18,6 +18,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted /// Always Encrypted public CspProvider Manual tests. /// // TODO: These tests are marked as Windows only for now but should be run for all platforms once the Master Key is accessible to this app from Azure Key Vault. + [Trait("Set", "AE")] [PlatformSpecific(TestPlatforms.Windows)] public class CspProviderExt { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/DateOnlyReadTests.netcore.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/DateOnlyReadTests.netcore.cs index 4c0900a68b..f91788c9c1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/DateOnlyReadTests.netcore.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/DateOnlyReadTests.netcore.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public sealed class DateOnlyReadTests : IClassFixture, IDisposable { private SQLSetupStrategy fixture; @@ -41,10 +42,10 @@ public void TestSelectOnEncryptedNonEncryptedColumns(string connString, string s // insert 1 row data CustomerDateOnly customer = new CustomerDateOnly( - 45, - "Microsoft", - "Corporation", - new DateOnly(2001, 1, 31), + 45, + "Microsoft", + "Corporation", + new DateOnly(2001, 1, 31), new TimeOnly(18, 36, 45)); DatabaseHelper.InsertCustomerDateOnlyData(sqlConn, null, tableName, customer); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs index 536be83a3e..4de82d39b7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -14,6 +14,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { // This test class is for internal use only + [Trait("Set", "AE")] public sealed class EnclaveAzureDatabaseTests : IDisposable, IClassFixture { private ColumnMasterKey akvColumnMasterKey; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/End2EndSmokeTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/End2EndSmokeTests.cs index a9c30646ae..6581fcf2e8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/End2EndSmokeTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/End2EndSmokeTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public sealed class End2EndSmokeTests : IClassFixture, IDisposable { private SQLSetupStrategy fixture; @@ -160,13 +161,13 @@ public IEnumerator GetEnumerator() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { - yield return new object[] { - connStrAE, + yield return new object[] { + connStrAE, true, /*sync*/ @"select CustomerId, FirstName, LastName from [{0}] where CustomerId = @CustomerId and FirstName = @FirstName", 3, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ - @"string", /*unencrypted datatype of second column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + @"string", /*unencrypted datatype of second column in select statement*/ @"string" /*datatype of third column in select statement*/}, 2, /*no:of input parameters*/ new object[] { @"CustomerId", /*input parameter name*/ @@ -177,13 +178,13 @@ public IEnumerator GetEnumerator() @"Microsoft" /*input parameter value*/} }; - yield return new object[] { + yield return new object[] { connStrAE, true, /*sync*/ @"select CustomerId, FirstName, LastName from [{0}] where CustomerId = @CustomerId", 3, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ - @"string", /*unencrypted datatype of second column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + @"string", /*unencrypted datatype of second column in select statement*/ @"string" /*datatype of third column in select statement*/}, 1, /*no:of input parameters*/ new object[] { @"CustomerId", /*input parameter name*/ @@ -191,13 +192,13 @@ public IEnumerator GetEnumerator() 45, /*input parameter value*/} }; - yield return new object[] { + yield return new object[] { connStrAE, true, /*sync*/ @"select CustomerId, FirstName, LastName from [{0}] where FirstName = @FirstName", 3, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ - @"string", /*unencrypted datatype of second column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + @"string", /*unencrypted datatype of second column in select statement*/ @"string" /*datatype of third column in select statement*/}, 1, /*no:of input parameters*/ new object[] { @"FirstName", /*input parameter name*/ @@ -210,8 +211,8 @@ public IEnumerator GetEnumerator() true, /*sync*/ @"select CustomerId, FirstName, LastName from [{0}] where LastName = @LastName", 3, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ - @"string", /*unencrypted datatype of second column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + @"string", /*unencrypted datatype of second column in select statement*/ @"string" /*datatype of third column in select statement*/}, 1, /*no:of input parameters*/ new object[] { @"LastName", /*input parameter name*/ @@ -224,7 +225,7 @@ public IEnumerator GetEnumerator() true, /*sync*/ @"select CustomerId, FirstName from [{0}] where CustomerId = @CustomerId and FirstName = @FirstName", 2, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ @"string", /*unencrypted datatype of second column in select statement*/}, 2, /*no:of input parameters*/ new object[] { @"CustomerId", /*input parameter name*/ @@ -240,8 +241,8 @@ public IEnumerator GetEnumerator() false, /*sync*/ @"select CustomerId, FirstName, LastName from [{0}] where CustomerId = @CustomerId and FirstName = @FirstName", 3, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ - @"string", /*unencrypted datatype of second column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + @"string", /*unencrypted datatype of second column in select statement*/ @"string" /*datatype of third column in select statement*/}, 2, /*no:of input parameters*/ new object[] { @"CustomerId", /*input parameter name*/ @@ -257,8 +258,8 @@ public IEnumerator GetEnumerator() false, /*sync*/ @"select CustomerId, FirstName, LastName from [{0}] where CustomerId = @CustomerId", 3, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ - @"string", /*unencrypted datatype of second column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + @"string", /*unencrypted datatype of second column in select statement*/ @"string" /*datatype of third column in select statement*/}, 1, /*no:of input parameters*/ new object[] { @"CustomerId", /*input parameter name*/ @@ -271,8 +272,8 @@ public IEnumerator GetEnumerator() false, /*sync*/ @"select CustomerId, FirstName, LastName from [{0}] where FirstName = @FirstName", 3, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ - @"string", /*unencrypted datatype of second column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + @"string", /*unencrypted datatype of second column in select statement*/ @"string" /*datatype of third column in select statement*/}, 1, /*no:of input parameters*/ new object[] { @"FirstName", /*input parameter name*/ @@ -285,8 +286,8 @@ public IEnumerator GetEnumerator() false, /*sync*/ @"select CustomerId, FirstName, LastName from [{0}] where LastName = @LastName", 3, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ - @"string", /*unencrypted datatype of second column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + @"string", /*unencrypted datatype of second column in select statement*/ @"string" /*datatype of third column in select statement*/}, 1, /*no:of input parameters*/ new object[] { @"LastName", /*input parameter name*/ @@ -299,7 +300,7 @@ public IEnumerator GetEnumerator() false, /*sync*/ @"select CustomerId, FirstName from [{0}] where CustomerId = @CustomerId and FirstName = @FirstName", 2, /*total number of columns in select statement*/ - new string[] { @"int", /*unencrypted datatype of first column in select statement*/ + new string[] { @"int", /*unencrypted datatype of first column in select statement*/ @"string", /*unencrypted datatype of second column in select statement*/}, 2, /*no:of input parameters*/ new object[] { @"CustomerId", /*input parameter name*/ diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs index 15679176ac..9f006c3f55 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public class ExceptionTestAKVStore : IClassFixture { private const string MasterKeyEncAlgo = "RSA_OAEP"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionsGenericError.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionsGenericError.cs index 1e3a00931a..00e45951db 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionsGenericError.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionsGenericError.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public class ExceptionsGenericErrors : IClassFixture { [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsNotAzureServer), Skip = "ActiveIssue 10036")] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlBulkCopyTruncation.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlBulkCopyTruncation.cs index 0bc6d63545..b80ed2c861 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlBulkCopyTruncation.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlBulkCopyTruncation.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public class SqlBulkCopyTruncation : IClassFixture { private SQLSetupStrategy _fixture; @@ -416,7 +417,7 @@ public void BulkCopySmallBinary(string connectionString) try { DoBulkCopy(tableNames["TabSmallBinarySource"], tableNames["TabSmallBinaryTarget"], connectionString); - // Verify its 8000 + // Verify its 8000 using (SqlConnection conn = new SqlConnection(GetOpenConnectionString(connectionString, true))) { conn.Open(); @@ -548,7 +549,7 @@ internal void TruncateTables(string sourceName, string targetName, string connec } /// - /// + /// /// /// /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs index c69a3cc314..f409a7db77 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public sealed class SqlNullValuesTests : IClassFixture, IDisposable { private SQLSetupStrategy fixture; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs index 5a4d3579be..b8ca21a68f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { + [Trait("Set", "AE")] public class TestTrustedMasterKeyPaths : IClassFixture { private readonly string dummyThumbprint; @@ -68,8 +69,8 @@ public void TestTrustedColumnEncryptionMasterKeyPathsWithNullDictionary(string c // Test INPUT parameter on an encrypted parameter using (SqlCommand sqlCommand = new SqlCommand( - $@"SELECT CustomerId, FirstName, LastName - FROM [{tableName}] + $@"SELECT CustomerId, FirstName, LastName + FROM [{tableName}] WHERE FirstName = @firstName", sqlConnection)) { @@ -113,8 +114,8 @@ public void TestTrustedColumnEncryptionMasterKeyPathsWithOneServer(string connec // Test INPUT parameter on an encrypted parameter using (SqlCommand sqlCommand = new SqlCommand( - $@"SELECT CustomerId, FirstName, LastName - FROM [{tableName}] + $@"SELECT CustomerId, FirstName, LastName + FROM [{tableName}] WHERE FirstName = @firstName", sqlConnection)) { @@ -171,8 +172,8 @@ public void TestTrustedColumnEncryptionMasterKeyPathsWithMultipleServers(string // Test INPUT parameter on an encrypted parameter using (SqlCommand sqlCommand = new SqlCommand( - $@"SELECT CustomerId, FirstName, LastName - FROM [{tableName}] + $@"SELECT CustomerId, FirstName, LastName + FROM [{tableName}] WHERE FirstName = @firstName", sqlConnection)) { @@ -217,8 +218,8 @@ public void TestTrustedColumnEncryptionMasterKeyPathsWithInvalidInputs(string co // Test INPUT parameter on an encrypted parameter using (SqlCommand sqlCommand = new SqlCommand( - $@"SELECT CustomerId, FirstName, LastName - FROM [{tableName}] + $@"SELECT CustomerId, FirstName, LastName + FROM [{tableName}] WHERE FirstName = @firstName", sqlConnection)) { @@ -249,8 +250,8 @@ FROM [{tableName}] // Test INPUT parameter on an encrypted parameter using (SqlCommand sqlCommand = new SqlCommand( - $@"SELECT CustomerId, FirstName, LastName - FROM [{tableName}] + $@"SELECT CustomerId, FirstName, LastName + FROM [{tableName}] WHERE FirstName = @firstName", sqlConnection)) { @@ -283,8 +284,8 @@ FROM [{tableName}] // Test INPUT parameter on an encrypted parameter using (SqlCommand sqlCommand = new SqlCommand( - $@"SELECT CustomerId, FirstName, LastName - FROM [{tableName}] + $@"SELECT CustomerId, FirstName, LastName + FROM [{tableName}] WHERE FirstName = @firstName", sqlConnection)) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AdjustPrecScaleForBulkCopy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AdjustPrecScaleForBulkCopy.cs index 6fdd82a6a1..1e64bf8255 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AdjustPrecScaleForBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AdjustPrecScaleForBulkCopy.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public static class AdjustPrecScaleForBulkCopy { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AzureDistributedTransaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AzureDistributedTransaction.cs index e767d5e6eb..69ec0c7660 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AzureDistributedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/AzureDistributedTransaction.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class AzureDistributedTransaction { private static readonly string s_connectionString = DataTestUtility.TCPConnectionString; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs index 02ac15a4c4..fad361ff9e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Bug84548 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs index 05eb812bbb..b3de348137 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Bug85007 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs index 2e51711339..d720fd9ebc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Bug903514 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs index 49699a81b7..f611e2e3b1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Bug98182 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs index 15f591524d..7443a99734 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CacheMetadata { private static readonly string sourceTable = "employees"; @@ -74,6 +75,7 @@ public void Test() } } + [Trait("Set", "2")] public class CacheMetadataInvalidate { private static readonly string sourceTable = "employees"; @@ -130,6 +132,7 @@ public void Test() } } + [Trait("Set", "2")] public class CacheMetadataDestinationChange { private static readonly string sourceTable = "employees"; @@ -189,6 +192,7 @@ public void Test() } } + [Trait("Set", "2")] public class CacheMetadataWithoutFlag { private static readonly string sourceTable = "employees"; @@ -243,6 +247,7 @@ public void Test() } } + [Trait("Set", "2")] public class CacheMetadataWithDataTable { private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50), col3 nvarchar(50))"; @@ -289,6 +294,7 @@ public void Test() } } + [Trait("Set", "2")] public class CacheMetadataColumnMappingsChange { private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50), col3 nvarchar(50))"; @@ -364,6 +370,7 @@ public void Test() } } + [Trait("Set", "2")] public class CacheMetadataColumnSubsetChange { private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50), col3 nvarchar(50))"; @@ -427,6 +434,7 @@ public void Test() } } + [Trait("Set", "2")] public class CacheMetadataAsync { private static readonly string sourceTable = "employees"; @@ -498,6 +506,7 @@ private static async Task TestAsync(string srcConstr, string dstConstr, string d } } + [Trait("Set", "2")] public class CacheMetadataCombinedWithKeepNulls { private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50) default 'DefaultVal', col3 nvarchar(50))"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs index f083cd28d1..ede548f9cf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CheckConstraints { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs index df2ccc5f6b..4cffae2fb2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class ColumnCollation { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs index 1211d861d6..23ff5ed245 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyAllFromReader { private static readonly string destinationTable = null; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs index 1557217f31..78bc18f17a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyAllFromReader1 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs index ab62f4843e..d9cc530472 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyAllFromReaderAsync { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs index acf93a4228..4329f7f5f4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyAllFromReaderCancelAsync { static CancellationTokenSource cts = null; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs index 5cea2952cd..44b9c89dd7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyAllFromReaderConnectionClosedAsync { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs index 89b354c3b2..4938014ca3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyAllFromReaderConnectionClosedOnEventAsync { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs index f97020024a..043c0f75f5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyMultipleReaders { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs index f380dbcf41..324ddad5c8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopySomeFromDataTable { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs index afac0b87b3..7a5eb6d8e7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopySomeFromDataTableAsync { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs index ada0ea4d1e..9d52892baf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopySomeFromReader { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs index 5a3975ca22..be48bf4351 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopySomeFromRowArray { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs index 20349dd153..b74144fc77 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopySomeFromRowArrayAsync { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs index 6126a50c1a..d690f849ca 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyVariants { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWidenNullInexactNumerics.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWidenNullInexactNumerics.cs index 9fd56392c3..00dd954119 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWidenNullInexactNumerics.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWidenNullInexactNumerics.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyWidenNullInexactNumerics { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs index 00aae3c5a9..fedfde00b9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyWithEvent { private static readonly long[] ExpectedRowCopiedResults = { 50, 100 }; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs index 60d4dc5774..4388594033 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyWithEvent1 { private static SqlBulkCopy bulkcopy; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs index 24cad97bf0..f552b0610b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class CopyWithEventAsync { private static readonly long[] ExpectedRowCopiedResults = { 50, 100 }; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs index bb9af9d2fd..11c4a262a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs @@ -86,6 +86,7 @@ public void Dispose() } } + [Trait("Set", "2")] public class DataConversionErrorMessageTest : IClassFixture { private readonly InitialDatabase _fixture; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs index 301f272190..230d450f80 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class DestinationTableNameWithSpecialChar { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs index d52c62947e..3ac8636467 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy /// /// Test case for SqlBulkEdit raising exception on rows marked as deleted /// + [Trait("Set", "2")] public class ErrorOnRowsMarkedAsDeleted { // This char is used to mark the row as 'to be deleted'. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs index 321cd5b738..6353197634 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class FireTrigger { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs index 50c56dbd02..0f18df215e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class HiddenTargetColumn { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs index bcc3a50471..19bdf2193e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class InvalidAccessFromEvent { private static string _dstConstr; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs index 7067cf5d7b..5228c1a35d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class KeepNulls { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs index c3e0f62789..ac5651f34f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class MissingTargetColumn { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs index 9265263b5d..2728b1efb4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class MissingTargetColumns { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs index a440771203..af103c1c0a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class MissingTargetTable { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHint.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHint.cs index 77512a4fdd..aeb6a277ed 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHint.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHint.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class OrderHint { private static readonly string sourceTable = "Customers"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintAsync.cs index 7b84097c1f..4741f66293 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintAsync.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class OrderHintAsync { private static readonly string sourceTable = "Customers"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintDuplicateColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintDuplicateColumn.cs index 0b15f4e063..4a07b5b6a4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintDuplicateColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintDuplicateColumn.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class OrderHintDuplicateColumn { private static readonly string sourceTable = "Customers"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintIdentityColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintIdentityColumn.cs index 4bc75496ee..96a218bfdc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintIdentityColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintIdentityColumn.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class OrderHintIdentityColumn { private static readonly string sourceTable = "Customers"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintMissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintMissingTargetColumn.cs index 2245453bc4..51d9c7abcd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintMissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintMissingTargetColumn.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class OrderHintMissingTargetColumn { private static readonly string sourceTable = "Customers"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs index 30a17024f9..e8a0571467 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class OrderHintTransaction { private static readonly string sourceTable = "Customers"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs index 1f57f32f19..1c10feebad 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class SpecialCharacterNames { private static string EscapeIdentifier(string name) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SqlGraphTables.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SqlGraphTables.cs index 0c0ec9aa1a..12353e8695 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SqlGraphTables.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SqlGraphTables.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class SqlGraphTables { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsAtLeastSQL2017))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs index 9bddfcea17..69c090d655 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class TableLock { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TestBulkCopyWithUTF8.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TestBulkCopyWithUTF8.cs index db3ea66f94..21cf670ae7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TestBulkCopyWithUTF8.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TestBulkCopyWithUTF8.cs @@ -14,6 +14,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy /// 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. /// + [Trait("Set", "2")] public sealed class TestBulkCopyWithUtf8 : IDisposable { private static string s_sourceTable = DataTestUtility.GetShortName("SourceTableForUTF8Data"); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs index 6fe82ab11e..b5f5e32b53 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Transaction { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs index c60a76a5f7..665b5a5108 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Transaction1 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs index 3e28ab5d72..9c3f69e28a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Transaction2 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs index 0b3bd0b999..f28aaad8c3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Transaction3 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs index 9d219187d8..2c48368483 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class Transaction4 { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs index 11a1049292..537f68d8f0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class TransactionTestAsync { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/WriteToServerTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/WriteToServerTest.cs index 871aaf3495..3a4acc0e9b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/WriteToServerTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/WriteToServerTest.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { + [Trait("Set", "2")] public class WriteToServerTest { private readonly string _connectionString = null; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDAsyncTest/DDAsyncTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDAsyncTest/DDAsyncTest.cs index bb1fd24a1f..ec2d5f060e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDAsyncTest/DDAsyncTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDAsyncTest/DDAsyncTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class DDAsyncTest { #region <> diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDDataTypesTest/DDDataTypesTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDDataTypesTest/DDDataTypesTest.cs index eda1a2c788..320102318e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDDataTypesTest/DDDataTypesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDDataTypesTest/DDDataTypesTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class DDDataTypesTest { // Synapse: Cannot find data type 'XML'. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDMARSTest/DDMARSTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDMARSTest/DDMARSTest.cs index ad057e587d..33dbf2600a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDMARSTest/DDMARSTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DDBasics/DDMARSTest/DDMARSTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -6,6 +6,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class DDMARSTest { // TODO Synapse: Remove dependency from Northwind database diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index d47ec611de..ce159f7821 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -4,9 +4,6 @@ ManualTests Microsoft.Data.SqlClient.ManualTests true - - - false @@ -20,245 +17,8 @@ $(TargetFrameworks);net462 - - - - - - - 123AE - $(TestSet.ToLower()) - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - xunit.runner.json - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest @@ -276,53 +36,28 @@ PreserveNewest TvpColumnBoundaries_Azure.bsl - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + PreserveNewest + xunit.runner.json + + + PreserveNewest + CodeCoverage.runsettings + @@ -370,12 +105,12 @@ - - - - + + + + @@ -397,11 +132,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 bf1551373b..a04d8df76a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class MultipleResultsTest { private const string ResultSet1_Message = "0"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs index 50e2b9253c..0743e8fad9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class ReaderTest { // TODO Synapse: Remove dependency from Northwind database 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 e99192732d..e5616776b1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class AADFedAuthTokenRefreshTest { private readonly ITestOutputHelper _testOutputHelper; @@ -31,7 +32,7 @@ public void FedAuthTokenRefreshTest() #pragma warning disable 0618 // Type or member is obsolete SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(DataTestUtility.ApplicationClientId)); #pragma warning restore 0618 // Type or member is obsolete - + string connectionString = DataTestUtility.AADPasswordConnectionString; using SqlConnection connection = new SqlConnection(connectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs index 798890c3fa..960f3ee822 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -15,6 +15,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class AdapterTest { private char[] _appendNewLineIndentBuffer = new char[0]; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs index 26f65b6fae..6fa45147da 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs @@ -61,6 +61,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// pool-timeout failures on otherwise healthy runs. /// /// + [Trait("Set", "1")] public class AsyncCancelledConnectionsTest { /// @@ -330,7 +331,7 @@ private async Task TimeBombAsync(SqlCommand command) delayMs = _random.Next(100, 3000); } await Task.Delay(delayMs); - + // Cancel the command command.Cancel(); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs index bc514dce92..1c0abdc5d7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncTimeoutTest.cs @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "1")] public static class AsyncTimeoutTest { static string delayQuery2s = "WAITFOR DELAY '00:00:02'"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecAsyncTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecAsyncTest.cs index cdf89fbac1..131d2f38f3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecAsyncTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecAsyncTest.cs @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "1")] public static class BeginExecAsyncTest { private static string GenerateCommandText() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecReaderAsyncTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecReaderAsyncTest.cs index daa58b1851..65eeec5229 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecReaderAsyncTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/BeginExecReaderAsyncTest.cs @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "1")] public static class BeginExecReaderAsyncTest { private static string GenerateCommandText() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/XmlReaderAsyncTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/XmlReaderAsyncTest.cs index 8be5437b9f..780edfa8bf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/XmlReaderAsyncTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/XmlReaderAsyncTest.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "1")] public static class XmlReaderAsyncTest { private const string CommandText = 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 991d55cdeb..01d54f2462 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class BatchTests { @@ -379,8 +380,8 @@ public static void ParameterInOutAndReturn() { string create = @" -CREATE PROCEDURE TestInAndOutParams - @Input int, +CREATE PROCEDURE TestInAndOutParams + @Input int, @InOut int OUTPUT, @Output int = default OUTPUT AS 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 160f7f550e..92db4f1101 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -18,6 +18,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class ConfigurableIpPreferenceTest { private const string CnnPrefIPv6 = ";IPAddressPreference=IPv6First"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs index d3d41d4c9a..e843e108be 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolStressTest.cs @@ -19,6 +19,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// /// Connection pool stress test to validate pool behavior under various concurrent load scenarios. /// + [Trait("Set", "3")] public class ConnectionPoolStressTest { #region Properties @@ -111,7 +112,7 @@ private bool DoomMicrosoftDataConnection(SqlConnection connection) #region Configuration /// - /// Sets up connection string + /// Sets up connection string /// /// Connection string to be set. internal void SetConnectionString(string connectionString) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.Debug.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.Debug.cs index 130ef02934..b2ad4f6fd1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.Debug.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.Debug.cs @@ -18,6 +18,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests // xUnit won't run tests in an abstract class. // // public static class ConnectionPoolTestDebug + [Trait("Set", "3")] public abstract class ConnectionPoolTestDebug { [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsUsingManagedSNI))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs index 0d9b076dd2..4b63ff655f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -15,7 +15,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public class ConnectionPoolConnectionStringProvider : IEnumerable { private static readonly string _TCPConnectionString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { - MultipleActiveResultSets = false, + MultipleActiveResultSets = false, Pooling = true}.ConnectionString; private static readonly string _tcpMarsConnStr = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = true, @@ -57,6 +57,7 @@ public IEnumerator GetEnumerator() } // TODO Synapse: Fix these tests for Azure Synapse. + [Trait("Set", "3")] public static class ConnectionPoolTest { /// 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 6d14f6fbd8..84f779cf96 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class PoolBlockPeriodTest { private const string AzureEndpointSample = "nonexistent.database.windows.net"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/TransactionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/TransactionPoolTest.cs index 6e358cc63a..96432c8bd6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/TransactionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/TransactionPoolTest.cs @@ -8,11 +8,12 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)] public static class TransactionPoolTest { /// - /// Tests if connections in a distributed transaction are put into a transaction pool. Also checks that clearallpools + /// Tests if connections in a distributed transaction are put into a transaction pool. Also checks that clearallpools /// does not clear transaction connections and that the transaction root is put into "stasis" when closed /// Synapse: only supports local transaction request. /// 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 32780dcfe6..36dda08cb2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs @@ -18,6 +18,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "1")] [PlatformSpecific(TestPlatforms.Windows)] public class CertificateTest : IDisposable { 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 bb1bb8b5f2..828a87d63e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs @@ -19,6 +19,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "1")] public class CertificateTestWithTdsServer : IDisposable { private static readonly string s_fullPathToPowershellScript = Path.Combine(Directory.GetCurrentDirectory(), "makepfxcert.ps1"); 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 3c68e6dafd..b60b4a1e9e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class AADConnectionTest { private static void ConnectAndDisconnect(string connectionString, SqlCredential credential = null) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectionBehaviorTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectionBehaviorTest.cs index 5b108400b0..2df2ddcd51 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectionBehaviorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectionBehaviorTest.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class ConnectionBehaviorTest { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] 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 28955b39ff..fbbee8db26 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public static class ConnectivityTest { private const string COL_SPID = "SPID"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/TcpDefaultForAzureTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/TcpDefaultForAzureTest.cs index f8271dfdc4..1ee593279b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/TcpDefaultForAzureTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/TcpDefaultForAzureTest.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] [OuterLoop("Takes minutes on some networks")] public static class TcpDefaultForAzureTest { @@ -35,7 +36,7 @@ static TcpDefaultForAzureTest() // Synapse: Named Pipes not supported for Azure Synapse // Expected protocol TCP Provider in the error message, but received: 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. + // 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: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [PlatformSpecific(TestPlatforms.Windows)] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs index a23efff073..555005618a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -10,14 +10,15 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class DNSCachingTest { public static Assembly systemData = Assembly.GetAssembly(typeof(SqlConnection)); public static Type SQLFallbackDNSCacheType = systemData.GetType("Microsoft.Data.SqlClient.SQLFallbackDNSCache"); public static Type SQLDNSInfoType = systemData.GetType("Microsoft.Data.SqlClient.SQLDNSInfo"); public static MethodInfo SQLFallbackDNSCacheGetDNSInfo = SQLFallbackDNSCacheType.GetMethod("GetDNSInfo", BindingFlags.Instance | BindingFlags.NonPublic); - - + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsDNSCachingSetup))] public void DNSCachingIsSupportedFlag() { @@ -37,21 +38,21 @@ public void DNSCachingIsSupportedFlag() Assert.Equal(expectedDNSCachingSupportedTR, isSupportedStateTR); } } - + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsDNSCachingSetup))] public void DNSCachingGetDNSInfo() - { + { using(SqlConnection connection = new SqlConnection(DataTestUtility.DNSCachingConnString)) { connection.Open(); } var SQLFallbackDNSCacheInstance = SQLFallbackDNSCacheType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public).GetValue(null); - + var serverList = new List>(); serverList.Add(new KeyValuePair(DataTestUtility.DNSCachingServerCR, DataTestUtility.IsDNSCachingSupportedCR)); serverList.Add(new KeyValuePair(DataTestUtility.DNSCachingServerTR, DataTestUtility.IsDNSCachingSupportedTR)); - + foreach(var server in serverList) { object[] parameters; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs index 028447251c..0589cd8844 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataBaseSchemaTest/ConnectionSchemaTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public static class ConnectionSchemaTest { public static bool CanRunSchemaTests() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs index ce5174b6ca..b2322baa92 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public static class DataClassificationTest { private static string s_tableName; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderCancellationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderCancellationTest.cs index 5199922174..4b0ef2e8a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderCancellationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderCancellationTest.cs @@ -9,12 +9,13 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class DataReaderCancellationTest { /// /// Test ensures cancellation token is registered before ReadAsync starts processing results from TDS Stream, /// such that when Cancel is triggered, the token is capable of canceling reading further results. - /// Synapse: Incompatible query. + /// Synapse: Incompatible query. /// /// Async Task [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] 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 380978f049..cf85112559 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs @@ -14,6 +14,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public static class DataReaderStreamsTest { [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] 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 aad117b336..2a2132dbcb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -19,6 +19,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public static class DataReaderTest { private static readonly object s_rowVersionLock = new(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataSourceParserTest/DataSourceParserTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataSourceParserTest/DataSourceParserTest.cs index 38d4685537..976260e98c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataSourceParserTest/DataSourceParserTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataSourceParserTest/DataSourceParserTest.cs @@ -6,6 +6,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.DataSourceParserTest { + [Trait("Set", "3")] public class DataSourceParserTest { [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotAzureSynapse))] 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 a49e14de6e..17c760ace9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -18,6 +18,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class DataStreamTest { private readonly string _testName; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs index b313d89e24..0867164cff 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public static class DateTimeTest { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] 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 c44ed97ed0..2d5162bcb2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class ConnectionExceptionTest { // test connection string diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ExceptionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ExceptionTest.cs index 4179b7bf4b..81dbd0e35d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ExceptionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ExceptionTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public static class ExceptionTest { // data value and server consts 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 4612145331..437e02c579 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class InstanceNameTest { private const char SemicolonSeparator = ';'; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs index 5342448af5..69acede640 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/IntegratedAuthenticationTest/IntegratedAuthenticationTest.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class IntegratedAuthenticationTest { private static bool IsIntegratedSecurityEnvironmentSet() => DataTestUtility.IsIntegratedSecuritySetup(); 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 06b76c7ed4..e9ad8be557 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs @@ -15,6 +15,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.JsonTest { + [Trait("Set", "3")] public class JsonBulkCopyTest { private readonly ITestOutputHelper _output; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs index 3cb0f1b534..eef2902932 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs @@ -18,6 +18,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class JsonRecord { public int Id { get; set; } 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 2b2fd2c8db..887482ed95 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs @@ -15,6 +15,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class JsonTest { private const string JsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]"; @@ -96,7 +97,7 @@ public void TestJsonWrite() int rowsAffected = command.ExecuteNonQuery(); ValidateRowsAffected(rowsAffected); - //Test 2 + //Test 2 //Write a SqlString type as json parameter.Value = new SqlString(JsonDataString); int rowsAffected2 = command.ExecuteNonQuery(); @@ -141,7 +142,7 @@ public async Task TestJsonWriteAsync() int rowsAffected = await command.ExecuteNonQueryAsync(); ValidateRowsAffected(rowsAffected); - //Test 2 + //Test 2 //Write a SqlString type as json parameter.Value = new SqlString(JsonDataString); int rowsAffected2 = await command.ExecuteNonQueryAsync(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/KerberosTests/KerberosTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/KerberosTests/KerberosTest.cs index c6026088f4..e7d22907e0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/KerberosTests/KerberosTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/KerberosTests/KerberosTest.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class KerberosTests { [PlatformSpecific(TestPlatforms.AnyUnix)] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs index c3aed471c0..f1fc1bb1a3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class LocalDBTest { private enum InfoType diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs index 14b5ab54c4..e84c9fabb6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class MarsSessionPoolingTest { private const int ConcurrentCommands = 5; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs index 1213c5bfa4..a067ad841a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class MARSTest { private static readonly string _connStr = (new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = true }).ConnectionString; 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 460d566a6d..15495d00b0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class ConnectionOnMirroringTest { private static ManualResetEvent workerCompletedEvent = new ManualResetEvent(false); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs index 73fbd8bdc6..6f3f74fe4e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class ParallelTransactionsTest { #region <> diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs index ec9f516260..05a01788c3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -51,16 +51,17 @@ public TestResult(object value, string baseTypeName) /// /// Tests for DateTime variant parameters with different date/time types. /// + [Trait("Set", "3")] [Trait("Category", "flaky")] public class DateTimeVariantTests { private static void RunTest( TestVariations tag, Func action, - object paramValue, - string expectedBaseTypeName, - string connStr, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + string connStr, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -127,9 +128,9 @@ private static void xsql(SqlConnection conn, string sql) [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSimpleParameter_Type( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -152,10 +153,10 @@ public void TestSimpleParameter_Type( return new TestResult(dr[0], dr.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -163,9 +164,9 @@ public void TestSimpleParameter_Type( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSimpleParameter_Variant( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -188,10 +189,10 @@ public void TestSimpleParameter_Variant( return new TestResult(dr[0], dr.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -199,9 +200,9 @@ public void TestSimpleParameter_Variant( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSqlDataRecordParameterToTVP_Type( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -231,10 +232,10 @@ public void TestSqlDataRecordParameterToTVP_Type( return new TestResult(dr[0], dr.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -242,9 +243,9 @@ public void TestSqlDataRecordParameterToTVP_Type( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSqlDataRecordParameterToTVP_Variant( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -274,10 +275,10 @@ public void TestSqlDataRecordParameterToTVP_Variant( return new TestResult(dr[0], dr.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -285,9 +286,9 @@ public void TestSqlDataRecordParameterToTVP_Variant( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSqlDataReaderParameterToTVP_Type( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -320,10 +321,10 @@ public void TestSqlDataReaderParameterToTVP_Type( } } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -331,9 +332,9 @@ public void TestSqlDataReaderParameterToTVP_Type( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSqlDataReaderParameterToTVP_Variant( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -368,10 +369,10 @@ public void TestSqlDataReaderParameterToTVP_Variant( } } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -379,9 +380,9 @@ public void TestSqlDataReaderParameterToTVP_Variant( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSqlDataReader_TVP_Type( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -437,10 +438,10 @@ public void TestSqlDataReader_TVP_Type( } } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -448,9 +449,9 @@ public void TestSqlDataReader_TVP_Type( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSqlDataReader_TVP_Variant( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -506,10 +507,10 @@ public void TestSqlDataReader_TVP_Variant( } } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -517,9 +518,9 @@ public void TestSqlDataReader_TVP_Variant( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSimpleDataReader_Type( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -557,10 +558,10 @@ public void TestSimpleDataReader_Type( return new TestResult(dr[0], dr.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -568,9 +569,9 @@ public void TestSimpleDataReader_Type( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void TestSimpleDataReader_Variant( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -608,10 +609,10 @@ public void TestSimpleDataReader_Variant( return new TestResult(dr[0], dr.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -619,9 +620,9 @@ public void TestSimpleDataReader_Variant( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void SqlBulkCopySqlDataReader_Type( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -675,10 +676,10 @@ public void SqlBulkCopySqlDataReader_Type( } } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -686,9 +687,9 @@ public void SqlBulkCopySqlDataReader_Type( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void SqlBulkCopySqlDataReader_Variant( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -746,10 +747,10 @@ public void SqlBulkCopySqlDataReader_Variant( } } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -757,9 +758,9 @@ public void SqlBulkCopySqlDataReader_Variant( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void SqlBulkCopyDataTable_Type( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -795,10 +796,10 @@ public void SqlBulkCopyDataTable_Type( return new TestResult(drVerify[0], drVerify.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -806,9 +807,9 @@ public void SqlBulkCopyDataTable_Type( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void SqlBulkCopyDataTable_Variant( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -844,10 +845,10 @@ public void SqlBulkCopyDataTable_Variant( return new TestResult(drVerify[0], drVerify.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -855,9 +856,9 @@ public void SqlBulkCopyDataTable_Variant( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void SqlBulkCopyDataRow_Type( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -888,10 +889,10 @@ public void SqlBulkCopyDataRow_Type( return new TestResult(drVerify[0], drVerify.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -899,9 +900,9 @@ public void SqlBulkCopyDataRow_Type( [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(GetParameterCombinations), DisableDiscoveryEnumeration = true)] public void SqlBulkCopyDataRow_Variant( - object paramValue, - string expectedBaseTypeName, - Dictionary expectedExceptions, + object paramValue, + string expectedBaseTypeName, + Dictionary expectedExceptions, Dictionary expectedValueOverrides, Dictionary expectedBaseTypeOverrides) { @@ -932,10 +933,10 @@ public void SqlBulkCopyDataRow_Variant( return new TestResult(drVerify[0], drVerify.GetString(1)); } }, - paramValue, - expectedBaseTypeName, - DataTestUtility.TCPConnectionString, - expectedExceptions, + paramValue, + expectedBaseTypeName, + DataTestUtility.TCPConnectionString, + expectedExceptions, expectedValueOverrides, expectedBaseTypeOverrides); } @@ -954,11 +955,11 @@ public static IEnumerable GetParameterCombinations() { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, - { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, - new Dictionary(), + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary(), new Dictionary()}; - yield return new object[] { DateTime.MaxValue, "date", - new Dictionary(), + yield return new object[] { DateTime.MaxValue, "date", + new Dictionary(), new Dictionary { { TestVariations.TestSimpleParameter_Type, new DateTime(3155378112000000000) }, @@ -993,10 +994,10 @@ public static IEnumerable GetParameterCombinations() { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, - { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, new Dictionary(), new Dictionary()}; - yield return new object[] { DateTime.MaxValue, "datetime2", + yield return new object[] { DateTime.MaxValue, "datetime2", new Dictionary(), new Dictionary { {TestVariations.TestSimpleParameter_Variant, new DateTime(3155378975999970000)}, @@ -1013,8 +1014,8 @@ public static IEnumerable GetParameterCombinations() {TestVariations.SqlBulkCopyDataTable_Variant, "datetime"}, {TestVariations.SqlBulkCopyDataRow_Variant, "datetime"} }}; - yield return new object[] { DateTime.MinValue, "datetime", - new Dictionary { + yield return new object[] { DateTime.MinValue, "datetime", + new Dictionary { { TestVariations.TestSimpleParameter_Type, SqlDateTimeOverflow }, { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, { TestVariations.TestSqlDataRecordParameterToTVP_Type, SqlDateTimeOverflow }, @@ -1026,21 +1027,21 @@ public static IEnumerable GetParameterCombinations() { TestVariations.TestSimpleDataReader_Type, VarcharToDateTimeOutOfRange}, { TestVariations.TestSimpleDataReader_Variant, VarcharToDateTimeOutOfRange}, { TestVariations.SqlBulkCopySqlDataReader_Type, VarcharToDateTimeOutOfRange}, - { TestVariations.SqlBulkCopySqlDataReader_Variant, VarcharToDateTimeOutOfRange}, + { TestVariations.SqlBulkCopySqlDataReader_Variant, VarcharToDateTimeOutOfRange}, { TestVariations.SqlBulkCopyDataTable_Type, SqlDateTimeOverflow}, { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow}, { TestVariations.SqlBulkCopyDataRow_Type, SqlDateTimeOverflow}, { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow}}, new Dictionary(), new Dictionary()}; - yield return new object[] { DateTime.MaxValue, "datetime", - new Dictionary { + yield return new object[] { DateTime.MaxValue, "datetime", + new Dictionary { { TestVariations.TestSqlDataReader_TVP_Type, CannotConvertCharacterStringToDateOrTime}, { TestVariations.TestSqlDataReader_TVP_Variant, CannotConvertCharacterStringToDateOrTime}, { TestVariations.TestSimpleDataReader_Type, CannotConvertCharacterStringToDateOrTime}, { TestVariations.TestSimpleDataReader_Variant, CannotConvertCharacterStringToDateOrTime}, { TestVariations.SqlBulkCopySqlDataReader_Type, CannotConvertCharacterStringToDateOrTime}, - { TestVariations.SqlBulkCopySqlDataReader_Variant, CannotConvertCharacterStringToDateOrTime}}, + { TestVariations.SqlBulkCopySqlDataReader_Variant, CannotConvertCharacterStringToDateOrTime}}, new Dictionary { { TestVariations.TestSimpleParameter_Type, new DateTime(3155378975999970000) }, @@ -1123,7 +1124,7 @@ public static IEnumerable GetParameterCombinations() { TestVariations.SqlBulkCopySqlDataReader_Type, ConversionFailedCharStringToSmallDateTime }, { TestVariations.SqlBulkCopySqlDataReader_Variant, ConversionFailedCharStringToSmallDateTime }, { TestVariations.SqlBulkCopyDataTable_Type, UnRepresentableDateTime }, - { TestVariations.SqlBulkCopyDataRow_Type, UnRepresentableDateTime }}, + { TestVariations.SqlBulkCopyDataRow_Type, UnRepresentableDateTime }}, new Dictionary { { TestVariations.TestSimpleParameter_Variant, new DateTime(3155378975999970000) }, { TestVariations.TestSqlDataRecordParameterToTVP_Variant, new DateTime(3155378975999970000) }, @@ -1181,7 +1182,7 @@ public static IEnumerable GetParameterCombinations() new Dictionary(), new Dictionary()}; yield return new object[] { DateTime.MinValue, "time", - new Dictionary { + new Dictionary { { TestVariations.SqlBulkCopyDataTable_Type, CannotConvertMinDateTimeToTime}, { TestVariations.SqlBulkCopyDataRow_Type, CannotConvertMinDateTimeToTime}, { TestVariations.TestSimpleParameter_Type, InvalidCastDateTimeToTimeSpan }, @@ -1197,7 +1198,7 @@ public static IEnumerable GetParameterCombinations() { TestVariations.SqlBulkCopySqlDataReader_Type, InvalidCastNotValid }, { TestVariations.SqlBulkCopySqlDataReader_Variant, InvalidCastNotValid }, { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, - { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, new Dictionary { {TestVariations.SqlBulkCopySqlDataReader_Type, TimeSpan.Zero}, {TestVariations.SqlBulkCopySqlDataReader_Variant, TimeSpan.Zero}, @@ -1208,7 +1209,7 @@ public static IEnumerable GetParameterCombinations() }, new Dictionary()}; yield return new object[] { DateTime.MaxValue, "time", - new Dictionary { + new Dictionary { { TestVariations.SqlBulkCopyDataTable_Type, CannotConvertMaxDateTimeToTime }, { TestVariations.SqlBulkCopyDataRow_Type, CannotConvertMaxDateTimeToTime }, { TestVariations.TestSimpleParameter_Type, InvalidCastDateTimeToTimeSpan }, @@ -1219,7 +1220,7 @@ public static IEnumerable GetParameterCombinations() { TestVariations.TestSimpleDataReader_Type, InvalidCastNotValid }, { TestVariations.TestSimpleDataReader_Variant, InvalidCastNotValid }, { TestVariations.SqlBulkCopySqlDataReader_Type, InvalidCastNotValid }, - { TestVariations.SqlBulkCopySqlDataReader_Variant, InvalidCastNotValid }}, + { TestVariations.SqlBulkCopySqlDataReader_Variant, InvalidCastNotValid }}, new Dictionary { {TestVariations.TestSqlDataReader_TVP_Variant, TimeSpan.Parse("23:59:59.9999999")}, {TestVariations.SqlBulkCopySqlDataReader_Type, TimeSpan.Parse("23:59:59.9999999")}, diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs index 537313792e..8b0322b559 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// /// Tests for output parameters. /// + [Trait("Set", "3")] public class OutputParameterTests { /// 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 782ef68ca8..95d3736d9a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -23,6 +23,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class ParametersTest { private static readonly string s_connString = DataTestUtility.TCPConnectionString; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs index aa59bc319c..67a9ddb92e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlAdapterUpdateBatch.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class SqlAdapterUpdateBatch { // TODO Synapse: Incorrect syntax near 'IF'. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index 5f47576baa..0f81496d65 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -17,6 +17,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// Tests for SQL Variant parameters. /// Tests all SqlTypes inside sql_variant to server using sql_variant parameter, SqlBulkCopy, and TVP parameter. /// + [Trait("Set", "3")] public sealed class SqlVariantParameterTests : IDisposable { private readonly string _connStr; @@ -31,7 +32,7 @@ public SqlVariantParameterTests() // which uses CultureInfo.CurrentCulture.LCID. On Linux, this LCID is 127 // (InvariantCulture), which is not a valid SQL Server collation and causes // "invalid TDS collation" errors in the TVP code path. - // SqlClient doesn't support invariant mode: + // SqlClient doesn't support invariant mode: // https://github.com/dotnet/SqlClient/issues/3742 _previousCulture = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/StreamInputParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/StreamInputParameterTests.cs index e6d9284dff..5e8bfa5841 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/StreamInputParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/StreamInputParameterTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -15,6 +15,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// Tests for stream input parameters (binary streams, text readers, XML). /// These tests run independently with their own baseline comparison. /// + [Trait("Set", "3")] [Collection("ParameterBaselineTests")] public class StreamInputParameterTests { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpColumnBoundariesTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpColumnBoundariesTests.cs index e8396e2cf6..81851902a5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpColumnBoundariesTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpColumnBoundariesTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -15,6 +15,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// Tests for TVP column boundaries with various data types. /// These tests run independently with their own baseline comparison. /// + [Trait("Set", "3")] [Collection("ParameterBaselineTests")] public class TvpColumnBoundariesTests { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpQueryHintsTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpQueryHintsTests.cs index 62df3f2c27..545a19a165 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpQueryHintsTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpQueryHintsTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// /// Tests for TVP query hints (sort order, uniqueness, default columns). /// + [Trait("Set", "3")] public sealed class TvpQueryHintsTests : IDisposable { private readonly SqlConnection _conn; 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 5c0459b687..a8baee786c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -21,6 +21,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class TvpTest { private const string TvpName = "@tvp"; @@ -863,7 +864,7 @@ private void CreateServerObjects(StePermutation tvpPerm) SqlCommand cmd = new(tsql.ToString(), conn); cmd.ExecuteNonQuery(); - // and create the proc that uses the type + // and create the proc that uses the type cmd.CommandText = string.Format("CREATE PROC {0}(@tvp {1} READONLY) AS SELECT * FROM @tvp order by {2}", GetProcName(tvpPerm), GetTypeName(tvpPerm), colOrdinal - 1); cmd.ExecuteNonQuery(); 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 99d606322c..d04753ec1b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class RandomStressTest : IDisposable { private static readonly TimeSpan TimeLimitDefault = new TimeSpan(0, 0, 10); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicCounterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicCounterTest.cs index 4b1b7add4d..9d90459696 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicCounterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicCounterTest.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class RetryLogicCounterTest { [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicTestHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicTestHelper.cs index 5c2aef92eb..225b1430b6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicTestHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/RetryLogicTestHelper.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class RetryLogicTestHelper { private static readonly HashSet s_defaultTransientErrors = diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs index a0b00a9bee..911b90ec5b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class SqlCommandReliabilityTest : IDisposable { private const int ConcurrentParallelExecutions = 3; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs index bcb221ff30..8ffbbdb5bf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class SqlConfigurationManagerReliabilityTest { private static readonly SqlConnectionReliabilityTest s_connectionCRLTest = new SqlConnectionReliabilityTest(); 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 d133d25e03..83feb0571a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] public class SqlConnectionReliabilityTest { internal const string InvalidInitialCatalog = "InvalidInitialCatalog_for_retry"; 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 beda53991a..433676875b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class SplitPacketTest : IDisposable { private int _port = -1; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs index 04b0de8072..dc97d98052 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class SqlCommandCancelTest { // Shrink the packet size - this should make timeouts more likely diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs index 1176447204..9969ef17a8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCompletedTest.cs @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class SqlCommandCompletedTest { private static readonly string s_connStr = (new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { PacketSize = 512 }).ConnectionString; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandExecuteScalarTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandExecuteScalarTest.cs index 590657e5db..b618872772 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandExecuteScalarTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandExecuteScalarTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests /// Tests for ExecuteScalar to verify proper exception handling. /// Regression tests for GitHub issue #3736: https://github.com/dotnet/SqlClient/issues/3736 /// + [Trait("Set", "3")] public static class SqlCommandExecuteScalarTest { /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs index b694bc6aee..4eb7e6779e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandSetTest.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class SqlCommandSetTest { private static Assembly mds = Assembly.GetAssembly(typeof(SqlConnection)); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs index 1c26151d94..0767c26894 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandStoredProcTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "1")] public class SqlCommandStoredProcTest { private static readonly string s_tcp_connStr = DataTestUtility.TCPConnectionString; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs index dd3132a41c..38295bd953 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class SqlCredentialTest { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] 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 62127bc9a8..4369d75ab0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "2")] [PlatformSpecific(TestPlatforms.Windows)] public class SqlDataSourceEnumeratorTest { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDependencyTest/SqlDependencyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDependencyTest/SqlDependencyTest.cs index c2a4ab79e8..d9f3988e02 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDependencyTest/SqlDependencyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDependencyTest/SqlDependencyTest.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class SqlDependencyTest { [Fact] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs index ccf0a3a90c..bd36cc37a2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class SqlFileStreamTest { private static bool IsFileStreamEnvironmentSet() => DataTestUtility.IsFileStreamSetup(); @@ -50,7 +51,7 @@ public static void ReadFilestream() string path = reader.GetString(0); byte[] transactionContext = reader.GetSqlBytes(1).Buffer; - // Create the SqlFileStream + // Create the SqlFileStream using (Stream fileStream = new SqlFileStream(path, transactionContext, FileAccess.Read, FileOptions.SequentialScan, allocationSize: 0)) { // Read the contents as bytes. @@ -118,11 +119,11 @@ public static void OverwriteFilestream() { while (reader.Read()) { - // Get the pointer for file + // Get the pointer for file string path = reader.GetString(0); byte[] transactionContext = reader.GetSqlBytes(1).Buffer; - // Create the SqlFileStream + // Create the SqlFileStream using Stream fileStream = new SqlFileStream(path, transactionContext, FileAccess.Write, FileOptions.SequentialScan, allocationSize: 0); // Overwrite the first row in the table fileStream.Write((insertedValue), 0, 4); @@ -183,15 +184,15 @@ public static void AppendFilestream() { while (reader.Read()) { - // Get the pointer for file + // Get the pointer for file string path = reader.GetString(0); byte[] transactionContext = reader.GetSqlBytes(1).Buffer; using Stream fileStream = new SqlFileStream(path, transactionContext, FileAccess.ReadWrite, FileOptions.SequentialScan, allocationSize: 0); - // Seek to the end of the file + // Seek to the end of the file fileStream.Seek(0, SeekOrigin.End); - // Append a single byte + // Append a single byte fileStream.WriteByte(appendedByte); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNamedPipesTest/SqlNamedPipesTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNamedPipesTest/SqlNamedPipesTest.cs index 497450f74e..9386b181ca 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNamedPipesTest/SqlNamedPipesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNamedPipesTest/SqlNamedPipesTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class SqlNamedPipesTest { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs index 24f34bbf12..3701b503a3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public sealed class SqlNotificationTest : IDisposable { // Misc constants 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 6f4c602c27..a7ef4993bb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class SqlSchemaInfoTest { #region TestMethods diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlStatisticsTest/SqlStatisticsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlStatisticsTest/SqlStatisticsTest.cs index f04bc38a4d..32bb8ff919 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlStatisticsTest/SqlStatisticsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlStatisticsTest/SqlStatisticsTest.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class SqlStatisticsTest { private static DateTime startTime = new DateTime(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs index 9ed25a6e74..7d1098ccad 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] [PlatformSpecific(TestPlatforms.Windows)] public class DistributedTransactionTestWindows { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs index b6b718e4dd..527665d2b1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class TransactionEnlistmentTest { // TODO Synapse: Cannot find data type 'text'. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs index 0adff2c8ae..9aea760e56 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class TransactionTest { public static TheoryData PoolEnabledConnectionStrings => 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 b590a7c451..712d1ec2c6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -16,6 +16,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class SqlServerTypesTest { private const string BuiltInUdtSelectQuery = diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs index 469c895a61..ed5dd7056b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtBulkCopyTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class UdtBulkCopyTest { private string _connStr; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs index 490bfe74b4..f5c2df4c09 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtDateTimeOffsetTest.cs @@ -12,6 +12,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class DateTimeOffsetList : SqlDataRecord { public DateTimeOffsetList(DateTimeOffset dateTimeOffset) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest.cs index 666b7bd258..12b92fa76d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class UdtTest { private readonly string _connStr; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs index 2159589f9c..c5c4955fee 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UdtTest2.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -13,6 +13,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { [ActiveIssue("5536")] // TODO Synapse: Remove dependency from UDTTest Database + [Trait("Set", "3")] public class UdtTest2 { private string _connStr = null; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs index 41f81b12e3..b20251ea69 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Utf8SupportTest/Utf8SupportTest.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class Utf8SupportTest { // Synapse: 'CONNECTIONPROPERTY' is not a recognized built-in function name. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs index 0a86534112..da55ceb1b3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -53,6 +53,7 @@ public static IEnumerable GetVectorFloat32TestData() } } + [Trait("Set", "3")] public sealed class NativeVectorFloat32Tests : IDisposable { private readonly ITestOutputHelper _output; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs index a805cb4dc9..607f4ce01b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest { + [Trait("Set", "3")] public sealed class VectorAPIValidationTest { // We need these testcases to validate ref assembly for vector APIs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs index 9db91d767f..d50c0e00d6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -41,6 +41,7 @@ public static IEnumerable GetVarcharVectorInsertTestData() } } + [Trait("Set", "3")] public sealed class VectorTypeBackwardCompatibilityTests : VectorBackwardCompatTestBase { public VectorTypeBackwardCompatibilityTests(ITestOutputHelper output) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/WeakRefTest/WeakRefTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/WeakRefTest/WeakRefTest.cs index 8062df455a..7d270935d1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/WeakRefTest/WeakRefTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/WeakRefTest/WeakRefTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class WeakRefTest { private const string COMMAND_TEXT_1 = "SELECT EmployeeID, LastName, FirstName, Title, Address, City, Region, PostalCode, Country from Employees"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/WeakRefTestYukonSpecific/WeakRefTestYukonSpecific.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/WeakRefTestYukonSpecific/WeakRefTestYukonSpecific.cs index 472392d47d..864d1a5447 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/WeakRefTestYukonSpecific/WeakRefTestYukonSpecific.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/WeakRefTestYukonSpecific/WeakRefTestYukonSpecific.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -8,6 +8,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public static class WeakRefTestYukonSpecific { private const string COMMAND_TEXT_1 = "SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax from Customers"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs index db49fc7c32..eb64eecce8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs @@ -21,6 +21,9 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + // TODO(ADO-39873): Re-enable these tests after addressing their flakiness. + [Trait("Set", "3")] + [Trait("Category", "flaky")] // Serialized execution: DiagnosticListener is global state, so these tests // must not run in parallel with each other. [Collection("DiagnosticTests")] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventSourceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventSourceTest.cs index 4992c55974..df8229025b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventSourceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventSourceTest.cs @@ -7,6 +7,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class EventSourceTest { [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] @@ -24,7 +25,7 @@ public void EventSourceTestAll() } } - // Need to investigate better way of validating traces in sequential runs, + // Need to investigate better way of validating traces in sequential runs, // For now we're collecting all traces to improve code coverage. Assert.All(TraceListener.IDs, item => { Assert.Contains(item, Enumerable.Range(1, 21)); }); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs index 6f9abd12cb..6d37051821 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs @@ -9,6 +9,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + [Trait("Set", "3")] public class MetricsTest { #if NETFRAMEWORK diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs index b03fc8b224..c1e4bad178 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs @@ -10,7 +10,6 @@ using Microsoft.Data.SqlClient.Tests.Common; using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; -using Xunit.Abstractions; #nullable enable @@ -97,6 +96,7 @@ public class XEventCleanerCollection : ICollectionFixture /// even in error scenarios. This is important to ensure that customers can rely on activity IDs being present in /// XEvent sessions for troubleshooting and correlation purposes. /// + [Trait("Set", "3")] [Collection("XEventCleaner")] public class XEventsTracingTest { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.cs b/src/Microsoft.Data.SqlClient/tests/TestUdts/Address/Address.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.cs rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Address/Address.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj b/src/Microsoft.Data.SqlClient/tests/TestUdts/Address/Address.csproj similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Address/Address.csproj diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.cs b/src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.cs rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj b/src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.csproj similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.csproj diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Point1.cs b/src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Point1.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Point1.cs rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Point1.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Line.cs b/src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Line.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Line.cs rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Line.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Point.cs b/src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Point.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Point.cs rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Point.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj b/src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Shapes.csproj similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Shapes.csproj diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.cs b/src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.cs rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj b/src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.csproj similarity index 100% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj rename to src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.csproj From 2d8d7866fcdfd98dd559f41f43073da8d99586a5 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:10:06 -0300 Subject: [PATCH 136/298] Add CI package pipeline for building and publishing NuGet packages (#4292) --- .../ci/package/sqlclient-package.yml | 172 ++++++++++++++++++ .../sqlclient-pr-package-ref-pipeline.yml | 1 + .../sqlclient-pr-project-ref-pipeline.yml | 1 + 3 files changed, 174 insertions(+) create mode 100644 eng/pipelines/ci/package/sqlclient-package.yml diff --git a/eng/pipelines/ci/package/sqlclient-package.yml b/eng/pipelines/ci/package/sqlclient-package.yml new file mode 100644 index 0000000000..e194e2abef --- /dev/null +++ b/eng/pipelines/ci/package/sqlclient-package.yml @@ -0,0 +1,172 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# This pipeline builds all packages using the build.proj Pack target with ReferenceType=Package, +# then publishes the resulting .nupkg and .snupkg files as a single pipeline artifact. +# +# It runs: +# - On pushes to GitHub main and ADO internal/main (batched) +# - Nightly at 00:00 UTC on both branches +# +# On internal/main the strong-name signing key is downloaded and used to sign assemblies during the +# build. +# +# GOTCHA: This pipeline definition is triggered by GitHub _and_ ADO CI. We distinguish the two via +# branch filters: +# +# - Only the GitHub repo has a 'main' branch. +# - Only the ADO repo has an 'internal/main' branch. + +name: $(DayOfYear)$(Rev:rr) + +# Do not trigger this pipeline for PRs. +pr: none + +# Trigger on pushes to main and internal/main, batching concurrent commits. +trigger: + batch: true + branches: + include: + - main + - internal/main + +# Nightly schedule at 00:00 UTC. +schedules: + - cron: '0 0 * * *' + displayName: Nightly Build + branches: + include: + - main + - internal/main + always: true + +# Pipeline parameters visible in the Azure DevOps UI. +parameters: + + # The agent image to use for the build. This must exist in both the ADO-1ES-Pool and + # ADO-CI-1ES-Pool agent pools. + - name: agentImage + displayName: Agent Image + type: string + default: ADO-UB24 + values: + - ADO-UB24 + - ADO-Win25 + + # The build configuration to use, either Debug or Release. + - name: buildConfiguration + displayName: Build Configuration + type: string + default: Release + values: + - Debug + - Release + + # True to enable debug steps and output. + - name: debug + displayName: Enable debug output + type: boolean + default: false + + # The verbosity level of dotnet CLI commands. + - name: dotnetVerbosity + displayName: dotnet CLI Verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +variables: + # Whether this is an internal (ADO.Net project) or public (Public project) build. + - name: isInternalBuild + value: ${{ eq(variables['System.TeamProject'], 'ADO.Net') }} + + # Signing key argument passed to build.proj. On internal builds this references the secure file + # downloaded by DownloadSecureFile@1; on public builds it expands to empty. + - name: signingKeyArg + ${{ if eq(variables.isInternalBuild, true) }}: + value: '-p:SigningKeyPath="$(keyFile.secureFilePath)"' + ${{ else }}: + value: '' + +jobs: + - job: pack_all_packages + displayName: Pack All Packages + + pool: + ${{ if eq(variables.isInternalBuild, true) }}: + name: ADO-1ES-Pool + ${{ else }}: + name: ADO-CI-1ES-Pool + demands: + - ImageOverride -equals ${{ parameters.agentImage }} + + steps: + + # Emit environment variables if debug is enabled. + - ${{ if eq(parameters.debug, true) }}: + - pwsh: | + Get-ChildItem Env: | Sort-Object Name | Format-Table -AutoSize -Wrap + displayName: '[Debug] Print Environment Variables' + + # Install the .NET SDK from global.json. + - template: /eng/pipelines/steps/install-dotnet.yml@self + parameters: + debug: ${{ parameters.debug }} + + # Restore dotnet local tools (pwsh, apicompat, etc.). + - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + + # Clean any pre-existing .nupkg / .snupkg files from the packages/ directory + # to ensure we only publish packages produced by this run. + - pwsh: | + Write-Host 'Cleaning packages/ directory...' + Remove-Item -Force "$(Build.SourcesDirectory)/packages/*.nupkg" -ErrorAction SilentlyContinue + Remove-Item -Force "$(Build.SourcesDirectory)/packages/*.snupkg" -ErrorAction SilentlyContinue + Write-Host 'Done.' + displayName: Clean packages/ directory + + # On internal builds, download the strong-name signing key. + - ${{ if eq(variables.isInternalBuild, true) }}: + - task: DownloadSecureFile@1 + displayName: Download Signing Key + inputs: + secureFile: netfxKeypair.snk + name: keyFile + + # Run the Pack target via build.proj. + - task: DotNetCoreCLI@2 + displayName: build.proj - Pack (ReferenceType=Package) + inputs: + command: build + projects: '$(Build.SourcesDirectory)/build.proj' + arguments: >- + -t:Pack + -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:BuildNumber="$(Build.BuildNumber)" + -p:BuildSuffix=ci + $(signingKeyArg) + --verbosity ${{ parameters.dotnetVerbosity }} + + # List produced packages for diagnostics. + - pwsh: | + Write-Host 'Packages produced:' + Get-ChildItem "$(Build.SourcesDirectory)/packages/*.nupkg" -ErrorAction SilentlyContinue | Format-Table Name, Length -AutoSize -Wrap + Get-ChildItem "$(Build.SourcesDirectory)/packages/*.snupkg" -ErrorAction SilentlyContinue | Format-Table Name, Length -AutoSize -Wrap + displayName: List produced packages + + # Publish all .nupkg and .snupkg files from packages/ as a pipeline artifact. + - task: PublishPipelineArtifact@1 + displayName: Publish NuGet Packages + inputs: + targetPath: '$(Build.SourcesDirectory)/packages' + artifactName: Packages + publishLocation: pipeline diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index be7e2e9e3b..70a37e0fa7 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -51,6 +51,7 @@ pr: - global.json - NuGet.config exclude: + - eng/pipelines/ci/* - eng/pipelines/kerberos/* - eng/pipelines/onebranch/* - eng/pipelines/stress/* diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index 30950ec21c..f777626c90 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -51,6 +51,7 @@ pr: - global.json - NuGet.config exclude: + - eng/pipelines/ci/* - eng/pipelines/kerberos/* - eng/pipelines/onebranch/* - eng/pipelines/stress/* From b5575bceeb5cda4e6c60c98b81a03a213564d70b Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:35:55 -0700 Subject: [PATCH 137/298] Update tools to latest versions (#4345) --- dotnet-tools.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet-tools.json b/dotnet-tools.json index 11e3494512..5c185c01e9 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -3,21 +3,21 @@ "isRoot": true, "tools": { "dotnet-coverage": { - "version": "18.3.2", + "version": "18.7.0", "commands": [ "dotnet-coverage" ], "rollForward": false }, "microsoft.dotnet.apicompat.tool": { - "version": "10.0.103", + "version": "10.0.300", "commands": [ "apicompat" ], "rollForward": false }, "powershell": { - "version": "7.6.0", + "version": "7.6.2", "commands": [ "pwsh" ], From f50d21ce320194ff94c7e396f479482fca65a4cc Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Tue, 9 Jun 2026 14:39:05 -0700 Subject: [PATCH 138/298] Escape prId variable in url. (#4350) --- eng/pipelines/scripts/Sync-GitHubToAdo.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/pipelines/scripts/Sync-GitHubToAdo.ps1 b/eng/pipelines/scripts/Sync-GitHubToAdo.ps1 index e8b7738021..642ee8b125 100644 --- a/eng/pipelines/scripts/Sync-GitHubToAdo.ps1 +++ b/eng/pipelines/scripts/Sync-GitHubToAdo.ps1 @@ -250,7 +250,7 @@ if ($activePr) { Write-Host "Active PR #$prId found. Updating description and posting comment..." # 1. PATCH the PR description with the refreshed commit summary. - $patchUri = "$apiBase/pullrequests/$prId?api-version=7.1" + $patchUri = "$apiBase/pullrequests/${prId}?api-version=7.1" $patchBody = @{ description = "## Automated GitHub Sync`n`nThis PR was updated automatically by the GitHub sync pipeline.`n`nSource: [dotnet/SqlClient@$GitHubBranch](https://github.com/dotnet/SqlClient/tree/$GitHubBranch)`nSync branch: ``$SyncBranchName```n`n### Latest Commits`n`n$commitSummary`n`n---`n_This PR requires manual review and merge. Auto-complete is not enabled._" } From 6494816643c9e8c78197fe42b60c7ba4f89726f7 Mon Sep 17 00:00:00 2001 From: Jonas Nyrup Date: Wed, 10 Jun 2026 20:23:19 +0200 Subject: [PATCH 139/298] Improve `EnclaveDiffieHellmanInfo.Size` (#4346) --- .../Data/SqlClient/AlwaysEncryptedEnclaveProviderUtils.cs | 2 +- .../Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs | 3 +-- .../Data/SqlClient/VirtualSecureModeEnclaveProvider.cs | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedEnclaveProviderUtils.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedEnclaveProviderUtils.cs index ff84ab3310..391dcdfd35 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedEnclaveProviderUtils.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedEnclaveProviderUtils.cs @@ -18,7 +18,7 @@ public EnclavePublicKey(byte[] payload) internal class EnclaveDiffieHellmanInfo { - public int Size => sizeof(int) + sizeof(int) + PublicKey?.Length ?? 0 + PublicKeySignature?.Length ?? 0; + public int Size => sizeof(int) + sizeof(int) + (PublicKey?.Length ?? 0) + (PublicKeySignature?.Length ?? 0); public byte[] PublicKey { get; private set; } 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 880ab081f0..6d26429122 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -208,8 +208,7 @@ public AzureAttestationInfo(byte[] attestationInfo) EnclaveDHInfo = new EnclaveDiffieHellmanInfo(attestationInfo, offset); offset += EnclaveDHInfo.Size; - // TODO(GH-3604): Fix this failing assertion. - // Debug.Assert(offset == attestationInfo.Length); + Debug.Assert(offset == attestationInfo.Length); } catch (Exception exception) { 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 dfd5fc65a4..aa7e17be03 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProvider.cs @@ -153,8 +153,7 @@ public AttestationInfo(byte[] attestationInfo) EnclaveDHInfo = new EnclaveDiffieHellmanInfo(attestationInfo, offset); offset += Convert.ToInt32(EnclaveDHInfo.Size); - // TODO(GH-3604): Fix this failing assertion. - // Debug.Assert(offset == attestationInfo.Length, $"{offset} == {attestationInfo.Length}"); + Debug.Assert(offset == attestationInfo.Length, $"{offset} == {attestationInfo.Length}"); } } From e46104d0e2053bb2eda778c3213761242e9b3d4f Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:57:29 -0700 Subject: [PATCH 140/298] Tests | Add Flaky test details to ConnectionFailoverTests (#4341) * Add Flaky test details to ConnectionFailoverTests * comments --- .../ConnectionFailoverTests.cs | 112 ++++++++++++++++-- 1 file changed, 101 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index ea3e2d3da7..29bb6cb6e5 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -19,8 +19,8 @@ public class ConnectionFailoverTests [Trait("Category", "flaky")] [Theory] [InlineData(40613)] - [InlineData(42108)] - [InlineData(42109)] + [InlineData(42108)] // Flaky: [errorcode: 42108] Assert.Equal() Failure: Strings differ | Assert.Equal() Failure: Values differ + [InlineData(42109)] // Flaky: [errorcode: 42109] Assert.Equal() Failure: Strings differ public void TransientFault_NoFailover_DoesNotClearPool(uint errorCode) { // When connecting to a server with a configured failover partner, @@ -67,16 +67,29 @@ public void TransientFault_NoFailover_DoesNotClearPool(uint errorCode) // Assert Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal(ConnectionState.Open, secondConnection.State); + + /* TODO: Fix flaky Assert.Equal() in this test method; + Assert.Equal() Failure: Strings differ + ↓ (pos 14) + Expected: "localhost,51966" + Actual: "localhost,51965" + ↑ (pos 14) + */ Assert.Equal($"localhost,{initialServer.EndPoint.Port}", connection.DataSource); Assert.Equal($"localhost,{initialServer.EndPoint.Port}", secondConnection.DataSource); // 1 for the initial connection, 2 for the second connection + /* TODO: Fix flaky test failure for errorcode 42108: + Assert.Equal() Failure: Values differ + Expected: 3 + Actual: 4 + */ Assert.Equal(3, initialServer.PreLoginCount - initialServer.AbandonedPreLoginCount); // A failover should not be triggered, so prelogin count to the failover server should be 0 Assert.Equal(0, failoverServer.PreLoginCount); } - [Trait("Category", "flaky")] + [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ | System.ComponentModel.Win32Exception : The wait operation timed out. [Fact] public void NetworkError_TriggersFailover_ClearsPool() { @@ -115,6 +128,13 @@ public void NetworkError_TriggersFailover_ClearsPool() // Open the initial connection to warm up the pool and populate failover partner information // for the pool group. using SqlConnection connection = new(builder.ConnectionString); + + /* TODO: Fix flaky test failure: + Microsoft.Data.SqlClient.SqlException : 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: 0 - The wait operation timed out.) + ---- System.ComponentModel.Win32Exception : The wait operation timed out. + */ connection.Open(); Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{initialServer.EndPoint.Port}", connection.DataSource); @@ -131,8 +151,13 @@ public void NetworkError_TriggersFailover_ClearsPool() Assert.Equal(ConnectionState.Open, secondConnection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", secondConnection.DataSource); Assert.Equal(1, initialServer.PreLoginCount); - Assert.Equal(1, failoverServer.PreLoginCount); + /* TODO: Fix flaky Assert.Equal() in this test method; + Assert.Equal() Failure: Values differ + Expected: 1 + Actual: 2 + */ + Assert.Equal(1, failoverServer.PreLoginCount); // Act // Request a new connection, should initiate a fresh connection attempt if the pool was cleared. @@ -242,7 +267,7 @@ public void NetworkDelay_ShouldConnectToPrimary() } [Fact] - [Trait("Category", "flaky")] + [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ | System.ComponentModel.Win32Exception : The wait operation timed out public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFailoverPartner() { using TdsServer failoverServer = new( @@ -277,6 +302,15 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa using SqlConnection connection = new(builder.ConnectionString); // Act + /* TODO: Fix flaky test failure: + Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. + The connection could have timed out while waiting for server to complete the login process and respond; + Or it could have timed out while attempting to create multiple active connections. + This failure occurred while attempting to connect to the Principle server. + The duration spent while attempting to connect to this server was - [Pre-Login] initialization=6; handshake=80; + [Login] initialization=0; authentication=0; [Post-Login] complete=5443; + ---- System.ComponentModel.Win32Exception : The wait operation timed out + */ connection.Open(); @@ -285,12 +319,18 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa // so the connection will retry on the failover server. Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", connection.DataSource); + + /* TODO: Fix flaky Assert.Equal() in this test method; + Assert.Equal() Failure: Values differ + Expected: 1 + Actual: 2 + */ Assert.Equal(1, failoverServer.PreLoginCount); Assert.Equal(1, server.PreLoginCount); } [Fact] - [Trait("Category", "flaky")] + [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ | System.ComponentModel.Win32Exception : The wait operation timed out public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFailoverPartner() { using TdsServer failoverServer = new( @@ -325,6 +365,24 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai }; using SqlConnection connection = new(builder.ConnectionString); // Act + /* TODO: Fix flaky test failure: + Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. + The timeout period elapsed during the post-login phase. + The connection could have timed out while waiting for server to complete the login process and respond; + Or it could have timed out while attempting to create multiple active connections. + This failure occurred while attempting to connect to the Principle server. + The duration spent while attempting to connect to this server was - [Pre-Login] initialization=4; handshake=68; + [Login] initialization=0; authentication=0; [Post-Login] complete=5443; + ---- System.ComponentModel.Win32Exception : The wait operation timed out + */ + /* TODO: Fix flaky test failure: + Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. + The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement. + This could be because the pre-login handshake failed or the server was unable to respond back in time. + This failure occurred while attempting to connect to the Principle server. + The duration spent while attempting to connect to this server was - [Pre-Login] initialization=0; handshake=5285; + ---- System.ComponentModel.Win32Exception : The wait operation timed out. + */ connection.Open(); // Assert @@ -332,6 +390,11 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai // so the connection will retry on the failover server. Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", connection.DataSource); + /* TODO: Fix flaky Assert.Equal() in this test method; + Assert.Equal() Failure: Values differ + Expected: 1 + Actual: 2 + */ Assert.Equal(1, server.PreLoginCount); Assert.Equal(0, server.Login7Count); Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); @@ -448,7 +511,7 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) [InlineData(40613)] [InlineData(42108)] [InlineData(42109)] - [Trait("Category", "flaky")] + [Trait("Category", "flaky")] // [errorcode: 40613] Assert.Equal() Failure: Values differ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint errorCode) { // Arrange @@ -490,17 +553,21 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 + /* TODO: Fix flaky Assert.Equal() in this test method; + Assert.Equal() Failure: Values differ + Expected: 2 + Actual: 3 + */ Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); // Login-phase errors must NOT trigger failover alternation Assert.Equal(0, failoverServer.PreLoginCount); } - [Trait("Category", "flaky")] + [Trait("Category", "flaky")] // [errorcode: 42109] Assert.Fail() Failure [Theory] [InlineData(40613)] [InlineData(42108)] [InlineData(42109)] - [Trait("Category", "flaky")] public void TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail(uint errorCode) { // Arrange @@ -542,12 +609,13 @@ public void TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail(uint Assert.Equal((int)errorCode, e.Number); return; } - + // TODO: Fix flaky Assert.Fail() in this test method; + // [errorcode: 42109] Assert.Fail() Failure Assert.Fail(); } [Fact] - [Trait("Category", "flaky")] + [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ | System.ComponentModel.Win32Exception : The wait operation timed out public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() { // Arrange @@ -585,6 +653,12 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs SqlConnection connection = new(builder.ConnectionString); // Connect once to the primary to trigger it to send the failover partner + /* TODO: Fix flaky test failure: + Microsoft.Data.SqlClient.SqlException : 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: 0 - The wait operation timed out.) + ---- System.ComponentModel.Win32Exception : The wait operation timed out. + */ connection.Open(); Assert.Equal("invalidhost", (connection.InnerConnection as SqlConnectionInternal)!.ServerProvidedFailoverPartner); @@ -606,8 +680,21 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // Assert Assert.Equal(ConnectionState.Open, failoverConnection.State); + + /* TODO: Fix flaky Assert.Equal() in this test method; + Assert.Equal() Failure: Strings differ + ↓ (pos 13) + Expected: "localhost,51479" + Actual: "localhost,51480" + ↑ (pos 13) + */ Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", failoverConnection.DataSource); // 1 for the initial connection + /* TODO: Fix flaky Assert.Equal() in this test method; + Assert.Equal() Failure: Values differ + Expected: 1 + Actual: 2 + */ Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); // 1 for the failover connection Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); @@ -789,6 +876,7 @@ public void TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_No /// and never attempts failover alternation. /// [Theory] + [Trait("Category", "flaky")] // Assert.Throws() Failure: No exception was thrown [InlineData(40613)] [InlineData(42108)] [InlineData(42109)] @@ -827,6 +915,8 @@ public void TransientFault_RetryDisabled_WithUserProvidedPartner_ShouldFail_NotF using SqlConnection connection = new(builder.ConnectionString); // No outer connect retry is allowed, so the first transient error should surface. + // TODO: Fix flakiness of Assert.Throws() in this test method; currently, if the exception is not thrown as expected, + // Assert.Throws will fail the test with an assertion failure ("No exception was thrown"). SqlException ex = Assert.Throws(() => connection.Open()); Assert.Equal((int)errorCode, ex.Number); From ff57e19f78b4c67b678b3e3c1c0eb6194e25047f Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:00:26 -0300 Subject: [PATCH 141/298] Fix stress pipeline: rename variables to avoid env var injection, simplify runner (#4329) --- .../ado-pipelines.instructions.md | 14 + eng/pipelines/stress/stress-tests-job.yml | 68 ++-- .../stress/stress-tests-pipeline.yml | 12 +- eng/pipelines/stress/stress-tests-stage.yml | 36 +- .../StressTests/Directory.Packages.props | 4 + .../IMonitorLoader/IMonitorLoader.cs | 2 +- .../IMonitorLoader/MonitorMetrics.cs | 2 +- .../GlobalExceptionHandlerAttribute.cs | 2 +- .../Attributes/GlobalTestCleanupAttribute.cs | 2 +- .../Attributes/GlobalTestSetupAttribute.cs | 2 +- .../Attributes/TestAttribute.cs | 2 +- .../Attributes/TestCleanupAttribute.cs | 2 +- .../Attributes/TestSetupAttribute.cs | 2 +- .../Attributes/TestVariationAttribute.cs | 2 +- .../DeadlockDetection.cs | 2 +- .../DeadlockDetectionTaskScheduler.cs | 2 +- .../SqlClient.Stress.Common/TestMetrics.cs | 2 +- .../SqlClient.Stress.Common/VersionUtil.cs | 2 +- .../SqlClient.Stress.Framework/AsyncUtils.cs | 3 +- .../SqlClient.Stress.Framework/DataSource.cs | 2 +- .../DataStressConnection.cs | 2 +- .../DataStressErrors.cs | 2 +- .../DataStressFactory.cs | 2 +- .../DataStressReader.cs | 2 +- .../DataStressSettings.cs | 2 +- .../DataTestGroup.cs | 3 +- .../SqlClient.Stress.Framework/Extensions.cs | 2 +- .../StressConfigReader.cs | 4 +- .../TrackedRandom.cs | 2 +- .../SqlClient.Stress.Runner/Constants.cs | 2 +- .../Ex API/MemApi.Windows.cs | 2 +- .../ITestAttributeFilter.cs | 2 +- .../SqlClient.Stress.Runner/LogManager.cs | 2 +- .../Monitor/FakeConsole.cs | 2 +- .../SqlClient.Stress.Runner/Monitor/Logger.cs | 2 +- .../Monitor/MonitorLoadUtils.cs | 7 +- .../Monitor/RecordedExceptions.cs | 2 +- .../SqlClient.Stress.Runner/PerfCounters.cs | 2 +- .../SqlClient.Stress.Runner/Program.cs | 337 ++++++++---------- .../SqlClient.Stress.Runner.csproj | 4 + .../SqlClient.Stress.Runner/StressEngine.cs | 3 +- .../SqlClient.Stress.Runner/TestFinder.cs | 2 +- .../Tests/MultithreadedTest.cs | 2 +- .../Tests/StressTest.cs | 2 +- .../SqlClient.Stress.Runner/Tests/Test.cs | 2 +- .../SqlClient.Stress.Runner/Tests/TestBase.cs | 2 +- .../Tests/ThreadPoolTest.cs | 2 +- .../FilteredDefaultTraceListener.cs | 2 +- .../HostsFileManager.cs | 2 +- .../MultiSubnetFailoverSetup.cs | 3 +- .../SqlClient.Stress.Tests/NetUtils.cs | 2 +- .../SqlClientStressFactory.cs | 3 +- .../SqlClientTestGroup.cs | 3 +- 53 files changed, 295 insertions(+), 285 deletions(-) diff --git a/.github/instructions/ado-pipelines.instructions.md b/.github/instructions/ado-pipelines.instructions.md index 9260a78b3d..7e0d02e4e2 100644 --- a/.github/instructions/ado-pipelines.instructions.md +++ b/.github/instructions/ado-pipelines.instructions.md @@ -110,6 +110,20 @@ Test timeout — `--blame-hang-timeout 10m` (configured in `build.proj` and thre - `localFeedPath` = `$(Build.SourcesDirectory)/packages` — local NuGet feed for inter-package deps - `packagePath` = `$(Build.SourcesDirectory)/output` — NuGet pack output +## Variable Naming — Avoid `{COMMAND}ARGUMENTS` Names + +The dotnet CLI (via System.CommandLine) reads environment variables named `{COMMAND}ARGUMENTS` and silently injects their content into the parsed arguments for that subcommand. Because Azure DevOps automatically exposes all pipeline variables as uppercased environment variables, a pipeline variable named `runArguments` becomes `RUNARGUMENTS`, which `dotnet run` reads and injects into the application's `args[]` — bypassing the `--` separator. + +**Forbidden variable names** (any casing): +- `runArguments` — injected into `dotnet run` +- `buildArguments` — injected into `dotnet build` +- `testArguments` — injected into `dotnet test` +- Any name matching `{dotnet-subcommand}Arguments` + +**Use instead**: `dotnetBuildOpts`, `dotnetRunOpts`, `stressTestArgs`, or other names that do not match the `{COMMAND}ARGUMENTS` pattern. + +This affects ALL .NET SDK versions (8.0+). The injection is invisible in `[command]` log lines, making it extremely hard to diagnose. The only symptom is the application receiving unexpected arguments. + ## Conventions When Editing Pipelines - Always use templates for reusable logic — do not inline complex steps diff --git a/eng/pipelines/stress/stress-tests-job.yml b/eng/pipelines/stress/stress-tests-job.yml index 2633445214..a02a0a4f4b 100644 --- a/eng/pipelines/stress/stress-tests-job.yml +++ b/eng/pipelines/stress/stress-tests-job.yml @@ -24,7 +24,6 @@ parameters: # True to enable debugging steps. - name: debug type: boolean - default: false # The prefix to prepend to the job's display name: # @@ -36,7 +35,6 @@ parameters: # The verbosity level for the dotnet CLI commands. - name: dotnetVerbosity type: string - default: normal values: - quiet - minimal @@ -48,20 +46,18 @@ parameters: - name: jobNameSuffix type: string - # True to fail the job when stress tests fail. When false, test failures produce warnings - # (SucceededWithIssues) but do not fail the job. - - name: failOnTestFailure + # When true, test failures produce warnings (SucceededWithIssues) but do not fail the job. + # When false, test failures fail the job. All test steps always run regardless of this setting. + - name: warnOnTestFailure type: boolean # The list of .NET Framework runtimes to test against. - name: netFrameworkTestRuntimes type: object - default: [] # The list of .NET runtimes to test against. - name: netTestRuntimes type: object - default: [] # The name of the Azure Pipelines pool to use. - name: poolName @@ -105,18 +101,6 @@ jobs: - name: project value: $(Build.SourcesDirectory)/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj - # dotnet CLI arguments for build. - - name: buildArguments - value: >- - --verbosity ${{ parameters.dotnetVerbosity }} - -p:Configuration=${{ parameters.buildConfiguration }} - - # dotnet CLI arguments for run. - - name: runArguments - value: >- - --verbosity ${{ parameters.dotnetVerbosity }} - --configuration ${{ parameters.buildConfiguration }} - # The contents of the config file to use for all tests. We will write this to a JSON file and # then point to it via the STRESS_CONFIG_FILE environment variable. - name: configContent @@ -137,9 +121,29 @@ jobs: } ] - # Stress test command-line arguments. - - name: testArguments - value: -a SqlClient.Stress.Tests -console + # IMPORTANT: Do NOT name pipeline variables "runArguments", "buildArguments", or + # "testArguments". ADO exposes all pipeline variables as environment variables (uppercased), + # and the dotnet CLI's System.CommandLine reads env vars matching {COMMAND}ARGUMENTS (e.g. + # RUNARGUMENTS, BUILDARGUMENTS) and silently injects their content into the parsed arguments — + # bypassing the "--" separator. This causes app arguments to contain SDK options and triggers + # unintended behavior. + + # dotnet CLI options for build. + - name: dotnetBuildOpts + value: >- + --verbosity ${{ parameters.dotnetVerbosity }} + -p:Configuration=${{ parameters.buildConfiguration }} + + # dotnet run options shared by all test steps (framework is appended per-step). + - name: dotnetRunOpts + value: >- + --no-build + --verbosity ${{ parameters.dotnetVerbosity }} + --configuration ${{ parameters.buildConfiguration }} + + # Stress test options passed after the "--" separator. + - name: stressTestOpts + value: --assembly SqlClient.Stress.Tests --console steps: @@ -178,7 +182,7 @@ jobs: inputs: command: build projects: $(project) - arguments: $(buildArguments) + arguments: ${{ variables.dotnetBuildOpts }} # Set a flag so test steps can distinguish a build failure from a test failure. - pwsh: Write-Host "##vso[task.setvariable variable=buildSucceeded]true" @@ -194,29 +198,33 @@ jobs: # - eq(variables['buildSucceeded'], 'true') gates on the flag set above, so tests are # skipped entirely if the build or any setup step failed (since there's nothing to run). # - # continueOnError: ${{ not(parameters.failOnTestFailure) }} - # - When failOnTestFailure is false, continueOnError is true: a test failure marks the + # continueOnError: ${{ parameters.warnOnTestFailure }} + # - When warnOnTestFailure is true, continueOnError is true: a test failure marks the # step and job as SucceededWithIssues (orange warning) rather than Failed. - # - When failOnTestFailure is true, continueOnError is false: a test failure fails the + # - When warnOnTestFailure is false, continueOnError is false: a test failure fails the # job (red), though subsequent runtimes still run due to the condition above. # - ${{ each runtime in parameters.netTestRuntimes }}: - task: DotNetCoreCLI@2 displayName: Test [${{ runtime }}] condition: and(succeededOrFailed(), eq(variables['buildSucceeded'], 'true')) - continueOnError: ${{ not(parameters.failOnTestFailure) }} + continueOnError: ${{ parameters.warnOnTestFailure }} + env: + STRESS_CONFIG_FILE: config.json inputs: command: run projects: $(project) - arguments: $(runArguments) --no-build -f ${{ runtime }} -e STRESS_CONFIG_FILE=config.jsonc -- $(testArguments) + arguments: ${{ variables.dotnetRunOpts }} -f ${{ runtime }} -- ${{ variables.stressTestOpts }} # Run the stress tests for each .NET Framework runtime. - ${{ each runtime in parameters.netFrameworkTestRuntimes }}: - task: DotNetCoreCLI@2 displayName: Test [${{ runtime }}] condition: and(succeededOrFailed(), eq(variables['buildSucceeded'], 'true')) - continueOnError: ${{ not(parameters.failOnTestFailure) }} + continueOnError: ${{ parameters.warnOnTestFailure }} + env: + STRESS_CONFIG_FILE: config.json inputs: command: run projects: $(project) - arguments: $(runArguments) --no-build -f ${{ runtime }} -e STRESS_CONFIG_FILE=config.jsonc -- $(testArguments) + arguments: ${{ variables.dotnetRunOpts }} -f ${{ runtime }} -- ${{ variables.stressTestOpts }} diff --git a/eng/pipelines/stress/stress-tests-pipeline.yml b/eng/pipelines/stress/stress-tests-pipeline.yml index 1f2e356461..edd962b911 100644 --- a/eng/pipelines/stress/stress-tests-pipeline.yml +++ b/eng/pipelines/stress/stress-tests-pipeline.yml @@ -14,7 +14,7 @@ # ADO.Net project: # Triggering pipeline: MDS Main CI (branch internal/main only) # Pipeline name: sqlclient-stress -# Pipeline URL: TODO: add URL when pipeline is created +# Pipeline URL: https://dev.azure.com/SqlClientDrivers/ADO.Net/_build?definitionId=2284 # Set the pipeline run name to the day-of-year and the daily run counter. name: $(DayOfYear)$(Rev:rr) @@ -73,10 +73,10 @@ parameters: type: boolean default: false - # True to fail the pipeline when stress tests fail. When false (default), test failures produce - # warnings but do not fail the overall pipeline run. - - name: failOnTestFailure - displayName: Fail pipeline on test failure + # When true, test failures produce warnings (SucceededWithIssues) but do not fail the pipeline. + # When false (default), test failures fail the pipeline. + - name: warnOnTestFailure + displayName: Warn (not fail) on test failure type: boolean default: false @@ -105,5 +105,5 @@ stages: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} - failOnTestFailure: ${{ parameters.failOnTestFailure }} + warnOnTestFailure: ${{ parameters.warnOnTestFailure }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} diff --git a/eng/pipelines/stress/stress-tests-stage.yml b/eng/pipelines/stress/stress-tests-stage.yml index f641ea0e7e..0ac192a601 100644 --- a/eng/pipelines/stress/stress-tests-stage.yml +++ b/eng/pipelines/stress/stress-tests-stage.yml @@ -30,16 +30,15 @@ parameters: # True to enable debugging steps. - name: debug type: boolean - default: false - # True to fail the job when stress tests fail. When false, test failures produce warnings. - - name: failOnTestFailure + # When true, test failures produce warnings (SucceededWithIssues) but do not fail the job. + # When false, test failures fail the job. All test steps always run regardless of this setting. + - name: warnOnTestFailure type: boolean # The verbosity level for the dotnet CLI commands. - name: dotnetVerbosity type: string - default: normal values: - quiet - minimal @@ -52,11 +51,11 @@ parameters: type: object default: [net462] - # The list of .NET runtimes to test against. These must align with the TargetFrameworks defined - # in the stress test Directory.Build.props and the TFMs that SqlClient ships. + # The list of .NET runtimes to test against. These should include the TFMs that SqlClient ships + # as well as any upcoming runtimes being validated (e.g. net10.0 is tested but not yet shipped). - name: netTestRuntimes type: object - default: [net8.0, net9.0] + default: [net8.0, net9.0, net10.0] stages: - stage: stress_tests_stage @@ -69,6 +68,15 @@ stages: - name: saPassword value: $[stageDependencies.secrets_stage.secrets_job.outputs['SaPassword.Value']] + # The 1ES pool name, determined automatically by ADO project: + # - ADO.Net -> ADO-1ES-Pool + # - public -> ADO-CI-1ES-Pool + - name: poolName + ${{ if eq(variables['System.TeamProject'], 'ADO.Net') }}: + value: ADO-1ES-Pool + ${{ else }}: + value: ADO-CI-1ES-Pool + jobs: # ---------------------------------------------------------------------------------------------- @@ -80,10 +88,12 @@ stages: debug: ${{ parameters.debug }} displayNamePrefix: Linux dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - failOnTestFailure: ${{ parameters.failOnTestFailure }} + warnOnTestFailure: ${{ parameters.warnOnTestFailure }} jobNameSuffix: linux + # No .NET Framework runtimes on Linux. + netFrameworkTestRuntimes: [] netTestRuntimes: ${{ parameters.netTestRuntimes }} - poolName: ADO-CI-1ES-Pool + poolName: ${{ variables.poolName }} saPassword: $(saPassword) sqlSetupStep: template: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml@self @@ -100,12 +110,12 @@ stages: debug: ${{ parameters.debug }} displayNamePrefix: Win dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - failOnTestFailure: ${{ parameters.failOnTestFailure }} + warnOnTestFailure: ${{ parameters.warnOnTestFailure }} jobNameSuffix: windows # Note that we include the .NET Framework runtimes for test runs on Windows. netFrameworkTestRuntimes: ${{ parameters.netFrameworkTestRuntimes }} netTestRuntimes: ${{ parameters.netTestRuntimes }} - poolName: ADO-CI-1ES-Pool + poolName: ${{ variables.poolName }} saPassword: $(saPassword) sqlSetupStep: template: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml@self @@ -124,8 +134,10 @@ stages: debug: ${{ parameters.debug }} displayNamePrefix: macOS dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - failOnTestFailure: ${{ parameters.failOnTestFailure }} + warnOnTestFailure: ${{ parameters.warnOnTestFailure }} jobNameSuffix: macos + # No .NET Framework runtimes on macOS. + netFrameworkTestRuntimes: [] netTestRuntimes: ${{ parameters.netTestRuntimes }} # We don't have any 1ES Hosted Pool images for macOS, so we use a generic one from Azure # Pipelines. diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props index 1d19b81910..addc60556f 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props @@ -9,4 +9,8 @@ true + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.cs index 1dcbde121f..a565889bd9 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/IMonitorLoader.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; -namespace Monitoring +namespace Microsoft.Data.SqlClient.Test.Stress { public interface IMonitorLoader { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/MonitorMetrics.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/MonitorMetrics.cs index ed37544e4e..5da0395e20 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/MonitorMetrics.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/IMonitorLoader/MonitorMetrics.cs @@ -4,7 +4,7 @@ using System; -namespace Monitoring +namespace Microsoft.Data.SqlClient.Test.Stress { public class MonitorMetrics { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalExceptionHandlerAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalExceptionHandlerAttribute.cs index 810580d9f8..cc4e5f6cc9 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalExceptionHandlerAttribute.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalExceptionHandlerAttribute.cs @@ -4,7 +4,7 @@ using System; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class GlobalExceptionHandlerAttribute : Attribute diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestCleanupAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestCleanupAttribute.cs index 2159d2630e..5a058fb23d 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestCleanupAttribute.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestCleanupAttribute.cs @@ -4,7 +4,7 @@ using System; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class GlobalTestCleanupAttribute : Attribute diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestSetupAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestSetupAttribute.cs index 00ed3d5b05..98d1c3c560 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestSetupAttribute.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/GlobalTestSetupAttribute.cs @@ -4,7 +4,7 @@ using System; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class GlobalTestSetupAttribute : Attribute diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestAttribute.cs index 3146c2d808..a3af2de8a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestAttribute.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestAttribute.cs @@ -4,7 +4,7 @@ using System; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public enum TestPriority { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestCleanupAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestCleanupAttribute.cs index 32bc5ee6bc..9825f98ac7 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestCleanupAttribute.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestCleanupAttribute.cs @@ -4,7 +4,7 @@ using System; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class TestCleanupAttribute : Attribute diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestSetupAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestSetupAttribute.cs index 5626032b69..b12254d75b 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestSetupAttribute.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestSetupAttribute.cs @@ -4,7 +4,7 @@ using System; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class TestSetupAttribute : Attribute diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestVariationAttribute.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestVariationAttribute.cs index e54acfa969..3277811e27 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestVariationAttribute.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/Attributes/TestVariationAttribute.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Text; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = true)] public class TestVariationAttribute : Attribute diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetection.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetection.cs index 50fc6d3d7a..b749726149 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetection.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetection.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public class DeadlockDetection { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetectionTaskScheduler.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetectionTaskScheduler.cs index 22a540def8..be5b44f0c9 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetectionTaskScheduler.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/DeadlockDetectionTaskScheduler.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public class DeadlockDetectionTaskScheduler : TaskScheduler { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/TestMetrics.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/TestMetrics.cs index 054a822dc1..fbab0e4248 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/TestMetrics.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/TestMetrics.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Reflection; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public static class TestMetrics { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/VersionUtil.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/VersionUtil.cs index 1778903834..ddcc24fb85 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/VersionUtil.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Common/VersionUtil.cs @@ -8,7 +8,7 @@ #pragma warning disable 618 -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public class VersionUtil { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/AsyncUtils.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/AsyncUtils.cs index 84f0fba0de..3d2b0cdbb5 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/AsyncUtils.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/AsyncUtils.cs @@ -8,9 +8,8 @@ using System.Runtime.ExceptionServices; using System.Threading.Tasks; using System.Xml; -using DPStressHarness; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { public enum SyncAsyncMode { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs index b61379aa0a..a83269e55e 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataSource.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { /// /// supported source types - values for 'type' attribute for 'source' node in App.config diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressConnection.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressConnection.cs index 46becd4897..cd47170ead 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressConnection.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressConnection.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { public class DataStressConnection : IDisposable { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressErrors.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressErrors.cs index 46b7751d50..5cc12b0dd7 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressErrors.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressErrors.cs @@ -5,7 +5,7 @@ using System; using System.Diagnostics; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { public enum ErrorHandlingAction { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressFactory.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressFactory.cs index 8e06a1de89..8d9ad88496 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressFactory.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressFactory.cs @@ -11,7 +11,7 @@ using System.Data.Common; using System.Diagnostics; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { /// /// Base class to generate utility objects required for stress tests to run. For example: connection strings, command texts, diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressReader.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressReader.cs index cad1bfa579..cee280fcf7 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressReader.cs @@ -13,7 +13,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { public class DataStressReader : IDisposable { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressSettings.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressSettings.cs index 8ddf737015..ddd498fde4 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressSettings.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataStressSettings.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { /// /// Loads dataStressSettings section from Stress.Data.Framework.dll.config (App.config in source tree) diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataTestGroup.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataTestGroup.cs index ea2bfe6553..b033e664cd 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataTestGroup.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/DataTestGroup.cs @@ -12,9 +12,8 @@ using System.Threading; using System.Threading.Tasks; -using DPStressHarness; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { /// /// basic set of tests to run on each managed provider diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/Extensions.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/Extensions.cs index 2629bc20bb..ceccbabc94 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/Extensions.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/Extensions.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using System.Xml; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { public static class Extensions { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressConfigReader.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressConfigReader.cs index 1b677f965a..db7f1957f6 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressConfigReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/StressConfigReader.cs @@ -8,9 +8,9 @@ using System.Text.Json; using System.Xml; using System.Xml.XPath; -using static Stress.Data.DataStressSettings; +using static Microsoft.Data.SqlClient.Test.Stress.DataStressSettings; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { /// /// Reads the configuration from a configuration file and provides the configuration diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/TrackedRandom.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/TrackedRandom.cs index b42128fff5..3b2211e5cf 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/TrackedRandom.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/TrackedRandom.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; -namespace Stress.Data +namespace Microsoft.Data.SqlClient.Test.Stress { /// /// Random number generator that tracks information necessary to reproduce a sequence of random numbers. diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Constants.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Constants.cs index 10f0ecb41b..8edd989cef 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Constants.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Constants.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Text; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public static class Constants { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Ex API/MemApi.Windows.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Ex API/MemApi.Windows.cs index 053aea09a1..fd0205eabb 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Ex API/MemApi.Windows.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Ex API/MemApi.Windows.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.InteropServices; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { static class MemApi { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/ITestAttributeFilter.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/ITestAttributeFilter.cs index c3afa9251d..51e54d0163 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/ITestAttributeFilter.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/ITestAttributeFilter.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 DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public interface ITestAttributeFilter { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/LogManager.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/LogManager.cs index 9b534a24b5..96271891dd 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/LogManager.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/LogManager.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Text; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public class LogManager: IDisposable { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/FakeConsole.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/FakeConsole.cs index f13949ae8e..b734c46d4d 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/FakeConsole.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/FakeConsole.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Text; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public static class FakeConsole { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/Logger.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/Logger.cs index 9226c4b930..ff9244d8b2 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/Logger.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/Logger.cs @@ -7,7 +7,7 @@ using System.Xml; using System.Diagnostics; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public class Logger { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/MonitorLoadUtils.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/MonitorLoadUtils.cs index 80661566fa..268f9d7dbf 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/MonitorLoadUtils.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/MonitorLoadUtils.cs @@ -3,19 +3,18 @@ // See the LICENSE file in the project root for more information. using System; -using Monitoring; using System.Reflection; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public static class MonitorLoader { public static IMonitorLoader LoadMonitorLoaderAssembly() { IMonitorLoader monitorloader = null; - const string classname = "Monitoring.MonitorLoader"; + const string classname = "Microsoft.Data.SqlClient.Test.Stress.MonitorLoader"; const string interfacename = "IMonitorLoader"; - Assembly mainAssembly = typeof(Monitoring.IMonitorLoader).GetTypeInfo().Assembly; + Assembly mainAssembly = typeof(IMonitorLoader).GetTypeInfo().Assembly; Type t = mainAssembly.GetType(classname); //make sure the type is derived from IMonitorLoader diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/RecordedExceptions.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/RecordedExceptions.cs index 72a10f4d30..36982ccde1 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/RecordedExceptions.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Monitor/RecordedExceptions.cs @@ -8,7 +8,7 @@ using System.Diagnostics; using System.Threading; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public class RecordedExceptions { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/PerfCounters.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/PerfCounters.cs index 84a3ccfba0..307de8e1e2 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/PerfCounters.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/PerfCounters.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 DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public class PerfCounters { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Program.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Program.cs index 6b49692aa7..da1ce59316 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Program.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Program.cs @@ -4,208 +4,195 @@ using System; using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Parsing; using System.IO; using System.Reflection; using Microsoft.Data.SqlClient; -namespace DPStressHarness//Microsoft.Data.SqlClient.Stress +namespace Microsoft.Data.SqlClient.Test.Stress { class Program { private static bool s_debugMode = false; - static int Main(string[] args) - { - Init(args); - return Run(); - } - - public enum RunMode - { - RunAll, - RunVerify, - Help, - ExitWithError - }; - - private static RunMode s_mode = RunMode.RunAll; + private static bool s_console = false; private static IEnumerable s_tests; private static StressEngine s_eng; - private static string s_error; - private static bool s_console = false; - public static void Init(string[] args) + static int Main(string[] args) { - for (int i = 0; i < args.Length; i++) + var assemblyOption = new Option("--assembly") + { + Description = "The name of the assembly containing the tests (loaded via Assembly.Load).", + Required = true + }; + + var overrideOption = new Option("--override") + { + Description = "Override a test property. Format: name=value", + AllowMultipleArgumentsPerToken = true + }; + + var variationOption = new Option("--variation") + { + Description = "Add a test variation.", + AllowMultipleArgumentsPerToken = true + }; + + var testOption = new Option("--test") { Description = "Run specific test(s), semicolon-separated." }; + var durationOption = new Option("--duration") { Description = "Duration of the test in seconds." }; + var threadsOption = new Option("--threads") { Description = "Number of threads to use." }; + var consoleOption = new Option("--console") { Description = "Emit all output to the console." }; + var debugOption = new Option("--debug") { Description = "Print process ID and wait for debugger attach." }; + var exceptionThresholdOption = new Option("--exception-threshold") { Description = "Limit on exceptions before test halts." }; + var monitorEnabledOption = new Option("--monitor-enabled") { Description = "Enable monitoring." }; + var randomSeedOption = new Option("--random-seed") { Description = "Seed for the random number generator." }; + var filterOption = new Option("--filter") { Description = "Run tests matching the given filter. Example: TestType=Query,Update;IsServerTest=True" }; + var printMethodNameOption = new Option("--print-method-name") { Description = "Print test method names to console." }; + var deadlockDetectionOption = new Option("--deadlock-detection") { Description = "Enable deadlock detection." }; + + var rootCommand = new RootCommand("SqlClient Stress Test Runner") { - switch (args[i]) + assemblyOption, + overrideOption, + variationOption, + testOption, + durationOption, + threadsOption, + consoleOption, + debugOption, + exceptionThresholdOption, + monitorEnabledOption, + randomSeedOption, + filterOption, + printMethodNameOption, + deadlockDetectionOption + }; + + rootCommand.SetAction((ParseResult result) => + { + // Assembly + TestFinder.AssemblyName = new AssemblyName(result.GetValue(assemblyOption)); + + // Overrides (format: name=value) + var overrides = result.GetValue(overrideOption); + if (overrides != null) { - case "-a": - string assemblyName = args[++i]; - TestFinder.AssemblyName = new AssemblyName(assemblyName); - break; - - case "-all": - s_mode = RunMode.RunAll; - break; - - case "-override": - TestMetrics.Overrides.Add(args[++i], args[++i]); - break; - - case "-variation": - TestMetrics.Variations.Add(args[++i]); - break; - - case "-test": - TestMetrics.SelectedTests.AddRange(args[++i].Split(';')); - break; - - case "-duration": - TestMetrics.StressDuration = int.Parse(args[++i]); - break; - - case "-threads": - TestMetrics.StressThreads = int.Parse(args[++i]); - break; - - case "-verify": - s_mode = RunMode.RunVerify; - break; - - case "-console": - s_console = true; - break; - - case "-debug": - s_debugMode = true; - if (System.Diagnostics.Debugger.IsAttached) + foreach (var item in overrides) + { + var eqIndex = item.IndexOf('='); + if (eqIndex <= 0) { - System.Diagnostics.Debugger.Break(); + Console.Error.WriteLine($"Error: --override value '{item}' must be in name=value format."); + Environment.Exit(1); } - else - { - Console.WriteLine("Current PID: {0}, attach the debugger and press Enter to continue the execution...", System.Diagnostics.Process.GetCurrentProcess().Id); - Console.ReadLine(); - } - break; + TestMetrics.Overrides.Add(item.Substring(0, eqIndex), item.Substring(eqIndex + 1)); + } + } - case "-exceptionThreshold": - TestMetrics.ExceptionThreshold = int.Parse(args[++i]); - break; + // Variations + var variations = result.GetValue(variationOption); + if (variations != null) + { + foreach (var v in variations) + { + TestMetrics.Variations.Add(v); + } + } - case "-monitorenabled": - TestMetrics.MonitorEnabled = bool.Parse(args[++i]); - break; + // Test + var test = result.GetValue(testOption); + if (test != null) + { + TestMetrics.SelectedTests.AddRange(test.Split(';')); + } - case "-randomSeed": - TestMetrics.RandomSeed = int.Parse(args[++i]); - break; + // Duration + var duration = result.GetValue(durationOption); + if (duration.HasValue) + { + TestMetrics.StressDuration = duration.Value; + } - case "-filter": - TestMetrics.Filter = args[++i]; - break; + // Threads + var threads = result.GetValue(threadsOption); + if (threads.HasValue) + { + TestMetrics.StressThreads = threads.Value; + } - case "-printMethodName": - TestMetrics.PrintMethodName = true; - break; + // Console + s_console = result.GetValue(consoleOption); - case "-deadlockdetection": - if (bool.Parse(args[++i])) - { - DeadlockDetection.Enable(); - } - break; + // Debug + s_debugMode = result.GetValue(debugOption); + if (s_debugMode) + { + if (System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Break(); + } + else + { + Console.WriteLine("Current PID: {0}, attach the debugger and press Enter to continue the execution...", System.Diagnostics.Process.GetCurrentProcess().Id); + Console.ReadLine(); + } + } - default: - s_mode = RunMode.Help; - break; + // Exception threshold + var threshold = result.GetValue(exceptionThresholdOption); + if (threshold.HasValue) + { + TestMetrics.ExceptionThreshold = threshold.Value; } - } - PrintConfigSummary(); + // Monitor enabled + var monitor = result.GetValue(monitorEnabledOption); + if (monitor.HasValue) + { + TestMetrics.MonitorEnabled = monitor.Value; + } - if (TestFinder.AssemblyName != null) - { - Console.WriteLine("Assembly Found for the Assembly Name " + TestFinder.AssemblyName); + // Random seed + var seed = result.GetValue(randomSeedOption); + if (seed.HasValue) + { + TestMetrics.RandomSeed = seed.Value; + } - // get and load all the tests - s_tests = TestFinder.GetTests(Assembly.Load(TestFinder.AssemblyName)); + // Filter + var filter = result.GetValue(filterOption); + if (filter != null) + { + TestMetrics.Filter = filter; + } - // instantiate the stress engine - s_eng = new StressEngine(TestMetrics.StressThreads, TestMetrics.StressDuration, s_tests, TestMetrics.RandomSeed); - } - else - { - Program.s_error = string.Format("Assembly {0} cannot be found.", TestFinder.AssemblyName); - s_mode = RunMode.ExitWithError; - } - } + // Print method name + TestMetrics.PrintMethodName = result.GetValue(printMethodNameOption); - public static int Run() - { - if (TestFinder.AssemblyName == null) - { - s_mode = RunMode.Help; - } - switch (s_mode) - { - case RunMode.RunAll: - return RunStress(); + // Deadlock detection + if (result.GetValue(deadlockDetectionOption)) + { + DeadlockDetection.Enable(); + } - case RunMode.RunVerify: - return RunVerify(); + // Print config, load tests, and run + PrintConfigSummary(); - case RunMode.ExitWithError: - return ExitWithError(); + Console.WriteLine("Assembly Found for the Assembly Name " + TestFinder.AssemblyName); + s_tests = TestFinder.GetTests(Assembly.Load(TestFinder.AssemblyName)); + s_eng = new StressEngine(TestMetrics.StressThreads, TestMetrics.StressDuration, s_tests, TestMetrics.RandomSeed); + }); - case RunMode.Help: - default: - return PrintHelp(); + var parseResult = rootCommand.Parse(args); + int exitCode = parseResult.Invoke(); + if (exitCode != 0 || s_eng == null) + { + return exitCode; } - } - private static int PrintHelp() - { - Console.WriteLine("stresstest.exe [-a ] "); - Console.WriteLine(); - Console.WriteLine(" -a should specify path to the assembly containing the tests."); - Console.WriteLine(); - Console.WriteLine("Supported options are:"); - Console.WriteLine(); - Console.WriteLine(" -all Run all tests - best for debugging, not perf measurements."); - Console.WriteLine(); - Console.WriteLine(" -verify Run in functional verification mode."); - Console.WriteLine(); - Console.WriteLine(" -duration Duration of the test in seconds."); - Console.WriteLine(); - Console.WriteLine(" -threads Number of threads to use."); - Console.WriteLine(); - Console.WriteLine(" -override Override the value of a test property."); - Console.WriteLine(); - Console.WriteLine(" -test Run specific test(s)."); - Console.WriteLine(); - Console.WriteLine(" -console Emit all output to the console."); - Console.WriteLine(); - Console.WriteLine(" -debug Print process ID in the beginning and wait for Enter (to give your time to attach the debugger)."); - Console.WriteLine(); - Console.WriteLine(" -exceptionThreshold An optional limit on exceptions which will be caught. When reached, test will halt."); - Console.WriteLine(); - Console.WriteLine(" -monitorenabled True or False to enable monitoring. Default is false"); - Console.WriteLine(); - Console.WriteLine(" -randomSeed Enables setting of the random number generator used internally. This serves both the purpose"); - Console.WriteLine(" of helping to improve reproducibility and making it deterministic from Chess's perspective"); - Console.WriteLine(" for a given schedule. Default is " + TestMetrics.RandomSeed + "."); - Console.WriteLine(); - Console.WriteLine(" -filter Run tests whose stress test attributes match the given filter. Filter is not applied if attribute"); - Console.WriteLine(" does not implement ITestAttributeFilter. Example: -filter TestType=Query,Update;IsServerTest=True "); - Console.WriteLine(); - Console.WriteLine(" -printMethodName Print tests' title in console window"); - Console.WriteLine(); - Console.WriteLine(" -deadlockdetection True or False to enable deadlock detection. Default is false"); - Console.WriteLine(); - - return 1; + return Run(); } private static void PrintConfigSummary() @@ -215,7 +202,6 @@ private static void PrintConfigSummary() Console.WriteLine(border); Console.WriteLine($"MDS Version: {GetMdsVersion()}"); Console.WriteLine($"Test Assembly Name: {TestFinder.AssemblyName}"); - Console.WriteLine($"Run mode: {Enum.GetName(typeof(RunMode), s_mode)}"); foreach (var item in TestMetrics.Overrides) { Console.WriteLine($"Override: {item.Key} = {item.Value}"); @@ -235,18 +221,7 @@ private static void PrintConfigSummary() Console.WriteLine(border); } - private static int ExitWithError() - { - Environment.FailFast("Exit with error(s)."); - return 1; - } - - private static int RunVerify() - { - throw new NotImplementedException(); - } - - private static int RunStress() + private static int Run() { if (!s_console) { @@ -275,7 +250,7 @@ private static string GetMdsVersion() // See: tools/targets/GenerateThisAssemblyCs.targets // var assembly = typeof(SqlConnection).Assembly; - var type = assembly.GetType("System.ThisAssembly"); + var type = assembly.GetType("Microsoft.Data.SqlClient.ThisAssembly"); if (type is null) { return ""; diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj index d0c325fcef..8a2bc08b4c 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj @@ -5,6 +5,10 @@ stresstest + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/StressEngine.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/StressEngine.cs index 349b5c0539..f20b9104d1 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/StressEngine.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/StressEngine.cs @@ -6,9 +6,8 @@ using System.Collections.Generic; using System.Threading; using System.Diagnostics; -using Monitoring; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public class StressEngine { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/TestFinder.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/TestFinder.cs index 3f18c6df43..498964c132 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/TestFinder.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/TestFinder.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Reflection; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { internal class TestFinder { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/MultithreadedTest.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/MultithreadedTest.cs index 01ea961426..1fe985936b 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/MultithreadedTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/MultithreadedTest.cs @@ -8,7 +8,7 @@ using System.Diagnostics; using System.Threading; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { internal class MultiThreadedTest : TestBase { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/StressTest.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/StressTest.cs index c2637d5e5d..a96838b4f5 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/StressTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/StressTest.cs @@ -9,7 +9,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { internal class StressTest : TestBase { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/Test.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/Test.cs index a73448a30b..35353ec7bf 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/Test.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/Test.cs @@ -7,7 +7,7 @@ using System.Reflection; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { internal class Test : TestBase { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/TestBase.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/TestBase.cs index 95546547da..3cc2dac8bf 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/TestBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/TestBase.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Reflection; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { public abstract class TestBase { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/ThreadPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/ThreadPoolTest.cs index f065c15312..c52125ea7e 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/ThreadPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/Tests/ThreadPoolTest.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; using System.Threading; -namespace DPStressHarness +namespace Microsoft.Data.SqlClient.Test.Stress { internal class ThreadPoolTest : TestBase { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/FilteredDefaultTraceListener.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/FilteredDefaultTraceListener.cs index ce29f8ee29..0e102fedef 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/FilteredDefaultTraceListener.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/FilteredDefaultTraceListener.cs @@ -7,7 +7,7 @@ using System.Reflection; using System.Text.RegularExpressions; -namespace Stress.Data.SqlClient +namespace Microsoft.Data.SqlClient.Test.Stress { /// /// A DefaultTraceListener that can filter out given asserts diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/HostsFileManager.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/HostsFileManager.cs index 6d44909ddb..7c665ee516 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/HostsFileManager.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/HostsFileManager.cs @@ -12,7 +12,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace Microsoft.Test.Data.SqlClient +namespace Microsoft.Data.SqlClient.Test.Stress { /// /// allows user to manipulate %windir%\system32\drivers\etc\hosts diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/MultiSubnetFailoverSetup.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/MultiSubnetFailoverSetup.cs index 2a0719d201..9fb1f9b5bb 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/MultiSubnetFailoverSetup.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/MultiSubnetFailoverSetup.cs @@ -2,14 +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.Test.Data.SqlClient; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; -namespace Stress.Data.SqlClient +namespace Microsoft.Data.SqlClient.Test.Stress { internal class MultiSubnetFailoverSetup { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/NetUtils.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/NetUtils.cs index 0e756d1bf9..c3bac5b53d 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/NetUtils.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/NetUtils.cs @@ -9,7 +9,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Microsoft.Test.Data.SqlClient +namespace Microsoft.Data.SqlClient.Test.Stress { public static class NetUtils { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientStressFactory.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientStressFactory.cs index 7aec9d461a..32db797c97 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientStressFactory.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientStressFactory.cs @@ -6,9 +6,8 @@ using System.Collections.Generic; using System.Diagnostics; using Microsoft.Data.SqlClient; -using Microsoft.Test.Data.SqlClient; -namespace Stress.Data.SqlClient +namespace Microsoft.Data.SqlClient.Test.Stress { public class SqlClientStressFactory : DataStressFactory { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs index 7c7597ba8a..b524a15a14 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClientTestGroup.cs @@ -11,10 +11,9 @@ using Microsoft.Data.SqlClient; using System.Xml; -using DPStressHarness; using System.IO; -namespace Stress.Data.SqlClient +namespace Microsoft.Data.SqlClient.Test.Stress { public class SqlClientTestGroup : DataTestGroup { From fd8cb4b8242a1410f17a4965804c9ccd644e7c81 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:05:37 -0300 Subject: [PATCH 142/298] Use upper-bound version ranges for sibling package dependencies (#4337) * Use upper-bound version ranges for sibling package dependencies Compute [floor, ceiling) ranges for sibling packages so NuGet cannot resolve an incompatible newer minor/major version at restore time. - Directory.Packages.props: derive *VersionCeiling from *PackageVersion; apply ranges to sibling PackageVersion items; add SniVersionRange - SqlClient csproj: compute version range properties for nuspec expansion - SqlClient nuspec: replace bare version tokens with range tokens * CI: Pass ReferenceType and dependency versions to pack jobs The CI/PR pack jobs for Abstractions and Azure extensions were not passing ReferenceType=Package to dotnet pack, causing the projects to use ProjectReference mode for sibling dependencies. NuGet converts ProjectReferences to bare version dependencies (no ranges), which defeats the upper-bound version ranges defined in Directory.Packages.props. Fix: - pack-abstractions-package-ci-job: Add referenceType, loggingArtifactsName, and loggingPackageVersion parameters. When referenceType=Package, download Logging artifacts and pass ReferenceType + LoggingPackageVersion to dotnet pack buildProperties. - pack-azure-package-ci-job: Add loggingPackageVersion parameter. When referenceType=Package, pass ReferenceType + dependency versions to dotnet pack buildProperties. - Wire the new parameters through build-abstractions-package-ci-stage.yml and dotnet-sqlclient-ci-core.yml. --- Directory.Packages.props | 41 +++++++++--- eng/pipelines/dotnet-sqlclient-ci-core.yml | 3 + .../jobs/pack-abstractions-package-ci-job.yml | 67 ++++++++++++++++--- .../jobs/pack-azure-package-ci-job.yml | 43 +++++++++--- .../build-abstractions-package-ci-stage.yml | 27 ++++++++ .../stages/build-azure-package-ci-stage.yml | 2 + .../src/Microsoft.Data.SqlClient.csproj | 11 ++- .../src/Microsoft.Data.SqlClient.nuspec | 30 ++++----- 8 files changed, 180 insertions(+), 44 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a74caf225a..c35ad35a59 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -40,6 +40,31 @@ false + + + + 6.0.2 + [$(SniVersion), $([MSBuild]::Add($(SniVersion.Split('.')[0]), 1)).0.0) + + + + $([MSBuild]::Add($(SqlServerPackageVersion.Trim().Split('.')[0]), 1)).0.0 + $([MSBuild]::Add($(LoggingPackageVersion.Trim().Split('.')[0]), 1)).0.0 + $([MSBuild]::Add($(AbstractionsPackageVersion.Trim().Split('.')[0]), 1)).0.0 + $([MSBuild]::Add($(SqlClientPackageVersion.Trim().Split('.')[0]), 1)).0.0 + $([MSBuild]::Add($(AzurePackageVersion.Trim().Split('.')[0]), 1)).0.0 + $([MSBuild]::Add($(AkvProviderPackageVersion.Trim().Split('.')[0]), 1)).0.0 + + @@ -47,22 +72,22 @@ + Version="[$(SqlServerPackageVersion), $(SqlServerVersionCeiling))" /> + Version="[$(LoggingPackageVersion), $(LoggingVersionCeiling))" /> + Version="[$(AbstractionsPackageVersion), $(AbstractionsVersionCeiling))" /> + Version="[$(SqlClientPackageVersion), $(SqlClientVersionCeiling))" /> + Version="[$(AzurePackageVersion), $(AzureVersionCeiling))" /> + Version="[$(AkvProviderPackageVersion), $(AkvProviderVersionCeiling))" /> @@ -108,8 +133,8 @@ - - + + diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 6c4399c9cb..9ee7c7132d 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -164,6 +164,9 @@ stages: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + loggingArtifactsName: $(loggingArtifactsName) + loggingPackageVersion: $(loggingPackageVersion) + referenceType: ${{ parameters.referenceType }} # When building Abstractions via packages, we must depend on the Logging # package. ${{ if eq(parameters.referenceType, 'Package') }}: diff --git a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml index a9cb5b1043..d077aa2115 100644 --- a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml @@ -53,6 +53,30 @@ parameters: - detailed - diagnostic + # The name of the Logging pipeline artifacts to download. + # + # This is used when the referenceType is 'Package'. + - name: loggingArtifactsName + type: string + default: Logging.Artifacts + + # The Logging package version to depend on. + # + # This is used when the referenceType is 'Package'. + - name: loggingPackageVersion + type: string + default: '' + + # The C# project reference type to use when building and packing the packages. + - name: referenceType + type: string + default: Project + values: + # Reference sibling packages as NuGet packages. + - Package + # Reference sibling packages as C# projects. + - Project + jobs: - job: pack_abstractions_package_job @@ -102,21 +126,46 @@ jobs: - pwsh: 'Get-ChildItem Env: | Sort-Object Name' displayName: '[Debug] Print Environment Variables' + # For Package reference builds, we must first download the dependency + # package artifacts. + - ${{ if eq(parameters.referenceType, 'Package') }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Logging Package Artifacts + inputs: + artifactName: ${{ parameters.loggingArtifactsName }} + targetPath: $(Build.SourcesDirectory)/packages + # Install the .NET SDK. - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} # Create the NuGet packages. - - task: DotNetCoreCLI@2 - displayName: Create NuGet Package - inputs: - command: pack - packagesToPack: $(project) - configurationToPack: ${{ parameters.buildConfiguration }} - packDirectory: $(dotnetPackagesDir) - verbosityToPack: ${{ parameters.dotnetVerbosity }} - buildProperties: AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }};AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }} + # + # When referenceType is Package, we must pass ReferenceType and the + # dependency version so that Directory.Packages.props applies version + # ranges to sibling package dependencies. + - ${{ if eq(parameters.referenceType, 'Package') }}: + - task: DotNetCoreCLI@2 + displayName: Create NuGet Package + inputs: + command: pack + packagesToPack: $(project) + configurationToPack: ${{ parameters.buildConfiguration }} + packDirectory: $(dotnetPackagesDir) + verbosityToPack: ${{ parameters.dotnetVerbosity }} + buildProperties: AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }};AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }};ReferenceType=Package;LoggingPackageVersion=${{ parameters.loggingPackageVersion }} + + - ${{ else }}: + - task: DotNetCoreCLI@2 + displayName: Create NuGet Package + inputs: + command: pack + packagesToPack: $(project) + configurationToPack: ${{ parameters.buildConfiguration }} + packDirectory: $(dotnetPackagesDir) + verbosityToPack: ${{ parameters.dotnetVerbosity }} + buildProperties: AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }};AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }} # Publish the NuGet packages as a named pipeline artifact. - task: PublishPipelineArtifact@1 diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index eaf0e3c66f..12567fec14 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -26,12 +26,19 @@ parameters: type: string default: Logging.Artifacts - # The Abstractions package verion to depend on. + # The Abstractions package version to depend on. # # This is used when the referenceType is 'Package'. - name: abstractionsPackageVersion type: string + # The Logging package version to depend on. + # + # This is used when the referenceType is 'Package'. + - name: loggingPackageVersion + type: string + default: '' + # The name of the pipeline artifacts to publish. - name: azureArtifactsName type: string @@ -151,15 +158,31 @@ jobs: debug: ${{ parameters.debug }} # Create the NuGet packages. - - task: DotNetCoreCLI@2 - displayName: Create NuGet Package - inputs: - command: pack - packagesToPack: $(project) - configurationToPack: ${{ parameters.buildConfiguration }} - packDirectory: $(dotnetPackagesDir) - verbosityToPack: ${{ parameters.dotnetVerbosity }} - buildProperties: AzurePackageVersion=${{ parameters.azurePackageVersion }};AzureAssemblyFileVersion=${{ parameters.azureAssemblyFileVersion }} + # + # When referenceType is Package, we must pass ReferenceType and the + # dependency versions so that Directory.Packages.props applies version + # ranges to sibling package dependencies. + - ${{ if eq(parameters.referenceType, 'Package') }}: + - task: DotNetCoreCLI@2 + displayName: Create NuGet Package + inputs: + command: pack + packagesToPack: $(project) + configurationToPack: ${{ parameters.buildConfiguration }} + packDirectory: $(dotnetPackagesDir) + verbosityToPack: ${{ parameters.dotnetVerbosity }} + buildProperties: AzurePackageVersion=${{ parameters.azurePackageVersion }};AzureAssemblyFileVersion=${{ parameters.azureAssemblyFileVersion }};ReferenceType=Package;LoggingPackageVersion=${{ parameters.loggingPackageVersion }};AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + + - ${{ else }}: + - task: DotNetCoreCLI@2 + displayName: Create NuGet Package + inputs: + command: pack + packagesToPack: $(project) + configurationToPack: ${{ parameters.buildConfiguration }} + packDirectory: $(dotnetPackagesDir) + verbosityToPack: ${{ parameters.dotnetVerbosity }} + buildProperties: AzurePackageVersion=${{ parameters.azurePackageVersion }};AzureAssemblyFileVersion=${{ parameters.azureAssemblyFileVersion }} # Publish the NuGet packages as a named pipeline artifact. - task: PublishPipelineArtifact@1 diff --git a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml index 6aae1703b8..8579b79caf 100644 --- a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml +++ b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml @@ -67,6 +67,30 @@ parameters: - detailed - diagnostic + # The name of the Logging pipeline artifacts to download. + # + # This is used when the referenceType is 'Package'. + - name: loggingArtifactsName + type: string + default: Logging.Artifacts + + # The Logging package version to depend on. + # + # This is used when the referenceType is 'Package'. + - name: loggingPackageVersion + type: string + default: '' + + # The C# project reference type to use when building and packing the packages. + - name: referenceType + type: string + default: Project + values: + # Reference sibling packages as NuGet packages. + - Package + # Reference sibling packages as C# projects. + - Project + stages: - stage: build_abstractions_package_stage @@ -141,3 +165,6 @@ stages: - test_abstractions_package_job_windows - test_abstractions_package_job_macos dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + loggingArtifactsName: ${{ parameters.loggingArtifactsName }} + loggingPackageVersion: ${{ parameters.loggingPackageVersion }} + referenceType: ${{ parameters.referenceType }} diff --git a/eng/pipelines/stages/build-azure-package-ci-stage.yml b/eng/pipelines/stages/build-azure-package-ci-stage.yml index 1da56a18ef..b3cf9073d5 100644 --- a/eng/pipelines/stages/build-azure-package-ci-stage.yml +++ b/eng/pipelines/stages/build-azure-package-ci-stage.yml @@ -331,4 +331,6 @@ stages: - test_azure_package_job_windows_integration - test_azure_package_job_macos dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + loggingArtifactsName: ${{ parameters.loggingArtifactsName }} + loggingPackageVersion: ${{ parameters.loggingPackageVersion }} referenceType: ${{ parameters.referenceType }} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index e9b3ad2741..06eaf9e914 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -207,9 +207,16 @@ - + - <_SqlClientPackNuspecExpandedText>$([System.IO.File]::ReadAllText('$(SqlClientPackNuspecTemplatePath)').Replace('$AbstractionsPackageVersion$','$(AbstractionsPackageVersion)').Replace('$LoggingPackageVersion$','$(LoggingPackageVersion)').Replace('$SqlServerPackageVersion$','$(SqlServerPackageVersion)')) + + <_AbstractionsVersionRange>[$(_AbstractionsPackageVersionTrimmed), $([MSBuild]::Add($(_AbstractionsPackageVersionTrimmed.Split('.')[0]), 1)).0.0) + <_LoggingVersionRange>[$(_LoggingPackageVersionTrimmed), $([MSBuild]::Add($(_LoggingPackageVersionTrimmed.Split('.')[0]), 1)).0.0) + <_SqlServerVersionRange>[$(_SqlServerPackageVersionTrimmed), $([MSBuild]::Add($(_SqlServerPackageVersionTrimmed.Split('.')[0]), 1)).0.0) + + + <_SqlClientPackNuspecExpandedText>$([System.IO.File]::ReadAllText('$(SqlClientPackNuspecTemplatePath)').Replace('$AbstractionsVersionRange$','$(_AbstractionsVersionRange)').Replace('$LoggingVersionRange$','$(_LoggingVersionRange)').Replace('$SqlServerVersionRange$','$(_SqlServerVersionRange)').Replace('$SniVersionRange$','$(SniVersionRange)')) - - - + + + @@ -56,38 +56,38 @@ - - - + + + - + - - - + + + - + - - - + + + - + From 377f5bf0e197626e4dee637451017bf0704b184c Mon Sep 17 00:00:00 2001 From: priyankatiwari08 Date: Thu, 11 Jun 2026 13:23:35 +0530 Subject: [PATCH 143/298] Auto-triage: label-gated follow-up, partial-progress acknowledgement, and /triage on-demand command (#4324) * Add one-time follow-up triage when author provides missing env info Extends the issue auto-triage workflow so a second (and final) triage summary is produced when: 1. The first triage flagged missing environment fields, AND 2. The original issue author responds with a follow-up comment. No further triage runs after the second summary. Comments from other users, bots, or on PRs do not trigger re-triage. NOTE: Maintainers must run 'gh aw compile' to regenerate .github/workflows/issue-triage.lock.yml from this .md source. * Regenerate issue-triage.lock.yml (gh aw compile v0.72.1) * Add label gate, /triage on-demand, and partial-progress follow-up routing * Silence noop tracking issue (report-as-issue: false) * Exclude /triage prefix from author follow-up branch Prevents non-maintainer issue authors from invoking the on-demand /triage command via the author+label follow-up branch. Lock file regenerated with gh aw compile v0.72.1. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Tighten /triage command matching to strict token Replace startsWith(body, '/triage') with strict token match (body == '/triage' || startsWith(body, '/triage ')) in both maintainer and author branches of the workflow-level if: gate so /triage-foo, /triagery, etc. no longer falsely match. Lock file regenerated with gh aw compile v0.72.1. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/issue-triage.lock.yml | 99 ++++++++++--- .github/workflows/issue-triage.md | 177 ++++++++++++++++++++++-- 2 files changed, 247 insertions(+), 29 deletions(-) diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 54d5661b0c..cc20a3b859 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"20b8fbbfaae4d08b3206fb6b2f4000e43b8915e37f5afc9a534056f8ce9a563b","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"3fbe69c77302fcde863ac1c3f9df536f89e6a71a6b141b505437f06ed45182bc","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -47,6 +47,9 @@ name: "SqlClient Issue Auto-Triage" "on": + issue_comment: + types: + - created issues: types: - opened @@ -61,6 +64,20 @@ run-name: "SqlClient Issue Auto-Triage" jobs: activation: + if: > + github.event_name == 'issues' || + (github.event_name == 'issue_comment' + && github.event.issue.pull_request == null + && !endsWith(github.event.comment.user.login, '[bot]') + && ( + ((github.event.comment.body == '/triage' || startsWith(github.event.comment.body, '/triage ')) + && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) + || + (github.event.comment.body != '/triage' + && !startsWith(github.event.comment.body, '/triage ') + && github.event.comment.user.login == github.event.issue.user.login + && contains(github.event.issue.labels.*.name, 'Auto-Triage: Waiting for Author')) + )) runs-on: ubuntu-slim permissions: actions: read @@ -185,24 +202,25 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_b5f7a25dd7272e5f_EOF' + cat << 'GH_AW_PROMPT_ab488ada0396c523_EOF' - GH_AW_PROMPT_b5f7a25dd7272e5f_EOF + GH_AW_PROMPT_ab488ada0396c523_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_b5f7a25dd7272e5f_EOF' + cat << 'GH_AW_PROMPT_ab488ada0396c523_EOF' - Tools: add_comment, missing_tool, missing_data, noop + Tools: add_comment, add_labels, remove_labels, missing_tool, missing_data, noop - GH_AW_PROMPT_b5f7a25dd7272e5f_EOF + GH_AW_PROMPT_ab488ada0396c523_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_b5f7a25dd7272e5f_EOF' + cat << 'GH_AW_PROMPT_ab488ada0396c523_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -231,12 +249,15 @@ jobs: {{/if}} - GH_AW_PROMPT_b5f7a25dd7272e5f_EOF + GH_AW_PROMPT_ab488ada0396c523_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_b5f7a25dd7272e5f_EOF' + if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then + cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" + fi + cat << 'GH_AW_PROMPT_ab488ada0396c523_EOF' {{#runtime-import .github/workflows/issue-triage.md}} - GH_AW_PROMPT_b5f7a25dd7272e5f_EOF + GH_AW_PROMPT_ab488ada0396c523_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -261,6 +282,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' with: script: | @@ -281,6 +303,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT, GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST } }); @@ -430,15 +453,17 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_421fab10d521b1cd_EOF' - {"add_comment":{"hide_older_comments":true,"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_421fab10d521b1cd_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a309dbbfe73b9ee4_EOF' + {"add_comment":{"hide_older_comments":true,"max":1},"add_labels":{"allowed":["Auto-Triage: Waiting for Author"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"remove_labels":{"allowed":["Auto-Triage: Waiting for Author"],"max":1},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_a309dbbfe73b9ee4_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Supports reply_to_id for discussion threading." + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Supports reply_to_id for discussion threading.", + "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"Auto-Triage: Waiting for Author\"].", + "remove_labels": " CONSTRAINTS: Maximum 1 label(s) can be removed. Only these labels can be removed: [Auto-Triage: Waiting for Author]." }, "repo_params": {}, "dynamic_tools": [] @@ -467,6 +492,25 @@ jobs: } } }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, "missing_data": { "defaultMax": 20, "fields": { @@ -524,6 +568,25 @@ jobs: } } }, + "remove_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, "report_incomplete": { "defaultMax": 5, "fields": { @@ -619,7 +682,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_95017dc8ef4cee83_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_52e7f3ed4af388a8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -663,7 +726,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_95017dc8ef4cee83_EOF + GH_AW_MCP_CONFIG_52e7f3ed4af388a8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -943,7 +1006,7 @@ jobs: GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_NOOP_REPORT_AS_ISSUE: "false" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1294,7 +1357,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"Auto-Triage: Waiting for Author\"],\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"allowed\":[\"Auto-Triage: Waiting for Author\"],\"max\":1},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index 793dfc81c3..4f0d391f41 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -2,8 +2,44 @@ on: issues: types: [opened] + issue_comment: + types: [created] roles: all +# Cheap gate evaluated BEFORE the agent boots. The activation job is skipped +# (zero compute, $0) if this is false. Only events matching one of the +# following three conditions cause the workflow to run: +# +# 1. issues.opened +# -> Initial triage. Always runs. +# +# 2. issue_comment.created from the issue's original author, on an issue +# (not a PR), not a bot, AND the issue currently has the label +# "Auto-Triage: Waiting for Author". +# -> Follow-up triage. The label is applied by the initial triage +# only when environment fields were missing, and removed by the +# follow-up triage once the author supplies them. Without the +# label, author comments do NOT boot the agent. +# +# 3. issue_comment.created whose body starts with "/triage", from a repo +# OWNER, MEMBER, or COLLABORATOR (maintainer-only on-demand override). +# -> On-demand triage. Bypasses the follow-up gate; produces a fresh +# triage summary regardless of label or prior summaries. +if: | + github.event_name == 'issues' || + (github.event_name == 'issue_comment' + && github.event.issue.pull_request == null + && !endsWith(github.event.comment.user.login, '[bot]') + && ( + ((github.event.comment.body == '/triage' || startsWith(github.event.comment.body, '/triage ')) + && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) + || + (github.event.comment.body != '/triage' + && !startsWith(github.event.comment.body, '/triage ') + && github.event.comment.user.login == github.event.issue.user.login + && contains(github.event.issue.labels.*.name, 'Auto-Triage: Waiting for Author')) + )) + engine: copilot permissions: @@ -16,27 +52,88 @@ tools: min-integrity: none safe-outputs: + # One triage summary per run. `hide-older-comments` collapses previous + # summaries so only the latest is visible. add-comment: max: 1 hide-older-comments: true + # Allow the workflow to apply/remove ONLY this one internal-state label. + # The label is the YAML-level flag that lets the cheap `if:` gate above + # decide whether an author comment should boot the agent at all. + add-labels: + allowed: ["Auto-Triage: Waiting for Author"] + max: 1 + remove-labels: + allowed: ["Auto-Triage: Waiting for Author"] + max: 1 + # Silently skip noop runs (e.g. random author comment with no new env info). + # Without this, gh-aw auto-creates a tracking issue "[aw] No-Op Runs" and + # appends a comment to it for every noop. + noop: + report-as-issue: false --- # SqlClient Issue Auto-Triage You are a triage specialist for **Microsoft.Data.SqlClient**. -A new issue has just been opened. Your job is to: - -1. Read the issue silently using GitHub read tools -2. Post **one** triage summary comment using `add_comment` - -That is the entire workflow. Do NOT call `add_comment` more than once. -Do NOT call `add_labels`. Do NOT apply any labels. +Your job is to post **at most one** triage summary comment per workflow run +using `add_comment`. + +This workflow runs in three situations. Identify which one **before** doing +any work, then follow the matching flow: + +1. **Initial triage** — `event_name == "issues"`. A new issue was just opened. + Always proceed to the triage instructions below. +2. **Follow-up triage** — `event_name == "issue_comment"` and the comment body + does NOT start with `/triage`. The workflow-level `if:` has already verified + the issue currently carries the label `Auto-Triage: Waiting for Author`, + so a prior triage flagged missing env info and the author has now responded. + Treat later author comments as part of the issue body and re-validate the + environment. There are three sub-cases routed by "Follow-up routing" under + Instructions: + - **No progress** (comment supplied no new env field, e.g. "will share + soon") → silent `noop`, label stays, no comment posted. + - **Partial progress** (comment supplied at least one new env field but + others are still missing) → post a fresh summary acknowledging what + was provided and re-asking for the rest, KEEP the label. + - **Complete** (all required env fields are now present) → post a fresh + summary, REMOVE the label. +3. **On-demand triage** — `event_name == "issue_comment"` and the comment body + starts with `/triage`. A maintainer is explicitly requesting a fresh triage. + Ignore label state and prior summary counts; proceed to the triage + instructions and produce a new summary. Do NOT change the label as part of + `/triage` runs (leave it as-is). + +Do NOT call `add_comment` more than once per run. +Do NOT call `add_labels` or `remove_labels` for any label other than +`Auto-Triage: Waiting for Author` — that single label is the only one this +workflow is permitted to manage. Do NOT post intermediate findings. Do NOT post separate comments for area detection, duplicate checking, or environment validation. Everything goes into the single triage summary at the end. --- +## Label-managed state + +The workflow uses one internal-state label to decide cheaply (at the YAML +`if:` level) whether an author comment should boot the agent at all: + +- **`Auto-Triage: Waiting for Author`** — present iff the most recent + triage summary flagged `⚠️ Missing:` or `⚠️ Partial:` environment fields + and we are waiting for the issue author to supply them. + +The agent (you) is responsible for keeping this label accurate — see the +"Actions" section below for exactly when to call `add_labels` / +`remove_labels`. + +Only if the workflow-level `if:` gate above evaluated to true, proceed to the +triage instructions below and produce a fresh summary. Treat the prior summary +as **invalidated** — the new one supersedes it (the older one will be collapsed +automatically by `hide-older-comments`). + +--- + ## Required Context Before analyzing the issue, you MUST read all project knowledge base files @@ -52,8 +149,51 @@ environment validation, and analysis. Do not skip this step. ## Instructions -Read the issue body. Then do ALL of the following analysis silently -(using read tools and search only — no comments, no outputs): +Read the issue body **and, for follow-up / on-demand runs, every subsequent +comment**. Then do ALL of the following analysis silently (using read tools +and search only — no comments, no outputs): + +### Follow-up routing (scenario 2 only) + +Before running the full analysis on a follow-up run, decide which of three +sub-cases this comment falls into. Use the rules in step **B** below to +determine which environment fields each source supplies. + +Compute two snapshots: + +- **BEFORE** = env fields supplied by the issue body + every author comment + EXCEPT the triggering comment. +- **AFTER** = env fields supplied by the issue body + every author comment + INCLUDING the triggering comment. + +Then route as follows: + +1. **No progress** — `AFTER == BEFORE` (the new comment did not supply any + new env field; e.g. "okay, will share details soon", a question, an + unrelated remark). → Call `noop` with a short reason like `"Author + commented but supplied no new env info"` and STOP. Do NOT call + `add_comment`. Do NOT change the label. The label stays so the next + author comment can re-trigger this workflow. + +2. **Partial progress** — `AFTER` adds at least one new env field but is + still incomplete (some required fields are still missing). → Proceed + to the full analysis below and post a fresh triage summary. The + `Environment` row MUST acknowledge what was just provided and list + only the fields that are STILL missing, e.g. + `⚠️ Partial: received SqlClient version and OS; still missing: .NET TFM, SQL Server version`. + Keep the label `Auto-Triage: Waiting for Author` on the issue (i.e. call + `add_labels` with that label — it is a no-op if already present). + +3. **Complete** — `AFTER` contains every required env field. → Proceed to + the full analysis below and post a fresh triage summary. The + `Environment` row says `All required environment details provided for + investigation`. Call `remove_labels` with the label. + +This routing does NOT apply to initial triage (scenario 1) or on-demand +`/triage` (scenario 3) — those always produce a fresh summary using the +standard label-management rules in the Actions section below. + +### Full analysis **A. Classify issue type**: Bug (reports unexpected behavior, crash, regression, or incorrect results), Feature (has proposal), Question, or Task. @@ -61,6 +201,8 @@ Read the issue body. Then do ALL of the following analysis silently SqlClient version, .NET target framework, SQL Server version, OS, repro steps, expected vs actual behavior. If any are missing, list them explicitly in the triage summary (e.g. "Missing: SQL Server version, OS"). +For follow-up runs, treat information supplied in any later comment by the +issue author as if it were part of the original issue body. Proceed with all remaining triage steps regardless of missing environment details. **C. Classify area**: Based on the issue content, pick the single best matching area label from this list: @@ -90,7 +232,9 @@ Proceed with all remaining triage steps regardless of missing environment detail ## Actions -Call `add_comment` exactly **once** with this markdown: +Call `add_comment` exactly **once** with this markdown. For follow-up runs +add "(updated after author response)" to the heading; for on-demand `/triage` +runs add "(on-demand re-triage)" to the heading: ``` ## 🔍 Triage Summary @@ -98,7 +242,7 @@ Call `add_comment` exactly **once** with this markdown: | Check | Result | |-------|--------| | Issue type | | -| Environment | | +| Environment | ; still missing: / ⚠️ Missing: list specific fields> | | Area | | | Duplicates | | | Regression | | @@ -124,4 +268,15 @@ and severity assessment (P0-P3)> > **Note**: This triage summary is auto-generated by an AI agent. The analysis and suggestions above have not been verified by a human maintainer. Please treat as preliminary guidance only. ``` +**Then manage the label**: + +- For **on-demand `/triage` runs**, do NOT touch the label — preserve + whatever state existed before. +- For **initial triage** and **follow-up triage**, base the decision on the + `Environment` row of the summary you just posted: + - If it contains `⚠️ Missing:` or `⚠️ Partial:` → call `add_labels` with + `["Auto-Triage: Waiting for Author"]` (no-op if already present). + - Otherwise (env complete) → call `remove_labels` with + `["Auto-Triage: Waiting for Author"]` (safe to call even if absent). + If the issue is spam or no action is needed, call the `noop` tool instead. \ No newline at end of file From b6c05695a054d1cdd846dfc1774a6df17ea6656f Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:48:33 -0300 Subject: [PATCH 144/298] Add TDS token data length bounds checks (#4340) --- .../Connection/SqlConnectionInternal.cs | 7 +- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 4 + .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 11 + .../src/Microsoft/Data/SqlClient/TdsParser.cs | 57 +- .../ConnectionEnhancedRoutingTests.cs | 2 + .../ConnectionFailoverTests.cs | 2 + .../ConnectionReadOnlyRoutingTests.cs | 8 +- .../ConnectionRoutingTests.cs | 4 +- .../ConnectionRoutingTestsAzure.cs | 8 +- .../FeatureExtAckBoundsTests.cs | 163 +++ .../FeatureExtensionNegotiationTests.cs | 2 + .../TdsTokenBoundsTests.cs | 1051 +++++++++++++++++ .../tools/TDS/TDS.Servers/GenericTdsServer.cs | 10 + 13 files changed, 1313 insertions(+), 16 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/FeatureExtAckBoundsTests.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/TdsTokenBoundsTests.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 295286b1e5..3d67df236e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -400,7 +400,7 @@ internal SqlConnectionInternal( try { - // If we want to consider pool operations against the overall connect timeout, + // If we want to consider pool operations against the overall connect timeout, // use the provided timeout. Otherwise, start a fresh timeout to receive the full // connect timeout. _timeout = ResolveLoginTimeout(timeout, connectionOptions.ConnectTimeout); @@ -1346,6 +1346,11 @@ internal void OnFeatureExtAck(int featureId, byte[] data) len = bLen; } + if (len < 0 || len > data.Length - i) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, len); + } + byte[] stateData = new byte[len]; Buffer.BlockCopy(data, i, stateData, 0, len); i += len; 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 96312a491d..84667d99e7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -731,6 +731,10 @@ internal static Exception ParsingErrorLength(ParsingErrorState state, int length { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ParsingErrorLength, ((int)state).ToString(CultureInfo.InvariantCulture), length)); } + internal static Exception ParsingErrorLength(ParsingErrorState state, uint length) + { + return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ParsingErrorLength, ((int)state).ToString(CultureInfo.InvariantCulture), length)); + } internal static Exception ParsingErrorStatus(ParsingErrorState state, int status) { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ParsingErrorStatus, ((int)state).ToString(CultureInfo.InvariantCulture), status)); 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 25a489d91d..9b71afc350 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -84,6 +84,17 @@ internal static class TdsEnums public const int MAX_PACKET_SIZE = 32768; public const int MAX_SERVER_USER_NAME = 256; // obtained from luxor + // Maximum allowed data length for token payloads (feature ext ack, + // session state, fedauth info). Prevents a malicious server from causing + // unbounded memory allocation via spoofed token length fields. + internal const int MaxTokenDataLength = 1 << 20; // 1 MB + + // Maximum allowed data length for a DTC promote transaction propagation token. + internal const int MaxPromoteTransactionLength = 1 << 16; // 64 KB + + // Maximum valid wire size for datetime types (DateTimeOffset = 5 time + 3 date + 2 offset). + internal const int MaxDateTimeLength = 10; + // Severity 0 - 10 indicates informational (non-error) messages // Severity 11 - 16 indicates errors that can be corrected by user (syntax errors, etc...) // Severity 17 - 19 indicates failure due to insufficient resources in the server 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 bb625d563d..79c725c074 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -3348,6 +3348,10 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb // new value has 4 byte length return result; } + if (env._newLength < 0 || env._newLength > TdsEnums.MaxPromoteTransactionLength) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, env._newLength); + } // read new value with 4 byte length env._newBinValue = new byte[env._newLength]; result = stateObj.TryReadByteArray(env._newBinValue, env._newLength); @@ -3846,10 +3850,15 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj { return result; } - byte[] data = new byte[dataLen]; - if (dataLen > 0) + if (dataLen > (uint)TdsEnums.MaxTokenDataLength) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, dataLen); + } + int dataLength = (int)dataLen; + byte[] data = new byte[dataLength]; + if (dataLength > 0) { - result = stateObj.TryReadByteArray(data, checked((int)dataLen)); + result = stateObj.TryReadByteArray(data, dataLength); if (result != TdsOperationStatus.Done) { return result; @@ -4169,6 +4178,10 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj, { throw SQL.ParsingErrorLength(ParsingErrorState.SessionStateLengthTooShort, length); } + if (length > TdsEnums.MaxTokenDataLength) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length); + } uint seqNum; TdsOperationStatus result = stateObj.TryReadUInt32(out seqNum); if (result != TdsOperationStatus.Done) @@ -4218,6 +4231,10 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj, return result; } } + if (stateLen < 0 || stateLen > TdsEnums.MaxTokenDataLength) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, stateLen); + } byte[] buffer = null; lock (sdata._delta) { @@ -4435,6 +4452,10 @@ private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj, SqlClientEventSource.Log.TryTraceEvent(" FEDAUTHINFO token stream length too short for CountOfInfoIDs."); throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForCountOfInfoIds, tokenLen); } + if (tokenLen > TdsEnums.MaxTokenDataLength) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, tokenLen); + } // read how many FedAuthInfo options there are uint optionsCount; @@ -4912,14 +4933,20 @@ internal TdsOperationStatus TryProcessReturnValue(int length, } // always read as sql types - Debug.Assert(valLen < (ulong)(int.MaxValue), "ProcessReturnValue received data size > 2Gb"); - - int intlen = valLen > (ulong)(int.MaxValue) ? int.MaxValue : (int)valLen; + int intlen; if (rec.metaType.IsPlp) { intlen = int.MaxValue; // If plp data, read it all } + else if (valLen > (ulong)int.MaxValue) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, unchecked((int)valLen)); + } + else + { + intlen = (int)valLen; + } if (rec.type == SqlDbTypeExtensions.Vector) { @@ -7111,9 +7138,20 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, return TdsOperationStatus.Done; } + // length originates as a single byte on the wire (nullable datetime length prefix), + // but is kept as int to match the TDS parsing API surface where all lengths are int. + // Using byte here would require casts at all call sites and silently truncate values + // from the sql_variant path where lenData is computed arithmetically. private TdsOperationStatus TryReadSqlDateTime(SqlBuffer value, byte tdsType, int length, byte scale, TdsParserStateObject stateObj) { - Span datetimeBuffer = ((uint)length <= 16) ? stackalloc byte[16] : new byte[length]; + // DateTimeOffset is the largest datetime type at 10 bytes (5 time + 3 date + 2 offset). + // Reject anything larger to prevent heap allocation from spoofed metadata. + if (length < 0 || length > TdsEnums.MaxDateTimeLength) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length); + } + + Span datetimeBuffer = stackalloc byte[TdsEnums.MaxDateTimeLength]; TdsOperationStatus result = stateObj.TryReadByteArray(datetimeBuffer, length); if (result != TdsOperationStatus.Done) @@ -7369,7 +7407,10 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp case TdsEnums.SQLVECTOR: { // Note: Better not come here with plp data!! - Debug.Assert(length <= TdsEnums.MAXSIZE); + if (length < 0 || length > TdsEnums.MAXSIZE) + { + throw SQL.ParsingErrorLength(ParsingErrorState.CorruptedTdsStream, length); + } result = stateObj.TryReadByteArrayWithContinue(length, isPlp: false, out byte[] b); if (result != TdsOperationStatus.Done) { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionEnhancedRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionEnhancedRoutingTests.cs index 7d8941a07d..c85365d98e 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionEnhancedRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionEnhancedRoutingTests.cs @@ -14,6 +14,8 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests; /// /// Tests connection routing using the enhanced routing feature extension and envchange token /// +// TODO: Do we need this collection? It serializes all tests within it, which we probably don't +// need since each test uses its own TDS Server with ephemeral listen port. [Collection("SimulatedServerTests")] public class ConnectionEnhancedRoutingTests { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index 29bb6cb6e5..566d3d0cef 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -12,6 +12,8 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { + // TODO: Do we need this collection? It serializes all tests within it, which we probably don't + // need since each test uses its own TDS Server with ephemeral listen port. [Collection("SimulatedServerTests")] public class ConnectionFailoverTests { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionReadOnlyRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionReadOnlyRoutingTests.cs index f0618ac269..8220871f22 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionReadOnlyRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionReadOnlyRoutingTests.cs @@ -11,6 +11,8 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { + // TODO: Do we need this collection? It serializes all tests within it, which we probably don't + // need since each test uses its own TDS Server with ephemeral listen port. [Collection("SimulatedServerTests")] public class ConnectionReadOnlyRoutingTests { @@ -71,7 +73,7 @@ public void RecursivelyRoutedConnection(int layers) router.Start(); routingLayers.Push(router); lastEndpoint = router.EndPoint; - lastConnectionString = (new SqlConnectionStringBuilder() { + lastConnectionString = (new SqlConnectionStringBuilder() { DataSource = $"localhost,{lastEndpoint.Port}", ApplicationIntent = ApplicationIntent.ReadOnly, Encrypt = false @@ -114,8 +116,8 @@ public async Task RecursivelyRoutedAsyncConnection(int layers) router.Start(); routingLayers.Push(router); lastEndpoint = router.EndPoint; - lastConnectionString = (new SqlConnectionStringBuilder() { - DataSource = $"localhost,{lastEndpoint.Port}", + lastConnectionString = (new SqlConnectionStringBuilder() { + DataSource = $"localhost,{lastEndpoint.Port}", ApplicationIntent = ApplicationIntent.ReadOnly, Encrypt = false }).ConnectionString; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs index 0eff08139f..3065f780bc 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs @@ -10,6 +10,8 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { + // TODO: Do we need this collection? It serializes all tests within it, which we probably don't + // need since each test uses its own TDS Server with ephemeral listen port. [Collection("SimulatedServerTests")] public class ConnectionRoutingTests { @@ -194,7 +196,7 @@ public void NetworkTimeoutAtRoutedLocation_RetryDisabled_ShouldFail() // Act var e = Assert.Throws(connection.Open); - // Assert + // Assert Assert.Equal(ConnectionState.Closed, connection.State); Assert.Contains("Connection Timeout Expired", e.Message); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs index 3cd2628300..642d54f5b2 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs @@ -10,6 +10,8 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { + // TODO: Do we need this collection? It serializes all tests within it, which we probably don't + // need since each test uses its own TDS Server with ephemeral listen port. [Collection("SimulatedServerTests")] public class ConnectionRoutingTestsAzure : IDisposable { @@ -21,8 +23,8 @@ public ConnectionRoutingTestsAzure() adpHelper.AddAzureSqlServerEndpoint("localhost"); } - public void Dispose() - { + public void Dispose() + { adpHelper.Dispose(); } @@ -192,7 +194,7 @@ public void NetworkTimeoutAtRoutedLocation_RetryDisabled_ShouldFail() // Act var e = Assert.Throws(connection.Open); - // Assert + // Assert Assert.Equal(ConnectionState.Closed, connection.State); Assert.Contains("Connection Timeout Expired", e.Message); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/FeatureExtAckBoundsTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/FeatureExtAckBoundsTests.cs new file mode 100644 index 0000000000..20c94c0361 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/FeatureExtAckBoundsTests.cs @@ -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. + +using System; +using System.IO; +using System.Linq; +using Microsoft.SqlServer.TDS; +using Microsoft.SqlServer.TDS.FeatureExtAck; +using Microsoft.SqlServer.TDS.Servers; +using TDSDoneToken = global::Microsoft.SqlServer.TDS.Done.TDSDoneToken; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests; + +/// +/// Tests that the TDS parser rejects feature extension acknowledgment tokens +/// with data lengths exceeding protocol-reasonable bounds. This prevents a +/// malicious server from causing unbounded memory allocation on the client. +/// +// TODO: Do we need this collection? It serializes all tests within it, which we probably don't +// need since each test uses its own TDS Server with ephemeral listen port. +[Collection("SimulatedServerTests")] +public class FeatureExtAckBoundsTests : IDisposable +{ + private readonly TdsServerFixture _fixture; + private readonly TdsServer _server; + private readonly string _connectionString; + + public FeatureExtAckBoundsTests() + { + _fixture = new TdsServerFixture(); + _server = _fixture.TdsServer; + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{_server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false + }; + _connectionString = builder.ConnectionString; + } + + public void Dispose() => _fixture.Dispose(); + + /// + /// Verifies that the TDS parser rejects a FeatureExtAck token whose data length + /// field exceeds (1 MB), throwing a + /// parsing error instead of attempting an unbounded heap allocation. + /// This guards against CVE denial-of-service via pre-auth memory exhaustion. + /// + [Fact] + public void FeatureExtAck_OversizedDataLength_ThrowsParsingError() + { + // Arrange: inject a malicious FeatureExtAck token with an absurdly large data length + _server.OnAuthenticationResponseCompleted = responseMessage => + { + // Remove any existing FeatureExtAck token + var existing = responseMessage.OfType().FirstOrDefault(); + if (existing != null) + { + responseMessage.Remove(existing); + } + + // Insert a malicious token with oversized data length before the DONE token + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + responseMessage.Insert(doneIndex, new MaliciousFeatureExtAckToken( + featureId: (TDSFeatureID)TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS, + claimedDataLen: (uint)(TdsEnums.MaxTokenDataLength + 1))); + }; + + // Act & Assert: connection should fail with a parsing error, NOT an OutOfMemoryException + using SqlConnection connection = new(_connectionString); + Exception ex = Assert.ThrowsAny(connection.Open); + + // The exception message should indicate a corrupted TDS stream + // with the oversized length value, not an OOM from attempting the allocation. + Assert.Contains("Error state: 18", ex.Message); // ParsingErrorState.CorruptedTdsStream = 18 + Assert.Contains($"Length: {TdsEnums.MaxTokenDataLength + 1}", ex.Message); + } + + /// + /// Verifies that a FeatureExtAck token with a data length at exactly the + /// allowed maximum is accepted without error, confirming there is no + /// off-by-one in the bounds check. + /// + [Fact] + public void FeatureExtAck_MaxAllowedDataLength_DoesNotThrow() + { + // Arrange: inject a FeatureExtAck token whose declared data length equals + // MaxTokenDataLength exactly. The bounds check should NOT fire for this + // value. The connection will fail for other reasons (not enough data on + // the wire), but the error must NOT be state 18 (CorruptedTdsStream). + _server.OnAuthenticationResponseCompleted = responseMessage => + { + var existing = responseMessage.OfType().FirstOrDefault(); + if (existing != null) + { + responseMessage.Remove(existing); + } + + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + // Insert token with dataLen = MaxTokenDataLength (at boundary, not over) + responseMessage.Insert(doneIndex, new MaliciousFeatureExtAckToken( + featureId: (TDSFeatureID)TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS, + claimedDataLen: (uint)TdsEnums.MaxTokenDataLength)); + }; + + using SqlConnection connection = new(_connectionString); + // The connection will fail (insufficient data for the claimed length), + // but the failure must NOT be the bounds-check error. + Exception ex = Assert.ThrowsAny(connection.Open); + Assert.DoesNotContain("Error state: 18", ex.Message); + } + + /// + /// A custom TDS packet token that writes a FEATUREEXTACK token with a fraudulently + /// large data length field. This simulates a malicious server attempting to cause + /// the client to allocate an unbounded byte array. + /// + private sealed class MaliciousFeatureExtAckToken : TDSPacketToken + { + private readonly TDSFeatureID _featureId; + private readonly uint _claimedDataLen; + + public MaliciousFeatureExtAckToken(TDSFeatureID featureId, uint claimedDataLen) + { + _featureId = featureId; + _claimedDataLen = claimedDataLen; + } + + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // Write the FEATUREEXTACK token type (0xAE) + destination.WriteByte((byte)TDSTokenType.FeatureExtAck); + + // Write the feature ID byte + destination.WriteByte((byte)_featureId); + + // Write the claimed data length (uint32, little-endian) — this is the lie + byte[] lenBytes = BitConverter.GetBytes(_claimedDataLen); + destination.Write(lenBytes, 0, 4); + + // Write only 1 byte of actual data (the client will try to read _claimedDataLen bytes + // but we only provide 1 — the bounds check should fire before the read attempt) + destination.WriteByte(0x01); + + // Write terminator + destination.WriteByte((byte)TDSFeatureID.Terminator); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/FeatureExtensionNegotiationTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/FeatureExtensionNegotiationTests.cs index e6342c4fa8..0141d44000 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/FeatureExtensionNegotiationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/FeatureExtensionNegotiationTests.cs @@ -13,6 +13,8 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests; +// Serializes execution with other SimulatedServerTests classes to avoid port/resource conflicts. +// Required here because IClassFixture shares a single TdsServer instance across all tests in this class. [Collection("SimulatedServerTests")] public class FeatureExtensionNegotiationTests : IClassFixture { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/TdsTokenBoundsTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/TdsTokenBoundsTests.cs new file mode 100644 index 0000000000..ef975dccb3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/TdsTokenBoundsTests.cs @@ -0,0 +1,1051 @@ +// 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.Linq; +using Microsoft.SqlServer.TDS; +using Microsoft.SqlServer.TDS.ColMetadata; +using Microsoft.SqlServer.TDS.Done; +using Microsoft.SqlServer.TDS.FeatureExtAck; +using Microsoft.SqlServer.TDS.Servers; +using Microsoft.SqlServer.TDS.SessionState; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests; + +/// +/// Tests that the TDS parser rejects various token types with data lengths +/// exceeding protocol-reasonable bounds, preventing unbounded memory allocation +/// from a malicious server. +/// +// Serializes execution with other SimulatedServerTests classes. Required here because +// DebugAssertSuppressor mutates the global Trace.Listeners collection, which is not +// safe to do concurrently with other tests that may trigger Debug.Assert. +[Collection("SimulatedServerTests")] +public class TdsTokenBoundsTests : IDisposable +{ + private readonly TdsServerFixture _fixture; + private readonly TdsServer _server; + private readonly string _connectionString; + + public TdsTokenBoundsTests() + { + _fixture = new TdsServerFixture(); + _server = _fixture.TdsServer; + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{_server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false + }; + _connectionString = builder.ConnectionString; + } + + public void Dispose() => _fixture.Dispose(); + + // ────────────────────────────────────────────────────────────────────────── + // Test 1: SessionState token with oversized total length + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryProcessSessionState rejects a SessionState token (0xE4) + /// whose total length field exceeds (1 MB), + /// preventing unbounded memory allocation from a spoofed token length. + /// + [Fact] + public void SessionState_OversizedTotalLength_ThrowsParsingError() + { + _server.OnAuthenticationResponseCompleted = responseMessage => + { + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + // Inject a SessionState token claiming a total length exceeding MaxTokenDataLength + responseMessage.Insert(doneIndex, new MaliciousSessionStateToken( + claimedTotalLength: (uint)(TdsEnums.MaxTokenDataLength + 1))); + }; + + using SqlConnection connection = new(_connectionString); + Exception ex = Assert.ThrowsAny(connection.Open); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains($"Length: {TdsEnums.MaxTokenDataLength + 1}", ex.Message); + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 2: SessionState token with oversized inner option length + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryProcessSessionState rejects an individual session state + /// option whose inner data length (encoded via the 0xFF + DWORD path) exceeds + /// , even when the outer token length is valid. + /// + [Fact] + public void SessionState_OversizedInnerOptionLength_ThrowsParsingError() + { + _server.OnAuthenticationResponseCompleted = responseMessage => + { + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + // Inject a SessionState token with a valid outer length but an inner + // state option claiming a huge data length + responseMessage.Insert(doneIndex, new MaliciousSessionStateInnerLenToken( + innerClaimedLength: TdsEnums.MaxTokenDataLength + 1)); + }; + + using SqlConnection connection = new(_connectionString); + Exception ex = Assert.ThrowsAny(connection.Open); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains($"Length: {TdsEnums.MaxTokenDataLength + 1}", ex.Message); + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 2b: SessionState token with negative inner option length + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryProcessSessionState rejects an individual session state + /// option whose inner data length (encoded via the 0xFF + DWORD path) is negative, + /// which would be interpreted as a huge unsigned value if not bounds-checked. + /// + [Fact] + public void SessionState_NegativeInnerOptionLength_ThrowsParsingError() + { + _server.OnAuthenticationResponseCompleted = responseMessage => + { + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + // Inject a SessionState token with an inner state option claiming + // a negative data length (-1 = 0xFFFFFFFF as uint32) + responseMessage.Insert(doneIndex, new MaliciousSessionStateInnerLenToken( + innerClaimedLength: -1)); + }; + + using SqlConnection connection = new(_connectionString); + Exception ex = Assert.ThrowsAny(connection.Open); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains("Length: -1", ex.Message); + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 3: SRECOVERY feature ack with malformed inner state data + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that the secondary parse of FEATUREEXT_SRECOVERY data in + /// SqlConnectionInternal.OnFeatureExtAck rejects inner state options + /// whose claimed length exceeds the remaining buffer, preventing an + /// out-of-bounds read or over-allocation. + /// + [Fact] + public void SRecovery_MalformedInnerStateLength_ThrowsParsingError() + { + _server.OnAuthenticationResponseCompleted = responseMessage => + { + // Remove existing FeatureExtAck if present + var existing = responseMessage.OfType().FirstOrDefault(); + if (existing != null) + { + responseMessage.Remove(existing); + } + + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + // Inject a FeatureExtAck with SessionRecovery feature containing + // inner state data where a state option claims a length exceeding the buffer + responseMessage.Insert(doneIndex, new MaliciousSRecoveryFeatureExtAckToken()); + }; + + using SqlConnection connection = new(_connectionString); + Exception ex = Assert.ThrowsAny(connection.Open); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains("Length: 999", ex.Message); + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 4: FedAuthInfo token with oversized total length + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryProcessFedAuthInfo rejects a FedAuthInfo token (0xEE) + /// whose total length exceeds (1 MB). + /// The token type is dispatched unconditionally by TryRun, so this check + /// fires regardless of whether federated authentication was negotiated. + /// + [Fact] + public void FedAuthInfo_OversizedTokenLength_ThrowsParsingError() + { + _server.OnAuthenticationResponseCompleted = responseMessage => + { + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + // Inject a FedAuthInfo token with a total length exceeding MaxTokenDataLength. + // The parser dispatches on token type regardless of whether FedAuth was negotiated. + responseMessage.Insert(doneIndex, new MaliciousFedAuthInfoToken( + claimedLength: TdsEnums.MaxTokenDataLength + 1)); + }; + + using SqlConnection connection = new(_connectionString); + Exception ex = Assert.ThrowsAny(connection.Open); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains($"Length: {TdsEnums.MaxTokenDataLength + 1}", ex.Message); + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 5: ENV_PROMOTETRANSACTION with oversized newLength + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryProcessEnvChange rejects a PromoteTransaction + /// environment change token (type 15) whose inner newLength field exceeds + /// (64 KB). A malicious server + /// can set the outer uint16 token length to a small value while writing an + /// int32 inner length claiming gigabytes, causing unbounded allocation. + /// + [Fact] + public void EnvChange_PromoteTransaction_OversizedLength_ThrowsParsingError() + { + _server.OnAuthenticationResponseCompleted = responseMessage => + { + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + // Inject a PromoteTransaction env change with a fraudulently large newLength + responseMessage.Insert(doneIndex, new MaliciousPromoteTransactionEnvChangeToken( + claimedNewLength: TdsEnums.MaxPromoteTransactionLength + 1)); + }; + + using SqlConnection connection = new(_connectionString); + Exception ex = Assert.ThrowsAny(connection.Open); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains($"Length: {TdsEnums.MaxPromoteTransactionLength + 1}", ex.Message); + } + + // ══════════════════════════════════════════════════════════════════════════ + // Malicious token helpers + // ══════════════════════════════════════════════════════════════════════════ + + /// + /// Writes a SessionState token (0xE4) with a fraudulently large total length. + /// Wire format: [0xE4][uint32 totalLen][uint32 seqNum][byte status][...] + /// The bounds check fires on the totalLen value before any data is read. + /// + private sealed class MaliciousSessionStateToken : TDSPacketToken + { + private readonly uint _claimedTotalLength; + + public MaliciousSessionStateToken(uint claimedTotalLength) + { + _claimedTotalLength = claimedTotalLength; + } + + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // Token type + destination.WriteByte((byte)TDSTokenType.SessionState); // 0xE4 + + // Total token length (uint32) — this is the fraudulent value + byte[] lenBytes = BitConverter.GetBytes(_claimedTotalLength); + destination.Write(lenBytes, 0, 4); + + // Write minimal valid-looking data: seqNum (4 bytes) + status (1 byte) + // The bounds check should fire before trying to process option data. + destination.Write(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 0, 4); // seqNum = 1 + destination.WriteByte(0x01); // status = recoverable + } + } + + /// + /// Writes a SessionState token (0xE4) with a valid outer length but an inner + /// state option that claims a huge data length (using the 0xFF + DWORD encoding). + /// Wire format: [0xE4][uint32 totalLen][uint32 seqNum][byte status] + /// [byte stateId][0xFF][int32 innerLen][...data...] + /// The inner bounds check fires on the innerLen value. + /// + private sealed class MaliciousSessionStateInnerLenToken : TDSPacketToken + { + private readonly int _innerClaimedLength; + + public MaliciousSessionStateInnerLenToken(int innerClaimedLength) + { + _innerClaimedLength = innerClaimedLength; + } + + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // Token type + destination.WriteByte((byte)TDSTokenType.SessionState); // 0xE4 + + // Calculate total token length: + // seqNum(4) + status(1) + stateId(1) + lenMarker(1) + innerLen(4) + minimal data(1) + uint totalLength = 4 + 1 + 1 + 1 + 4 + 1; + + byte[] lenBytes = BitConverter.GetBytes(totalLength); + destination.Write(lenBytes, 0, 4); + + // Sequence number + destination.Write(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 0, 4); + + // Status (recoverable) + destination.WriteByte(0x01); + + // State option: stateId + destination.WriteByte(0x00); // UserOptions state ID + + // Length marker: 0xFF means next 4 bytes are the DWORD length + destination.WriteByte(0xFF); + + // Inner claimed length — fraudulently large + byte[] innerLenBytes = BitConverter.GetBytes(_innerClaimedLength); + destination.Write(innerLenBytes, 0, 4); + + // Write only 1 byte of actual data + destination.WriteByte(0x42); + } + } + + /// + /// Writes a FeatureExtAck token (0xAE) with a SessionRecovery feature (ID=1) + /// that carries inner state data where a state option claims a length exceeding + /// the remaining buffer. This exercises the bounds check in + /// SqlConnectionInternal.OnFeatureExtAck for FEATUREEXT_SRECOVERY. + /// + private sealed class MaliciousSRecoveryFeatureExtAckToken : TDSPacketToken + { + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // Token type: FEATUREEXTACK + destination.WriteByte((byte)TDSTokenType.FeatureExtAck); // 0xAE + + // Feature ID: SessionRecovery = 0x01 + destination.WriteByte(0x01); + + // The feature data for SRECOVERY is parsed by SqlConnectionInternal.OnFeatureExtAck: + // It reads pairs of [stateId(1)][lenByte(1)][data(len)] or [stateId(1)][0xFF][int32 len][data(len)] + // We'll craft inner data where one option claims length > remaining buffer. + + // Inner data layout: + // stateId(1) + 0xFF marker(1) + int32 len(4) + 1 byte actual data + byte[] innerData; + using (var ms = new MemoryStream()) + { + ms.WriteByte(0x00); // stateId = 0 + + // Use 0xFF marker to indicate DWORD length + ms.WriteByte(0xFF); + + // Claim a length that exceeds the remaining buffer + // The remaining buffer after reading stateId + 0xFF + 4-byte-len will be 1 byte, + // but we claim 999 bytes + byte[] claimedLen = BitConverter.GetBytes(999); + ms.Write(claimedLen, 0, 4); + + // Only provide 1 byte of actual data + ms.WriteByte(0x42); + + innerData = ms.ToArray(); + } + + // Feature data length (uint32) + byte[] featureDataLen = BitConverter.GetBytes((uint)innerData.Length); + destination.Write(featureDataLen, 0, 4); + + // Feature data + destination.Write(innerData, 0, innerData.Length); + + // Terminator + destination.WriteByte((byte)TDSFeatureID.Terminator); // 0xFF + } + } + + /// + /// Writes a FedAuthInfo token (0xEE) with a fraudulently large total length. + /// Wire format: [0xEE][int32 tokenLen][...data...] + /// The bounds check fires on tokenLen before any data is read. + /// + private sealed class MaliciousFedAuthInfoToken : TDSPacketToken + { + private readonly int _claimedLength; + + public MaliciousFedAuthInfoToken(int claimedLength) + { + _claimedLength = claimedLength; + } + + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // Token type + destination.WriteByte(0xEE); // SQLFEDAUTHINFO + + // Token length (int32) — fraudulently large + byte[] lenBytes = BitConverter.GetBytes(_claimedLength); + destination.Write(lenBytes, 0, 4); + + // Write minimal data (at least sizeof(uint) to pass the lower bound check, + // but the upper bound check should fire first) + destination.Write(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 0, 4); // optionsCount = 1 + } + } + + /// + /// Writes an EnvChange token (0xE3) with type PromoteTransaction (15) whose + /// inner int32 newLength exceeds . + /// Wire format: [0xE3][uint16 tokenLen][byte type=15][int32 newLen][data...][byte oldLen=0] + /// The outer uint16 tokenLen is set to accommodate the header but the int32 + /// newLength claims far more data than actually follows, triggering the bounds check. + /// + private sealed class MaliciousPromoteTransactionEnvChangeToken : TDSPacketToken + { + private readonly int _claimedNewLength; + + public MaliciousPromoteTransactionEnvChangeToken(int claimedNewLength) + { + _claimedNewLength = claimedNewLength; + } + + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // Token type: ENVCHANGE + destination.WriteByte((byte)TDSTokenType.EnvironmentChange); // 0xE3 + + // Outer token length (uint16): type(1) + newLength(4) + 1 byte data + oldLength(1) + // We write just enough to contain the header fields the parser reads before + // hitting the bounds check. The parser reads: type(1) + int32 newLen(4) = 5 bytes min. + ushort outerLength = 1 + 4 + 1 + 1; // type + newLen + 1 fake byte + oldLen + byte[] outerLenBytes = BitConverter.GetBytes(outerLength); + destination.Write(outerLenBytes, 0, 2); + + // Env change type: PromoteTransaction = 15 + destination.WriteByte(15); + + // newLength (int32) — fraudulently large + byte[] newLenBytes = BitConverter.GetBytes(_claimedNewLength); + destination.Write(newLenBytes, 0, 4); + + // Write 1 byte of fake data (the bounds check fires before attempting to read this much) + destination.WriteByte(0x00); + + // Old value length (byte) = 0 + destination.WriteByte(0x00); + } + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 6: Post-login batch response injection — PromoteTransaction + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that bounds checks fire during command execution (post-login) + /// by injecting a malicious PromoteTransaction env change token into the + /// SQL batch response. This exercises the OnSQLBatchCompleted hook + /// on the simulated server and proves the same bounds check fires regardless + /// of whether the token arrives during login or command execution. + /// + [Fact] + public void BatchResponse_PromoteTransaction_OversizedLength_ThrowsParsingError() + { + _server.OnSQLBatchCompleted = responseMessage => + { + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + responseMessage.Insert(doneIndex, new MaliciousPromoteTransactionEnvChangeToken( + claimedNewLength: TdsEnums.MaxPromoteTransactionLength + 1)); + }; + + using SqlConnection connection = new(_connectionString); + connection.Open(); + + using SqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT 1"; + + Exception ex = Assert.ThrowsAny( + () => command.ExecuteNonQuery()); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains($"Length: {TdsEnums.MaxPromoteTransactionLength + 1}", ex.Message); + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 7: Post-login batch response — DateTime oversized length + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryReadSqlDateTime rejects a TIME column value whose + /// data length byte exceeds the maximum datetime wire size (10 bytes). A + /// malicious server could set this length to a large value, causing the + /// parser to attempt an unbounded heap allocation. + /// + [Fact] + public void BatchResponse_DateTime_OversizedLength_ThrowsParsingError() + { + _server.OnSQLBatchCompleted = responseMessage => + { + // Replace the entire response with a crafted result set containing + // a TIME column whose ROW data length exceeds the maximum. + responseMessage.Clear(); + + // Use proper library tokens for COLMETADATA so framing is correct + var metadata = new TDSColMetadataToken(); + var col = new TDSColumnData(); + col.DataType = TDSDataType.TimeN; + col.DataTypeSpecific = (byte)7; // scale = 7 + col.Flags.IsNullable = true; + col.Name = string.Empty; + metadata.Columns.Add(col); + responseMessage.Add(metadata); + + // Add a malicious ROW token with oversized data length + responseMessage.Add(new MaliciousTimeRowToken()); + + // Add DONE token + responseMessage.Add(new TDSDoneToken(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Count, TDSDoneTokenCommandType.Select, 1)); + }; + + using SqlConnection connection = new(_connectionString); + connection.Open(); + + using SqlCommand command = connection.CreateCommand(); + command.CommandText = "MALICIOUS_QUERY_NOT_RECOGNIZED"; + + SqlDataReader reader = command.ExecuteReader(); + try + { + // Read() returns true (data is ready) but doesn't actually parse the + // column values — that happens on GetValue/GetTimeSpan. Force the read. + Assert.True(reader.Read()); + Exception ex = Assert.ThrowsAny( + () => reader.GetValue(0)); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains("Length: 11", ex.Message); + } + finally + { + // Disposing the reader after a corrupted stream causes the driver to + // attempt further TDS parsing during teardown, which can trip unrelated + // Debug.Assert calls in TdsParser. + using (new DebugAssertSuppressor()) + { + try { reader.Dispose(); } catch { } + } + } + } + + /// + /// Writes a ROW token (0xD1) with a single TimeN column whose data length + /// byte is set to 11 (exceeding the maximum datetime wire size of 10 bytes). + /// + private sealed class MaliciousTimeRowToken : TDSPacketToken + { + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // ROW token type + destination.WriteByte(0xD1); + + // TimeN data: length prefix (1 byte) = 11 (INVALID — max for time is 5, max datetime overall is 10) + destination.WriteByte(11); + + // Write 11 bytes of dummy data + destination.Write(new byte[11], 0, 11); + } + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 8: Post-login batch response — ReturnValue with oversized data length + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryProcessReturnValue rejects a RETURNVALUE token (0xAC) + /// whose inner data length (for a non-PLP IMAGE type) exceeds int.MaxValue. + /// A malicious server can craft a TEXT/IMAGE return value with a spoofed int32 + /// data length that becomes a huge value when cast to ulong, triggering unbounded + /// allocation. + /// + [Fact] + public void BatchResponse_ReturnValue_OversizedLength_ThrowsParsingError() + { + _server.OnSQLBatchCompleted = responseMessage => + { + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + responseMessage.Insert(doneIndex, new MaliciousReturnValueToken()); + }; + + using SqlConnection connection = new(_connectionString); + connection.Open(); + + using SqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT 1"; + + // The malicious RETURNVALUE token corrupts parser state before the + // exception propagates. Suppress Debug.Assert calls that fire in + // TdsParser during error handling and connection teardown. + using (new DebugAssertSuppressor()) + { + Exception ex = Assert.ThrowsAny( + () => command.ExecuteNonQuery()); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains("Length: -1", ex.Message); + } + } + + /// + /// Writes a RETURNVALUE token (0xAC) with an IMAGE (0x22) type whose data + /// length field is set to -1 (0xFFFFFFFF). When cast to ulong this exceeds + /// int.MaxValue, triggering the bounds check in TryProcessReturnValue. + /// Wire layout: + /// [0xAC] token + /// [uint16] ordinal + /// [byte] param name length (0) + /// [byte] status + /// [uint32] user type + /// [byte] flags1 + /// [byte] flags2 + /// [byte] tds type = 0x22 (IMAGE) + /// [int32] max length + /// [byte] textPtrLen = 16 + /// [16 bytes] textPtr + /// [8 bytes] timestamp + /// [int32] data length = -1 (INVALID) + /// + private sealed class MaliciousReturnValueToken : TDSPacketToken + { + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // RETURNVALUE token + destination.WriteByte(0xAC); + + // Ordinal (uint16 LE) + destination.WriteByte(0x00); + destination.WriteByte(0x00); + + // Param name length (byte) = 0 + destination.WriteByte(0x00); + + // Status (byte) = 0x01 (output parameter) + destination.WriteByte(0x01); + + // UserType (uint32 LE) = 0 + destination.Write(new byte[4], 0, 4); + + // Flags byte 1 (ignored) + destination.WriteByte(0x00); + + // Flags byte 2 + destination.WriteByte(0x00); + + // TDS type = SQLIMAGE (0x22) + destination.WriteByte(0x22); + + // MaxLen (int32 LE) — for IMAGE this is read via TryGetTokenLength + // which for 0x22 reads int32. Value doesn't matter much, just needs + // to be valid for MetaType lookup. + destination.Write(new byte[] { 0x10, 0x00, 0x00, 0x00 }, 0, 4); // 16 + + // -- TryProcessColumnHeaderNoNBC: IsLong && !IsPlp path -- + // TextPtr length (byte) = 16 + destination.WriteByte(0x10); + + // TextPtr data (16 bytes) + destination.Write(new byte[16], 0, 16); + + // Timestamp (8 bytes) + destination.Write(new byte[8], 0, 8); + + // Data length (int32 LE) = -1 (0xFFFFFFFF) + // (ulong)(-1) = 0xFFFFFFFFFFFFFFFF > int.MaxValue → triggers bounds check + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + } + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 9: Post-login batch response — PLP ReturnValue (regression guard) + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryProcessReturnValue correctly handles PLP + /// (Partially Length-Prefixed) return values without triggering the bounds + /// check. PLP types use the unknown-length sentinel (0xFFFFFFFFFFFFFFFE) + /// which must NOT be rejected by the non-PLP bounds check. + /// + [Fact] + public void BatchResponse_ReturnValue_PlpUnknownLength_Succeeds() + { + _server.OnSQLBatchCompleted = responseMessage => + { + int doneIndex = responseMessage.FindIndex(t => t is TDSDoneToken); + if (doneIndex < 0) + { + doneIndex = responseMessage.Count; + } + + responseMessage.Insert(doneIndex, new ValidPlpReturnValueToken()); + }; + + using SqlConnection connection = new(_connectionString); + connection.Open(); + + using SqlCommand command = connection.CreateCommand(); + command.CommandText = "SELECT 1"; + + // Should NOT throw — PLP unknown-length sentinel is valid + command.ExecuteNonQuery(); + } + + /// + /// Writes a valid RETURNVALUE token (0xAC) with a PLP VARBINARY(MAX) type + /// using the unknown-length sentinel (0xFFFFFFFFFFFFFFFE) followed by an + /// immediate PLP terminator (chunk length = 0). This exercises the IsPlp + /// branch in TryProcessReturnValue and must NOT trigger the bounds check. + /// Wire layout: + /// [0xAC] token + /// [uint16] ordinal + /// [byte] param name length (0) + /// [byte] status + /// [uint32] user type + /// [byte] flags1 + /// [byte] flags2 + /// [byte] tds type = 0xA5 (BIGVARBINARY) + /// [uint16] max length = 0xFFFF (PLP marker) + /// [uint64] PLP length = 0xFFFFFFFFFFFFFFFE (unknown) + /// [uint32] chunk length = 0 (terminator) + /// + private sealed class ValidPlpReturnValueToken : TDSPacketToken + { + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // RETURNVALUE token + destination.WriteByte(0xAC); + + // Ordinal (uint16 LE) + destination.WriteByte(0x00); + destination.WriteByte(0x00); + + // Param name length (byte) = 0 + destination.WriteByte(0x00); + + // Status (byte) = 0x01 (output parameter) + destination.WriteByte(0x01); + + // UserType (uint32 LE) = 0 + destination.Write(new byte[4], 0, 4); + + // Flags byte 1 (ignored) + destination.WriteByte(0x00); + + // Flags byte 2 + destination.WriteByte(0x00); + + // TDS type = SQLBIGVARBINARY (0xA5) + destination.WriteByte(0xA5); + + // MaxLen (uint16 LE) = 0xFFFF — PLP marker + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + + // -- TryProcessColumnHeaderNoNBC: non-IsLong path -- + // TryGetDataLength → TryReadPlpLength: + // reads uint64 = 0xFFFFFFFFFFFFFFFE (unknown length sentinel) + destination.WriteByte(0xFE); + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + destination.WriteByte(0xFF); + + // PLP chunk terminator (uint32 = 0) — empty data + destination.Write(new byte[4], 0, 4); + } + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 10: Post-login batch response — sql_variant with oversized binary + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryReadSqlValueInternal rejects a binary value inside + /// a sql_variant column whose inner data length exceeds + /// (8000 bytes). The bounds check in the + /// sql_variant deserialization path prevents unbounded heap allocation. + /// + [Fact] + public void BatchResponse_SqlVariantBinary_OversizedLength_ThrowsParsingError() + { + _server.OnSQLBatchCompleted = responseMessage => + { + responseMessage.Clear(); + + // COLMETADATA: one SSVariant column + var metadata = new TDSColMetadataToken(); + var col = new TDSColumnData(); + col.DataType = TDSDataType.SSVariant; + col.DataTypeSpecific = (uint)8009; // max length for SSVariant + col.Flags.IsNullable = true; + col.Name = string.Empty; + metadata.Columns.Add(col); + responseMessage.Add(metadata); + + // ROW with a sql_variant containing oversized binary data + responseMessage.Add(new MaliciousSqlVariantBinaryRowToken()); + + // DONE + responseMessage.Add(new TDSDoneToken(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Count, TDSDoneTokenCommandType.Select, 1)); + }; + + using SqlConnection connection = new(_connectionString); + connection.Open(); + + using SqlCommand command = connection.CreateCommand(); + command.CommandText = "MALICIOUS_QUERY_NOT_RECOGNIZED"; + + SqlDataReader reader = command.ExecuteReader(); + try + { + Assert.True(reader.Read()); + Exception ex = Assert.ThrowsAny( + () => reader.GetValue(0)); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains("Length: 8001", ex.Message); + } + finally + { + // Disposing the reader after a corrupted stream causes the driver to + // attempt further TDS parsing during teardown, which can trip unrelated + // Debug.Assert calls in TdsParser. + using (new DebugAssertSuppressor()) + { + try { reader.Dispose(); } catch { } + } + } + } + + // ────────────────────────────────────────────────────────────────────────── + // Test 11: Post-login batch response — sql_variant with negative inner length + // ────────────────────────────────────────────────────────────────────────── + + /// + /// Verifies that TryReadSqlValueInternal rejects a sql_variant binary + /// value whose declared total length is too small for the type overhead, + /// causing the computed inner data length to be negative. The bounds check + /// if (length < 0 || length > TdsEnums.MAXSIZE) catches this. + /// + [Fact] + public void BatchResponse_SqlVariantBinary_NegativeLength_ThrowsParsingError() + { + _server.OnSQLBatchCompleted = responseMessage => + { + responseMessage.Clear(); + + // COLMETADATA: one SSVariant column + var metadata = new TDSColMetadataToken(); + var col = new TDSColumnData(); + col.DataType = TDSDataType.SSVariant; + col.DataTypeSpecific = (uint)8009; // max length for SSVariant + col.Flags.IsNullable = true; + col.Name = string.Empty; + metadata.Columns.Add(col); + responseMessage.Add(metadata); + + // ROW with a sql_variant claiming a total length too small for its overhead + responseMessage.Add(new NegativeLenSqlVariantBinaryRowToken()); + + // DONE + responseMessage.Add(new TDSDoneToken(TDSDoneTokenStatusType.Final | TDSDoneTokenStatusType.Count, TDSDoneTokenCommandType.Select, 1)); + }; + + using SqlConnection connection = new(_connectionString); + connection.Open(); + + using SqlCommand command = connection.CreateCommand(); + command.CommandText = "MALICIOUS_QUERY_NOT_RECOGNIZED"; + + SqlDataReader reader = command.ExecuteReader(); + try + { + Assert.True(reader.Read()); + Exception ex = Assert.ThrowsAny( + () => reader.GetValue(0)); + Assert.Contains("Error state: 18", ex.Message); // CorruptedTdsStream + Assert.Contains("Length: -1", ex.Message); + } + finally + { + // Disposing the reader after a corrupted stream causes the driver to + // attempt further TDS parsing during teardown, which can trip unrelated + // Debug.Assert calls in TdsParser. + using (new DebugAssertSuppressor()) + { + try { reader.Dispose(); } catch { } + } + } + } + + /// + /// Writes a ROW token (0xD1) with a single SSVariant column containing a + /// BigVarBinary variant whose inner data length exceeds MAXSIZE (8000). + /// Wire layout for the variant: + /// [int32] total variant length = 8005 + /// [byte] inner type = 0xA5 (BigVarBinary) + /// [byte] cbPropBytes = 2 + /// [ushort] maxLen (property) = 8001 + /// [8001 bytes would be data, but we only write 4 to trigger the check] + /// lenData = 8005 - 2(SQLVARIANT_SIZE) - 2(cbProps) = 8001 > MAXSIZE → throws + /// + private sealed class MaliciousSqlVariantBinaryRowToken : TDSPacketToken + { + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // ROW token type + destination.WriteByte(0xD1); + + // SSVariant column data: total length (int32 LE) + // lenData = totalLength - SQLVARIANT_SIZE(2) - cbPropBytes(2) = totalLength - 4 + // We want lenData = 8001, so totalLength = 8005 + int totalLength = 8005; + byte[] lenBytes = BitConverter.GetBytes(totalLength); + destination.Write(lenBytes, 0, 4); + + // Inner type: BigVarBinary = 0xA5 + destination.WriteByte(0xA5); + + // cbPropBytes = 2 + destination.WriteByte(0x02); + + // Properties: maxLen (ushort) = 8001 + destination.WriteByte(0x41); // 8001 & 0xFF = 0x41 + destination.WriteByte(0x1F); // 8001 >> 8 = 0x1F + + // Write 4 bytes of dummy data (bounds check fires before trying to read 8001) + destination.Write(new byte[4], 0, 4); + } + } + + /// + /// Writes a ROW token (0xD1) with a single SSVariant column containing a + /// BigVarBinary variant whose total length (3) is too small to cover the + /// type overhead (SQLVARIANT_SIZE=2 + cbPropBytes=2 = 4), producing a + /// negative computed inner data length: lenData = 3 - 4 = -1. + /// + private sealed class NegativeLenSqlVariantBinaryRowToken : TDSPacketToken + { + public override bool Inflate(Stream source) => throw new NotSupportedException(); + + public override void Deflate(Stream destination) + { + // ROW token type + destination.WriteByte(0xD1); + + // SSVariant column data: total length (int32 LE) + // lenConsumed = SQLVARIANT_SIZE(2) + cbPropBytes(2) = 4 + // lenData = totalLength - lenConsumed = 3 - 4 = -1 → triggers < 0 check + int totalLength = 3; + byte[] lenBytes = BitConverter.GetBytes(totalLength); + destination.Write(lenBytes, 0, 4); + + // Inner type: BigVarBinary = 0xA5 + destination.WriteByte(0xA5); + + // cbPropBytes = 2 + destination.WriteByte(0x02); + + // Properties: maxLen (ushort) = 100 (arbitrary, just need 2 bytes) + destination.WriteByte(0x64); // 100 & 0xFF + destination.WriteByte(0x00); // 100 >> 8 + + // No data bytes — the bounds check fires before attempting to read + } + } + + /// + /// Temporarily suppresses Debug.Assert failures by clearing trace listeners. Used when + /// disposing resources after intentionally corrupting a TDS stream. A static lock serializes + /// access for the lifetime of the instance because Trace.Listeners is a global collection. + /// + private sealed class DebugAssertSuppressor : IDisposable + { + private static readonly object s_listenerLock = new(); + private readonly System.Diagnostics.TraceListener[] _listeners; + + public DebugAssertSuppressor() + { + System.Threading.Monitor.Enter(s_listenerLock); + try + { + _listeners = new System.Diagnostics.TraceListener[System.Diagnostics.Trace.Listeners.Count]; + System.Diagnostics.Trace.Listeners.CopyTo(_listeners, 0); + System.Diagnostics.Trace.Listeners.Clear(); + } + catch + { + System.Threading.Monitor.Exit(s_listenerLock); + throw; + } + } + + public void Dispose() + { + try + { + System.Diagnostics.Trace.Listeners.Clear(); + System.Diagnostics.Trace.Listeners.AddRange(_listeners); + } + finally + { + System.Threading.Monitor.Exit(s_listenerLock); + } + } + } +} 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 1eb8bdf8c6..c3fadc3e0a 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 @@ -156,6 +156,13 @@ public GenericTdsServer(T arguments, QueryEngine queryEngine) public OnAuthenticationCompletedDelegate OnAuthenticationResponseCompleted { private get; set; } + /// + /// Delegate invoked after a SQL batch response is prepared but before it is + /// sent to the client. Tests can use this to inject or replace tokens in the + /// response message. + /// + public Action OnSQLBatchCompleted { get; set; } + public OnLogin7ValidatedDelegate OnLogin7Validated { private get; set; } @@ -473,6 +480,9 @@ public virtual TDSMessageCollection OnSQLBatchRequest(ITDSServerSession session, session.PacketSize = (uint)Arguments.PacketSize; } + // Allow tests to modify or inject tokens into the response + OnSQLBatchCompleted?.Invoke(responseMessage[0]); + return responseMessage; } From 348f00721c5f3103b6ba6957831d4d83ccac0c9f Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 11 Jun 2026 14:33:16 -0500 Subject: [PATCH 145/298] PR Pipeline - Azure + AE (#4338) * Let's just enable azure stuff and see what breaks. * config :hammer: * this syntax is garbage * Job names. * :robot: Run AE in release mode separately, exlude AE from azure test runs. * Decompose the single step test-buildproj-job.yml file * platformDotnet * Manual test name * Disable sqlbulkcopy that fails on azure # Conflicts: # src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs * Feedback from previous PR * Flaky tests in azure. * Another flaky test. * :robot: Pipe dotnet verbosity through pipeline * jsonc changes * One more flaky test * One more more flaky test * Unflaky tests so I can hopefully get a reason why they failed. * Wire in managed identity client ID to hopefully enable AKV tests. * Use TCP connection strings on HGSVBS AKV tests * Add traces for newly flaky tests --- eng/pipelines/pr/jobs/test-buildproj-job.yml | 42 ++++-- .../pr/jobs/test-sqlclientmanual-job.yml | 100 ++++++++++---- eng/pipelines/pr/pr-pipeline.yml | 17 +++ eng/pipelines/pr/stages/pack-stage.yml | 7 +- eng/pipelines/pr/stages/test-stages.yml | 123 +++++++++++++----- .../pr/steps/build-buildproj-step.yml | 46 ------- eng/pipelines/pr/steps/install-dotnet.yml | 27 ---- .../pr/steps/pack-buildproj-step.yml | 2 +- .../pr/steps/test-buildproj-step.yml | 59 --------- eng/pipelines/pr/variables/pr-variables.yml | 1 + .../ManualTests/AlwaysEncrypted/AKVTests.cs | 24 +++- .../ManualTests/AlwaysEncrypted/ApiShould.cs | 12 ++ ...llFromReaderConnectionCloseOnEventAsync.cs | 3 +- .../MarsSessionPoolingTest.cs | 14 ++ .../ParameterTest/SqlVariantParameterTests.cs | 2 + 15 files changed, 269 insertions(+), 210 deletions(-) delete mode 100644 eng/pipelines/pr/steps/build-buildproj-step.yml delete mode 100644 eng/pipelines/pr/steps/install-dotnet.yml delete mode 100644 eng/pipelines/pr/steps/test-buildproj-step.yml diff --git a/eng/pipelines/pr/jobs/test-buildproj-job.yml b/eng/pipelines/pr/jobs/test-buildproj-job.yml index 0b051bf50e..ec2d292ec8 100644 --- a/eng/pipelines/pr/jobs/test-buildproj-job.yml +++ b/eng/pipelines/pr/jobs/test-buildproj-job.yml @@ -18,6 +18,17 @@ parameters: - name: buildSuffix type: string + # Dotnet CLI verbosity level. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + # Platform Parameters ==================================================== # Display name of the platform. This will be formatted into the job's official name as well as @@ -73,17 +84,26 @@ jobs: - imageOverride -equals ${{ parameters.platformImage }} steps: - # Install the version of the dotnet runtime we will use to execute the test target. - - template: /eng/pipelines/pr/steps/install-dotnet.yml@self + # Install dotnet and the runtime that will run the tests (if it is not netframework) + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: - runtimeVersion: ${{ parameters.platformDotnet }} + ${{ if not(contains(parameters.platformDotnet, 'net4')) }}: + runtimes: + - "${{ replace(parameters.platformDotnet, 'net', '') }}.x" - # Execute the test target - - template: /eng/pipelines/pr/steps/test-buildproj-step.yml - parameters: - buildConfiguration: ${{ parameters.buildConfiguration }} - buildSuffix: ${{ parameters.buildSuffix }} + # Restore dotnet tools + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self - packageShortName: ${{ parameters.packageShortName }} - testFramework: ${{ parameters.platformDotnet }} - testProject: ${{ parameters.testProject }} + # Execute the test target + - task: DotNetCoreCLI@2 + displayName: 'Test: ${{ parameters.packageShortName }}${{ parameters.testProject }}' + inputs: + command: build + projects: build.proj + verbosity: ${{ parameters.dotnetVerbosity }} + arguments: >- + -t:Test${{ parameters.packageShortName }}${{ parameters.testProject }} + -p:Configuration=${{ parameters.buildConfiguration }} + -p:BuildNumber='$(Build.BuildNumber)' + -p:BuildSuffix='${{ parameters.buildSuffix }}' + -p:TestFramework=${{ parameters.platformDotnet }} diff --git a/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml b/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml index ff81d983a3..9ca2c7c555 100644 --- a/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml +++ b/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml @@ -5,6 +5,8 @@ ################################################################################# parameters: + # General Parameters ===================================================== + # Configuration with which to build the packages and execute the tests. - name: buildConfiguration type: string @@ -16,6 +18,17 @@ parameters: - name: buildSuffix type: string + # Dotnet CLI verbosity level. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + # Name of the stage that generated the SA password - name: stageNameSecrets type: string @@ -51,39 +64,58 @@ parameters: # Test Configuration Parameters ========================================== - # Azure Key Vault tenant ID that will be set in the config.json file. + # Azure Key Vault tenant ID that will be set in the config.jsonc file. - name: azureKeyVaultTenantId type: string - # Azure Key Vault URL that will be set in the config.json file. + # Azure Key Vault URL that will be set in the config.jsonc file. - name: azureKeyVaultUrl type: string - # Connection string using named pipes that will be set in the config.json file. + # Display name for the config being used for this execution of the manual jobs. This will be used + # for the job name, so it may only contain alphanumeric and '_' characters. + - name: configDisplayName + type: string + + # Connection string using named pipes that will be set in the config.jsonc file. - name: connectionStringNp type: string - # Connection string using TCP that will be set in the config.json file. + # Connection string using TCP that will be set in the config.jsonc file. - name: connectionStringTcp type: string - # Path to use for file stream tests, this value will be stored in the config.json file. + # Path to use for file stream tests, this value will be stored in the config.jsonc file. - name: fileStreamDirectory type: string + # Whether the server that tests will be executed against is local to the job agent. If `true` + # server configuration steps will be executed prior to running the tests. + - name: isLocalServer + type: boolean + # Name of the local DB application name for localdb tests, this value will be stored in the - # config.json + # config.jsonc - name: localDbAppName type: string # Name of the local DB shared instance name for localdb tests, this value will be stored in the - # config.json + # config.jsonc - name: localDbSharedInstanceName type: string + # Manual test set to execute. + - name: testSet + type: string + + # Name of the user managed identity client ID. This value will be stored in config.jsonc for use + # in AKV tests and substituted in for Azure connection string user IDs. + - name: userManagedIdentityClientId + type: string + jobs: - - job: "test_${{ parameters.platformDisplayName }}_sqlclient_manual" - displayName: "sqlclient_manual_${{ parameters.platformDisplayName }}" + - job: "test_${{ parameters.platformDisplayName }}_${{ parameters.configDisplayName }}_${{ parameters.testSet }}_sqlclient_manual" + displayName: "sqlclient_manual_${{ parameters.configDisplayName}}_${{ parameters.testSet }}_${{ parameters.platformDisplayName }}" pool: name: ${{ parameters.poolName }} @@ -97,17 +129,23 @@ jobs: value: $[stageDependencies.${{ parameters.stageNameSecrets }}.secrets_job.outputs['SaPassword.Value']] steps: - # Install the version of the dotnet runtime we will use to execute the test target. - - template: /eng/pipelines/pr/steps/install-dotnet.yml@self + # Install dotnet and the runtime that will run the tests (if it is not netframework) + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: - runtimeVersion: ${{ parameters.platformDotnet }} + ${{ if not(contains(parameters.platformDotnet, 'net4')) }}: + runtimes: + - "${{ replace(parameters.platformDotnet, 'net', '') }}.x" + + # Restore dotnet tools + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self # Configure the local SQL Server instance - - template: /eng/pipelines/pr/steps/configure-sqlserver-step.yml@self - parameters: - fileStreamDirectory: ${{ parameters.fileStreamDirectory }} - operatingSystem: ${{ parameters.platformOperatingSystem }} - saPassword: $(saPassword) + - ${{ if eq(parameters.isLocalServer, true) }}: + - template: /eng/pipelines/pr/steps/configure-sqlserver-step.yml@self + parameters: + fileStreamDirectory: ${{ parameters.fileStreamDirectory }} + operatingSystem: ${{ parameters.platformOperatingSystem }} + saPassword: $(saPassword) # Assign the generated SA password to the $Password field. This will allow $(Password) to be # be replaced in connection strings with whatever set in the secrets stage. @@ -116,12 +154,12 @@ jobs: Write-Host "##vso[task.setvariable variable=Password;isSecret=true]$password" displayName: Set Connection String Password - # Write configuration information to the config.json file. + # Write configuration information to the config.jsonc file. - pwsh: | # Read existing default configuration from file - $defaultConfigPath = "$(REPO_ROOT)/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json" + $defaultConfigPath = "$(REPO_ROOT)/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.jsonc" $configDirectory = "$(Agent.TempDirectory)/mds-config" - $configPath = Join-Path $configDirectory "config.json" + $configPath = Join-Path $configDirectory "config.jsonc" $config = Get-Content -Raw $defaultConfigPath | ConvertFrom-Json # Update the config values @@ -133,6 +171,7 @@ jobs: $config.LocalDbSharedInstanceName = "${{ parameters.localDbSharedInstanceName }}" $config.ManagedIdentitySupported = $true $config.SupportsIntegratedSecurity = $false + $config.UserManagedIdentityClientId = "${{ parameters.userManagedIdentityClientId }}" if ("${{ parameters.platformOperatingSystem }}" -eq "Windows") { $config.FileStreamDirectory = "${{ parameters.fileStreamDirectory }}" @@ -149,12 +188,17 @@ jobs: displayName: 'Generate manual test config' # Execute TestSqlClientManual target from build.proj - - template: /eng/pipelines/pr/steps/test-buildproj-step.yml - parameters: - buildConfiguration: ${{ parameters.buildConfiguration }} - buildSuffix: ${{ parameters.buildSuffix }} - - packageShortName: "SqlClient" - testFramework: ${{ parameters.platformDotnet }} - testProject: "Manual" + - task: DotNetCoreCLI@2 + displayName: 'Test: SqlClientManual ${{ parameters.testSet }}' + inputs: + command: build + projects: build.proj + verbosity: ${{ parameters.dotnetVerbosity }} + arguments: >- + -t:TestSqlClientManual + -p:Configuration=${{ parameters.buildConfiguration }} + -p:BuildNumber='$(Build.BuildNumber)' + -p:BuildSuffix='${{ parameters.buildSuffix }}' + -p:TestFramework=${{ parameters.platformDotnet }} + -p:TestSet=${{ parameters.testSet }} diff --git a/eng/pipelines/pr/pr-pipeline.yml b/eng/pipelines/pr/pr-pipeline.yml index 9774ebdbfa..daf393e502 100644 --- a/eng/pipelines/pr/pr-pipeline.yml +++ b/eng/pipelines/pr/pr-pipeline.yml @@ -6,6 +6,21 @@ name: $(DayOfYear)$(Rev:rr) +parameters: + # General Parameters ===================================================== + + # Dotnet CLI verbosity level. + - name: dotnetVerbosity + displayName: dotnet CLI Verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + variables: - template: /eng/pipelines/common/variables/common-variables.yml@self - template: /eng/pipelines/pr/variables/pr-variables.yml@self @@ -28,6 +43,7 @@ stages: parameters: buildConfiguration: Debug buildSuffix: pr + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} poolName: $(PoolNameDefault) stageNamePack: ${{ variables.stageNamePack }} stageNameSecrets: ${{ variables.stageNameSecrets }} @@ -72,6 +88,7 @@ stages: manualTestFileStreamDirectory: "$(Pipeline.Workspace)/filestream" manualTestLocalDbAppName: $(LocalDbAppName) manualTestLocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + manualTestUserManagedIdentityClientId: $(UserManagedIdentityClientId) # Stage 3: Collect code coverage # @TODO: diff --git a/eng/pipelines/pr/stages/pack-stage.yml b/eng/pipelines/pr/stages/pack-stage.yml index c0a135e246..5473027731 100644 --- a/eng/pipelines/pr/stages/pack-stage.yml +++ b/eng/pipelines/pr/stages/pack-stage.yml @@ -50,8 +50,11 @@ stages: vmImage: 'ubuntu-latest' steps: - # Install dotnet SDK and restore tools - - template: /eng/pipelines/pr/steps/install-dotnet.yml@self + # Install dotnet + - template: /eng/pipelines/common/steps/install-dotnet.yml@self + + # Restore dotnet tools + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self # ################################################################ # Build/Pack Microsoft.Data.SqlClient diff --git a/eng/pipelines/pr/stages/test-stages.yml b/eng/pipelines/pr/stages/test-stages.yml index f2ffbc09cf..3a1e2c2011 100644 --- a/eng/pipelines/pr/stages/test-stages.yml +++ b/eng/pipelines/pr/stages/test-stages.yml @@ -9,6 +9,8 @@ # confidence and expedience of validation. parameters: + # General Parameters ===================================================== + # Configuration with which to build the packages and execute the tests. - name: buildConfiguration type: string @@ -20,6 +22,21 @@ parameters: - name: buildSuffix type: string + # Dotnet CLI verbosity level. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + + # Name of the pool to use for jobs that require customized VM images. + - name: poolName + type: string + # Name of the build/pack stage that this stage will depend on. - name: stageNamePack type: string @@ -28,55 +45,56 @@ parameters: - name: stageNameSecrets type: string - # Name of the pool to use for jobs that require customized VM images. - - name: poolName - type: string - # Manual Test Configuration Parameters ================================== - # Azure Key Vault tenant ID that will be set in the config.json file for sqlclient manual tests. + # Azure Key Vault tenant ID that will be set in the config.jsonc file for sqlclient manual tests. - name: manualTestAzureKeyVaultTenantId type: string - # Azure Key Vault URL that will be set in the config.json file for sqlclient manual tests. + # Azure Key Vault URL that will be set in the config.jsonc file for sqlclient manual tests. - name: manualTestAzureKeyVaultUrl type: string - # Connection string to Azure SQL using named pipes that will be set in the config.json file for + # Connection string to Azure SQL using named pipes that will be set in the config.jsonc file for # sqlclient manual tests against Azure SQL. - name: manualTestConnectionStringNpAzure type: string - # Connection string to localhost using named pipes that will be set in the config.json file for + # Connection string to localhost using named pipes that will be set in the config.jsonc file for # sqlclient manual tests against a local box product. - name: manualTestConnectionStringNpLocalhost type: string - # Connection string to Azure SQL using TCP that will be set in the config.json file for sqlclient + # Connection string to Azure SQL using TCP that will be set in the config.jsonc file for sqlclient # manual tests against Azure SQL. - name: manualTestConnectionStringTcpAzure type: string - # Connection string to localhost using TCP that will be set in the config.json file for sqlclient + # Connection string to localhost using TCP that will be set in the config.jsonc file for sqlclient # manual tests against a local box product. - name: manualTestConnectionStringTcpLocalhost type: string - # Path to use for file stream tests, this value will be stored in the config.json for sqlclient + # Path to use for file stream tests, this value will be stored in the config.jsonc for sqlclient # manual tests. - name: manualTestFileStreamDirectory type: string # Name of the local DB application name for localdb tests, this value will be stored in the - # config.json for sqlclient manual tests. + # config.jsonc for sqlclient manual tests. - name: manualTestLocalDbAppName type: string # Name of the local DB shared instance name for localdb tests, this value will be stored in the - # config.json for sqlclient manual tests. + # config.jsonc for sqlclient manual tests. - name: manualTestLocalDbSharedInstanceName type: string + # Name of the managed identity client ID, this value will be stored in the config.jsonc for + # sqlclient manual tests. + - name: manualTestUserManagedIdentityClientId + type: string + # This is an array of objects with the following fields: # - displayName - Friendly name to use when displaying the stage name and job names. This # must only contain alphanumeric characters and '_'. It will be used for both @@ -106,6 +124,7 @@ stages: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildSuffix: ${{ parameters.buildSuffix }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} platformDisplayName: ${{ platform.displayName }} platformDotnet: ${{ platform.dotnet }} platformImage: ${{ platform.image }} @@ -119,6 +138,7 @@ stages: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildSuffix: ${{ parameters.buildSuffix }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} platformDisplayName: ${{ platform.displayName }} platformDotnet: ${{ platform.dotnet }} platformImage: ${{ platform.image }} @@ -132,6 +152,7 @@ stages: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildSuffix: ${{ parameters.buildSuffix }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} platformDisplayName: ${{ platform.displayName }} platformDotnet: ${{ platform.dotnet }} platformImage: ${{ platform.image }} @@ -146,6 +167,7 @@ stages: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildSuffix: ${{ parameters.buildSuffix }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} stageNameSecrets: ${{ parameters.stageNameSecrets }} platformDisplayName: ${{ platform.displayName }} @@ -156,37 +178,74 @@ stages: azureKeyVaultTenantId: ${{ parameters.manualTestAzureKeyVaultTenantId }} azureKeyVaultUrl: ${{ parameters.manualTestAzureKeyVaultUrl }} + configDisplayName: "localhost" connectionStringNp: ${{ parameters.manualTestConnectionStringNpLocalhost }} connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpLocalhost }} fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} + isLocalServer: true + localDbAppName: ${{ parameters.manualTestLocalDbAppName }} + localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} + testSet: "123" + userManagedIdentityClientId: ${{ parameters.manualTestUserManagedIdentityClientId }} + + # TestSqlClientManual - Azure + - template: /eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + stageNameSecrets: ${{ parameters.stageNameSecrets }} + + platformDisplayName: ${{ platform.displayName }} + platformDotnet: ${{ platform.dotnet }} + platformImage: ${{ platform.image }} + platformOperatingSystem: ${{ platform.operatingSystem }} + poolName: ${{ parameters.poolName }} + + azureKeyVaultTenantId: ${{ parameters.manualTestAzureKeyVaultTenantId }} + azureKeyVaultUrl: ${{ parameters.manualTestAzureKeyVaultUrl }} + configDisplayName: "azure" + connectionStringNp: ${{ parameters.manualTestConnectionStringNpAzure }} + connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpAzure }} + fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} + isLocalServer: false localDbAppName: ${{ parameters.manualTestLocalDbAppName }} localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} + testSet: "123" + userManagedIdentityClientId: ${{ parameters.manualTestUserManagedIdentityClientId }} - # @TODO: -# # TestSqlClientManual - Azure -# - template: /eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml@self -# parameters: -# buildConfiguration: ${{ parameters.buildConfiguration }} -# buildSuffix: ${{ parameters.buildSuffix }} -# platformDisplayName: ${{ platform.displayName }} -# platformDotnet: ${{ platform.dotnet }} -# platformImage: ${{ platform.image }} -# poolName: ${{ parameters.poolName }} -# -# azureKeyVaultTenantId: ${{ parameters.manualTestAzureKeyVaultTenantId }} -# azureKeyVaultUrl: ${{ parameters.manualTestAzureKeyVaultUrl }} -# connectionStringNp: ${{ parameters.manualTestConnectionStringNpAzure }} -# connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpAzure }} -# fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} -# localDbAppName: ${{ parameters.manualTestLocalDbAppName }} -# localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} -# saPassword: $(manualTestSaPassword) + # TestSqlClientManual - Localhost Always Encrypted + - template: /eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml@self + parameters: + buildConfiguration: "Release" # AE tests are not stable enough to run in DEBUG mode (as of 5/29/26) + buildSuffix: ${{ parameters.buildSuffix }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + stageNameSecrets: ${{ parameters.stageNameSecrets }} + + platformDisplayName: ${{ platform.displayName }} + platformDotnet: ${{ platform.dotnet }} + platformImage: ${{ platform.image }} + platformOperatingSystem: ${{ platform.operatingSystem }} + poolName: ${{ parameters.poolName }} + + azureKeyVaultTenantId: ${{ parameters.manualTestAzureKeyVaultTenantId }} + azureKeyVaultUrl: ${{ parameters.manualTestAzureKeyVaultUrl }} + configDisplayName: "localhost_ae" + connectionStringNp: ${{ parameters.manualTestConnectionStringNpLocalhost }} + connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpLocalhost }} + fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} + isLocalServer: true + localDbAppName: ${{ parameters.manualTestLocalDbAppName }} + localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} + testSet: "AE" + userManagedIdentityClientId: ${{ parameters.manualTestUserManagedIdentityClientId }} # TestSqlClientUnit - template: /eng/pipelines/pr/jobs/test-buildproj-job.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildSuffix: ${{ parameters.buildSuffix }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} platformDisplayName: ${{ platform.displayName }} platformDotnet: ${{ platform.dotnet }} platformImage: ${{ platform.image }} diff --git a/eng/pipelines/pr/steps/build-buildproj-step.yml b/eng/pipelines/pr/steps/build-buildproj-step.yml deleted file mode 100644 index 3e81edfbf4..0000000000 --- a/eng/pipelines/pr/steps/build-buildproj-step.yml +++ /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. # -################################################################################# - -# This collection of steps to build a project via the build.proj. This will execute the "Build*" -# target in build.proj, where * is the packageShortName provided in the parameters. -# -# Note: This differs from the onebranch build-buildproj-step.yml in that it does *not* strong-name -# sign the assemblies, it only builds in project reference mode, and as such it does not allow -# version parameters or dependencies to be provided. - -parameters: - - name: buildConfiguration - type: string - values: - - Debug - - Release - - - name: buildSuffix - type: string - - - name: packageShortName - type: string - values: - - Azure - - AkvProvider - - Abstractions - - Logging - - SqlClient - - SqlServer - -steps: - - task: DotNetCoreCLI@2 - displayName: 'build.proj - Build${{ parameters.packageShortName }}' - inputs: - command: build - projects: build.proj - arguments: >- - -t:Build${{ parameters.packageShortName }} - -p:Configuration=${{ parameters.buildConfiguration }} - -p:BuildNumber='$(Build.BuildNumber)' - -p:BuildSuffix='${{ parameters.buildSuffix }}' - - diff --git a/eng/pipelines/pr/steps/install-dotnet.yml b/eng/pipelines/pr/steps/install-dotnet.yml deleted file mode 100644 index fe1c8f7878..0000000000 --- a/eng/pipelines/pr/steps/install-dotnet.yml +++ /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. # -################################################################################# - -# This is a step to wrap the install-dotnet step for use in the PR pipeline. It will always install -# the SDK version specified in the global.json file, and if the runtime version specified does not -# begin with "net4" (ie, indicating netfx runtime is being used), the version will be passed in as -# part of the runtime parameters. -# -# This version chains into the common install-dotnet step to enable the pr pipeline to call a -# single step template and get the desired behavior without complexity. - -parameters: - - name: runtimeVersion - type: string - default: '' - -steps: - - template: /eng/pipelines/common/steps/install-dotnet.yml@self - parameters: - ${{ if and( not(eq(parameters.runtimeVersion, '' )), not(contains(parameters.runtimeVersion, 'net4')) ) }}: - runtimes: - - "${{ replace(parameters.runtimeVersion, 'net', '') }}.x" - - - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self diff --git a/eng/pipelines/pr/steps/pack-buildproj-step.yml b/eng/pipelines/pr/steps/pack-buildproj-step.yml index 6b2980cbb4..f947bafb0e 100644 --- a/eng/pipelines/pr/steps/pack-buildproj-step.yml +++ b/eng/pipelines/pr/steps/pack-buildproj-step.yml @@ -46,7 +46,7 @@ parameters: steps: - task: DotNetCoreCLI@2 - displayName: 'build.proj - Build${{ parameters.packageShortName }}' + displayName: 'Pack: ${{ parameters.packageShortName }}' condition: ${{ parameters.condition }} inputs: command: build diff --git a/eng/pipelines/pr/steps/test-buildproj-step.yml b/eng/pipelines/pr/steps/test-buildproj-step.yml deleted file mode 100644 index 5309e7ef96..0000000000 --- a/eng/pipelines/pr/steps/test-buildproj-step.yml +++ /dev/null @@ -1,59 +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: - # General Parameters ======================================================= - - # Configuration to use to build the project. - - name: buildConfiguration - type: string - values: - - Debug - - Release - - # Suffix to apply to the generated packages such that the package will be considered a prerelease - # version. The resulting package version will look like: `1.2.3-suffix888.1` where 1.2.3 is the - # default version specified in Versions.props, suffix is this parameter, and 888.1 is the build - # number for the currently executing build. - - name: buildSuffix - type: string - - # Test Parameters ======================================================== - - # Package/project to test. - - name: packageShortName - type: string - values: - - Azure - - AkvProvider - - Abstractions - - Logging - - SqlClient - - SqlServer - - - name: testFramework - type: string - - # If the project to test has multiple projects supported by build.proj, use this optional - # parameter to specify the project to execute. This will be appended to Test{PackageShortName} to - # generate the target to execute. See build.proj for supported targets. - - name: testProject - type: string - default: '' - -steps: - - task: DotNetCoreCLI@2 - displayName: 'build.proj - Test${{ parameters.packageShortName }}${{ parameters.testProject }}' - inputs: - command: build - projects: build.proj - verbosity: detailed - arguments: >- - -t:Test${{ parameters.packageShortName }}${{ parameters.testProject }} - -p:Configuration=${{ parameters.buildConfiguration }} - -p:BuildNumber='$(Build.BuildNumber)' - -p:BuildSuffix='${{ parameters.buildSuffix }}' - -p:TestFramework=${{ parameters.testFramework }} diff --git a/eng/pipelines/pr/variables/pr-variables.yml b/eng/pipelines/pr/variables/pr-variables.yml index 4a28253dc7..a9da73850e 100644 --- a/eng/pipelines/pr/variables/pr-variables.yml +++ b/eng/pipelines/pr/variables/pr-variables.yml @@ -20,6 +20,7 @@ variables: # LocalDbAppName # LocalDbSharedInstanceName # PoolNameDefault + # UserManagedIdentityClientId - group: sqlclient-testconfig-v1 # General Variables ====================================================== diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs index 53232b1431..6c2db5dd6e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs @@ -30,7 +30,13 @@ public AKVTest(SQLSetupStrategyAzureKeyVault fixture) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void TestEncryptDecryptWithAKV() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) + // Select connection string - If an enclave connection string exists, use that, but for + // non-enclave test runs, use the standard TCP connection string. + string baseConnectionString = string.IsNullOrWhiteSpace(DataTestUtility.TCPConnectionStringHGSVBS) + ? DataTestUtility.TCPConnectionString + : DataTestUtility.TCPConnectionStringHGSVBS; + + SqlConnectionStringBuilder builder = new(baseConnectionString) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, @@ -71,7 +77,13 @@ It aims to confirm that three consecutive connections will consistently fail wit [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void ForcedColumnDecryptErrorTestShouldFail() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) + // Select connection string - If an enclave connection string exists, use that, but for + // non-enclave test runs, use the standard TCP connection string. + string baseConnectionString = string.IsNullOrWhiteSpace(DataTestUtility.TCPConnectionStringHGSVBS) + ? DataTestUtility.TCPConnectionString + : DataTestUtility.TCPConnectionStringHGSVBS; + + SqlConnectionStringBuilder builder = new(baseConnectionString) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, @@ -148,7 +160,13 @@ public void TestRoundTripWithAKVAndCertStoreProvider() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))] public void TestLocalCekCacheIsScopedToProvider() { - SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS) + // Select connection string - If an enclave connection string exists, use that, but for + // non-enclave test runs, use the standard TCP connection string. + string baseConnectionString = string.IsNullOrWhiteSpace(DataTestUtility.TCPConnectionStringHGSVBS) + ? DataTestUtility.TCPConnectionString + : DataTestUtility.TCPConnectionStringHGSVBS; + + SqlConnectionStringBuilder builder = new(baseConnectionString) { ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled, AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified, diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index feefe77adb..6f564250ce 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -1999,6 +1999,8 @@ public void TestBeginAndEndExecuteReaderWithAsyncCallback(string connection, Com } } + [Trait("Category", "flaky")] // System.AggregateException : One or more errors occurred. (Assert.ThrowsAny() Failure: No exception was thrown + // Expected: typeof(System.Exception)) [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithExecutionMethod))] public void TestSqlCommandCancel(string connection, string value) @@ -2184,6 +2186,16 @@ public void TestSqlCommandCancel(string connection, string value) tasks[1].Start(); // Wait for the threads to finish. + // Flaky Stack Trace -------------------------------------- + // Assert.ThrowsAny() Failure: No exception was thrown + // Expected: typeof(System.Exception) + // Stack Trace: + // at System.Threading.Tasks.Task.WaitAllCore(ReadOnlySpan`1 tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken) + // at System.Threading.Tasks.Task.WaitAll(Task[] tasks) + // at Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.ApiShould.TestSqlCommandCancel(String connection, String value) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\ManualTests\AlwaysEncrypted\ApiShould.cs:line 2187 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) + Task.WaitAll(tasks); CommandHelper.s_sleepAfterReadDescribeEncryptionParameterResults?.SetValue(null, false); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs index 4938014ca3..e48bf5906a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs @@ -13,7 +13,8 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy [Trait("Set", "2")] public class CopyAllFromReaderConnectionClosedOnEventAsync { - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [Trait("Category", "flaky")] // Hangs and crashes on occasion + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] public void Test() { string srcConstr = DataTestUtility.TCPConnectionString; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs index e84c9fabb6..9f666fc45f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs @@ -228,6 +228,9 @@ public void ExecuteReader_GarbageCollectReader(CommandType commandType) } } + [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ + // Expected: 5 + // Actual: 4 [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] @@ -254,6 +257,17 @@ public void ExecuteReader_DisposeCommand(CommandType commandType) // Disposing of the command does *not* close the reader, meaning the MARS session // is left in an incomplete state. As such, with each command that's executed, a // new session is opened. + + // Flaky stack trace ------------------ + // Assert.Equal() Failure: Values differ + // Expected: 5 + // Actual: 4 + // Stack Trace: + // at Microsoft.Data.SqlClient.ManualTesting.Tests.MarsSessionPoolingTest.AssertSessionsAndRequests(SqlConnection connection, Int32 openMarsSessions, Int32 openRequests) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\ManualTests\SQL\MARSSessionPoolingTest\MarsSessionPoolingTest.cs:line 368 + // at Microsoft.Data.SqlClient.ManualTesting.Tests.MarsSessionPoolingTest.ExecuteReader_DisposeCommand(CommandType commandType) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\ManualTests\SQL\MARSSessionPoolingTest\MarsSessionPoolingTest.cs:line 256 + // at InvokeStub_MarsSessionPoolingTest.ExecuteReader_DisposeCommand(Object, Span`1) + // at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) + AssertSessionsAndRequests(connection, openMarsSessions: i + 1, openRequests: i + 1); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs index 0f81496d65..a6d5b741ed 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/SqlVariantParameterTests.cs @@ -259,6 +259,7 @@ public void SqlType_BulkCopyFromDataRow_RoundTripsCorrectly(object paramValue, s /// /// Round trip sql_variant value using TVP with a SqlMetaData/SqlDataRecord source. /// + [Trait("Category", "flaky")] // Doesn't work well on Azure. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(SqlTypeTestData), DisableDiscoveryEnumeration = true)] public void SqlType_TvpFromSqlMetaData_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) @@ -305,6 +306,7 @@ public void SqlType_TvpFromSqlMetaData_RoundTripsCorrectly(object paramValue, st /// /// Round trip sql_variant value using TVP with a SqlDataReader source. /// + [Trait("Category", "flaky")] // Doesn't work well on Azure. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [MemberData(nameof(SqlTypeTestData), DisableDiscoveryEnumeration = true)] public void SqlType_TvpFromSqlDataReader_RoundTripsCorrectly(object paramValue, string expectedTypeName, string expectedBaseTypeName) From 1afe6118cee9b3fcde71121eeacf1aededfadf36 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:26:06 -0700 Subject: [PATCH 146/298] Add `/ready` slash command and author notification for "Author attention needed" label (#4353) * Introduces workflow to make PR ready to review again with addressed feedback * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * include label comment * Update label * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * updates --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/notify-author-attention.yml | 35 ++++++++ .../remove-author-attention-label.yml | 83 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 .github/workflows/notify-author-attention.yml create mode 100644 .github/workflows/remove-author-attention-label.yml diff --git a/.github/workflows/notify-author-attention.yml b/.github/workflows/notify-author-attention.yml new file mode 100644 index 0000000000..4799e9efd1 --- /dev/null +++ b/.github/workflows/notify-author-attention.yml @@ -0,0 +1,35 @@ +name: Notify Author attention needed + +on: + pull_request_target: + types: [labeled] + +jobs: + notify-author: + # Only run when 'Author attention needed' label is added to a PR + if: >- + github.repository == 'dotnet/SqlClient' && + github.event.label.name == 'Author attention needed' + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Post comment to PR author + uses: actions/github-script@v9 + with: + script: | + const issue_number = context.payload.pull_request.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + const author = context.payload.pull_request.user.login; + + const body = `@${author} This pull request has been marked as **Author attention needed**.\n\nWhen you have addressed the reviewer feedback and are ready for another review, please post a comment with \`/ready\` to remove the label and re-engage reviewers.`; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + core.info(`Posted notification comment on PR #${issue_number}`); diff --git a/.github/workflows/remove-author-attention-label.yml b/.github/workflows/remove-author-attention-label.yml new file mode 100644 index 0000000000..808de9b9c2 --- /dev/null +++ b/.github/workflows/remove-author-attention-label.yml @@ -0,0 +1,83 @@ +name: Remove Author attention needed Label + +on: + issue_comment: + types: [created] + +jobs: + remove-label: + # Only run on PR comments with '/ready' from the PR author + if: >- + github.repository == 'dotnet/SqlClient' && + github.event.issue.pull_request != null && + github.event.comment.user.login == github.event.issue.user.login && + startsWith(github.event.comment.body, '/ready') + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Remove 'Author attention needed' label + uses: actions/github-script@v9 + with: + script: | + const labelName = 'Author attention needed'; + const issue_number = context.issue.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Check if the label exists on the PR + const labels = await github.paginate(github.rest.issues.listLabelsOnIssue, { + owner, + repo, + issue_number, + per_page: 100, + }); + + const hasLabel = labels.some(label => label.name === labelName); + + if (!hasLabel) { + core.info(`PR #${issue_number} does not have the '${labelName}' label. No action taken.`); + return; + } + + await github.rest.issues.removeLabel({ + owner, + repo, + issue_number, + name: labelName, + }); + core.info(`Removed '${labelName}' label from PR #${issue_number}`); + + // Re-request reviews from existing reviewers (paginate to include all reviews) + const reviews = await github.paginate(github.rest.pulls.listReviews, { + owner, + repo, + pull_number: issue_number, + per_page: 100, + }); + + // Collect unique reviewers (exclude the PR author) + const author = context.payload.issue.user.login; + const reviewers = [...new Set( + reviews + .filter(r => r.user?.type === 'User') + .map(r => r.user?.login) + .filter(login => login && login !== author) + )]; + + if (reviewers.length > 0) { + try { + await github.rest.pulls.requestReviewers({ + owner, + repo, + pull_number: issue_number, + reviewers: reviewers.slice(0, 15), + }); + core.info(`Re-requested reviews from: ${reviewers.join(', ')}`); + } catch (err) { + core.warning(`Failed to re-request reviewers: ${err.message}`); + } + } else { + core.info('No previous reviewers to re-request.'); + } From e85a83fbee0623cc33ce56049209db405ee7a5d4 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:21:09 -0700 Subject: [PATCH 147/298] Update issue-triage workflow with environment config (#4361) --- .github/workflows/issue-triage.lock.yml | 36 +++++++++++-------------- .github/workflows/issue-triage.md | 2 ++ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index cc20a3b859..468ce93488 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"3fbe69c77302fcde863ac1c3f9df536f89e6a71a6b141b505437f06ed45182bc","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c9a288bea873cc74dd33e81e0f5784e167d990ee0e2645c1722106b45ae1f77d","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -89,7 +89,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} text: ${{ steps.sanitized.outputs.text }} @@ -131,11 +130,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -207,20 +201,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_ab488ada0396c523_EOF' + cat << 'GH_AW_PROMPT_671235e4d67f4971_EOF' - GH_AW_PROMPT_ab488ada0396c523_EOF + GH_AW_PROMPT_671235e4d67f4971_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_ab488ada0396c523_EOF' + cat << 'GH_AW_PROMPT_671235e4d67f4971_EOF' Tools: add_comment, add_labels, remove_labels, missing_tool, missing_data, noop - GH_AW_PROMPT_ab488ada0396c523_EOF + GH_AW_PROMPT_671235e4d67f4971_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_ab488ada0396c523_EOF' + cat << 'GH_AW_PROMPT_671235e4d67f4971_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -249,15 +243,15 @@ jobs: {{/if}} - GH_AW_PROMPT_ab488ada0396c523_EOF + GH_AW_PROMPT_671235e4d67f4971_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_ab488ada0396c523_EOF' + cat << 'GH_AW_PROMPT_671235e4d67f4971_EOF' {{#runtime-import .github/workflows/issue-triage.md}} - GH_AW_PROMPT_ab488ada0396c523_EOF + GH_AW_PROMPT_671235e4d67f4971_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -337,6 +331,7 @@ jobs: agent: needs: activation runs-on: ubuntu-latest + environment: issue-triage permissions: contents: read issues: read @@ -453,9 +448,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a309dbbfe73b9ee4_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2171a9d22dc2eb73_EOF' {"add_comment":{"hide_older_comments":true,"max":1},"add_labels":{"allowed":["Auto-Triage: Waiting for Author"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"remove_labels":{"allowed":["Auto-Triage: Waiting for Author"],"max":1},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_a309dbbfe73b9ee4_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_2171a9d22dc2eb73_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -682,7 +677,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_52e7f3ed4af388a8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_e021cf5fa19f3aa2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -726,7 +721,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_52e7f3ed4af388a8_EOF + GH_AW_MCP_CONFIG_e021cf5fa19f3aa2_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -958,6 +953,7 @@ jobs: always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true') runs-on: ubuntu-slim + environment: issue-triage permissions: contents: read discussions: write @@ -1070,7 +1066,6 @@ jobs: GH_AW_WORKFLOW_ID: "issue-triage" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} @@ -1289,6 +1284,7 @@ jobs: - detection if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' runs-on: ubuntu-slim + environment: issue-triage permissions: contents: read discussions: write diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index 4f0d391f41..ef157d7f62 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -42,6 +42,8 @@ if: | engine: copilot +environment: issue-triage + permissions: contents: read issues: read From ef601224fe725111150272391892481a593e555d Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:00:15 -0700 Subject: [PATCH 148/298] Public Roadmap (#4331) --- CONTRIBUTING.md | 40 +++++++++++++++++ README.md | 1 + contributing-workflow.md | 93 ++++++++++++++++++++++++++++++++++++-- roadmap.md | 96 +++++++++++++++++++++++++++++++++------- 4 files changed, 210 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8dca7be35c..13ee1ef83b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -129,6 +129,46 @@ To prevent closure, simply add comments, push commits, or respond to feedback. 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). +## Submitting Pull Requests + +- **New features from community PRs must be driven by creating a GitHub issue first.** Discuss the proposal in the issue before starting implementation. This helps avoid wasted effort and ensures alignment with project goals. +- **Community contributions must not derail the project roadmap.** We prioritize features and fixes according to our published milestones. PRs that conflict with or distract from active roadmap items may be deferred. +- **Our maintainers reserve the right to reject PRs** that do not meet the required criteria to qualify for review. This includes PRs that: + - Lack a corresponding approved issue (i.e., an issue that has been reviewed, acknowledged, and agreed upon by maintainers — typically indicated by the **`PM Approved`** field being set to **`Approved`** in the GitHub Project board) + - Introduce breaking changes without prior discussion + - Do not follow the project's coding standards and conventions + - Are missing adequate test coverage + - Conflict with work already in progress by the team +- **Bug fixes and small improvements** are generally welcome without a prior issue, but a linked issue helps us triage and prioritize your contribution. + +### What Makes a Great Contribution + +1. **Start with an issue** — File a [feature request](https://github.com/dotnet/SqlClient/issues/new?template=feature_request.md) or [bug report](https://github.com/dotnet/SqlClient/issues/new?template=bug-report.md) and wait for maintainer feedback +2. **Follow the conventions** — See [coding guidelines](policy/coding-style.md) +3. **Include tests** — Both unit tests and integration tests where applicable +4. **Keep scope focused** — One feature or fix per PR +5. **Update documentation** — For any public API changes + +### Contribution Workflow + +See [contributing-workflow.md](contributing-workflow.md) for details on how PRs are tracked through our review process. + +## Feedback + +The best way to give feedback is to create issues in the [dotnet/SqlClient](https://github.com/dotnet/SqlClient) repo. + +Please give us feedback that will provide insight on the following: + +- Existing features that are missing some capability or otherwise don't work well enough. +- Missing features that should be added to the product. +- Design choices for a feature that is currently in-progress. + +Some important caveats: + +- It is best to give design feedback quickly for improvements that are in development. We're unlikely to hold a feature from a release on late feedback. +- We are most likely to include improvements that either have a positive impact on a broad scenario or have very significant positive impact on a niche scenario. This means that we are unlikely to prioritize modest improvements to niche scenarios. +- Compatibility will almost always be given a higher priority than improvements. + ## Contribution Standards Project maintainers will merge changes that improve the product significantly and broadly and that align with the [Microsoft.Data.SqlClient roadmap](roadmap.md). diff --git a/README.md b/README.md index 1a066c52d4..07374c727b 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ When targeting .NET on Windows, a package reference to [Microsoft.Data.SqlClient | Coding Style | [coding-style.md](/policy/coding-style.md) | | Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) | | Copyright Information | [COPYRIGHT.md](COPYRIGHT.md) | +| Roadmap | [roadmap.md](roadmap.md) | | Review Process | [review-process.md](/policy/review-process.md) | | Support Policy | [SUPPORT.md](SUPPORT.md) | diff --git a/contributing-workflow.md b/contributing-workflow.md index 266c39c1a7..be7fffdbf2 100644 --- a/contributing-workflow.md +++ b/contributing-workflow.md @@ -7,7 +7,7 @@ You can contribute to Microsoft.Data.SqlClient with issues and PRs. Simply filin We use and recommend the following workflow: 1. **Create an issue for your work.** - - You can skip this step for trivial changes. + - **For all significant changes you intend to make in this repository, an issue is required.** This gives maintainers a chance to review the proposal before implementation begins and prevents wasted effort. Bug fixes and small improvements are generally welcome without a prior issue, but a linked issue helps us triage and prioritize your contribution. - 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. - Suggest [Labels](CONTRIBUTING.md#using-labels) to add for your issue. @@ -39,11 +39,12 @@ We use and recommend the following workflow: - It is OK to create your PR as Draft on the upstream repo before the implementation is done. - This can be useful if you'd like to start the feedback process concurrent with your implementation. - State that this is the case in the initial PR comment. +- **Keep your PR in Draft until CI validation passes.** The PR pipeline runs without maintainer intervention, so contributors should ensure all checks are green before marking the PR as ready for review. PRs appearing ready for review with failing checks will not be picked up by reviewers. ### Commit Guidelines - 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. +- PRs are merged via the default "Squash and Merge" strategy (see [Merging Pull Requests](#merging-pull-requests)), so your commit history is automatically collapsed into a single commit at merge time. ## PR - CI Process @@ -63,13 +64,98 @@ If the CI build fails for any reason, the PR issue will be updated with a link t - **Build failures**: Check the CI logs for specific error messages. - **Test failures**: Ensure all tests pass locally before pushing. -- **Merge conflicts**: Rebase your branch against the latest main branch. +- **Merge conflicts**: + - *Before review has started*: Rebase your branch against the latest `main` branch to keep history clean. + - *After a reviewer has started reviewing*: Do **not** rebase. Rebasing rewrites commit history and makes it impossible for reviewers to tell which commits they have already reviewed, forcing them to start over. Instead, resolve conflicts by merging the latest `main` into your branch (`git merge main`) so that previously reviewed commits remain intact and reviewers can pick up where they left off. ### Getting Help - Comment on your PR if you're stuck. - Reach out in [GitHub Discussions](https://github.com/dotnet/SqlClient/discussions) for broader questions. +## Tracking PRs in the GitHub Project + +The [SqlClient Board](https://github.com/orgs/dotnet/projects/588) (dotnet org project #588) is the team's triage board for tracking issues and PRs. When a PR is linked to an issue, its progress is tracked through the project's fields described below. + +### Project Fields + +| Field | Type | Values | Purpose | +|-------|------|--------|---------| +| **Status** | Single select | `To triage`, `Waiting for customer`, `In progress`, `In review`, `In validation`, `Done` | Tracks the current workflow stage of the item | +| **Priority** | Single select | `P0`, `P1`, `P2`, `P3` | Indicates urgency and scheduling priority | +| **Size** | Single select | `XS`, `S`, `M`, `L`, `XL` | Estimates effort/complexity of the work | +| **API Impact** | Single select | `Breaking Change`, `New API`, `None` | Flags whether the change affects public API surface | +| **PM Approved** | Single select | `N/A`, `Pending`, `Approved` | Tracks product manager sign-off for API or behavioral changes | +| **Assignees** | Field | *(GitHub users)* | Who is responsible for the work | +| **Labels** | Field | *(GitHub labels)* | Categorization and area tags | +| **Reviewers** | Field | *(GitHub users)* | Assigned code reviewers | +| **Milestone** | Field | *(GitHub milestones)* | Target release version | +| **Linked pull requests** | Field | *(PR references)* | PRs associated with the issue | +| **Parent issue** | Field | *(issue reference)* | Epic or parent tracking issue | +| **Sub-issues progress** | Field | *(auto-calculated)* | Completion progress of child issues | +| **Comment** | Text | *(free text)* | Additional context or notes | +| **AB-Link** | Text | *(URL)* | Link to Azure Boards work item (if applicable) | + +### Status Stages for PRs + +PRs (and their linked issues) move through the **Status** field to communicate reviewability and progress: + +| Status | What it means for a PR | +|--------|------------------------| +| **To triage** | PR just opened or linked issue is awaiting initial assessment. Maintainers will review scope, assign reviewers, and set priority. | +| **In progress** | Author is actively developing the change. PR may be in Draft state. | +| **In review** | PR is ready for code review. Reviewers are assigned and the author considers the implementation complete. | +| **Waiting for customer** | Review feedback has been given; the PR author needs to respond or push changes. When a reviewer requests changes, they will also apply the **`Author attention needed`** label so the PR is easy to surface in queries and dashboards. Once the author addresses the feedback, they should remove the `Author attention needed` label if permissions allow or comment `/ready` on the PR to remove the label `Author attention needed` and re-engage the reviewers to move the PR back to `In review`. | +| **In validation** | PR is approved and being validated (CI, manual testing, integration checks) before merge. | +| **Done** | PR has been merged and the associated work is complete. | + +### Status Transitions + +When a reviewer leaves feedback that needs author action, they move the PR to `Waiting for customer` **and** apply the `Author attention needed` label. The author removes the label (and updates Status back to `In review`/`In progress`) once the feedback is addressed. + +``` ++--------------+ +---------------+ +-------------------------+ +-----------------+ +--------+ +| To triage |------>| In progress |------>| In review |------>| In validation |------>| Done | ++--------------+ +---------------+ +------------+------------+ +-----------------+ +--------+ + ^ | ^ + | | changes | author addresses + | | requested | feedback (minor) + + | | + label | removes label + | v | + | +------------------------+ + | | Waiting for customer | + | | + Author attention | + | | needed (label) | + | +------------------------+ + | | + | | major rework needed + +----------------------+ +``` + +### Additional Field Usage + +- **Priority**: `P0` items are critical fixes that should be reviewed and merged urgently. `P1` items are high priority for the current milestone. `P2`/`P3` items are scheduled as capacity allows. +- **Size**: Helps reviewers estimate review effort. `XS`/`S` PRs should get faster turnaround. `L`/`XL` PRs may need multiple reviewers or phased review. +- **API Impact**: PRs marked `Breaking Change` or `New API` require **PM Approved = Approved** before merge. `ref/` project updates must accompany these PRs. +- **PM Approved**: Set to `Pending` when a PR introduces API changes. Must reach `Approved` before the PR can be merged. `N/A` for internal or non-API changes. + +### Guidelines for Contributors + +> **Note:** Setting labels and project board fields (Status, Priority, etc.) requires maintainer or triage access. External contributors cannot modify these directly. Instead, suggest labels in your PR description and the team will apply them. If review feedback asks you to "remove the label" or "update Status", a maintainer will handle it if you don't have access—just let the team know in a comment. + +1. **Link your PR to an issue** — This ensures the PR appears on the project board and is tracked through the workflow. +2. **Keep Status current** — If you're the author, move your linked issue to `In review` when your PR is ready for feedback. +3. **Respond promptly** — When status is `Waiting for customer` and the **`Author attention needed`** label is applied, the team is blocked on your response. Address the feedback, push the updates, **remove the `Author attention needed` label**, and move Status back to `In review` (or `In progress` for major rework) so reviewers know the PR is ready for another pass. If you are a community contributor, you may post a comment `/ready` on the PR to automate removing the label and re-engaging reviewers on the PR. +4. **Flag API changes early** — Set **API Impact** appropriately so PM review can happen in parallel with code review. +5. **Don't skip validation** — Even after approval, the PR stays in `In validation` until CI passes and any manual verification is complete. + +### Guidelines for Reviewers + +1. **Signal that author action is required** — When your review requests changes (whether via formal "Request changes" or a comment that requires author follow-up), apply the **`Author attention needed`** label to the PR and move its linked issue to `Waiting for customer`. This pair (label + status) is what the team uses to find PRs that are blocked on the author versus those still awaiting review. +2. **Don't remove the label yourself** — Leave the `Author attention needed` label in place until the author pushes updates addressing the feedback; the author is responsible for removing it when they hand the PR back for review. +3. **Re-review promptly** — Once the author removes the label, the PR status should be updated either by Author (if they have permissions) or the reviewer to `In review`, so it doesn't stall and is picked up for review on time. +4. **Only reviewers resolve their own feedback threads** — A review comment thread should only be resolved by the reviewer who created it, once they are satisfied the feedback has been addressed. Authors should not resolve reviewer threads themselves. + ## Stale PR Management The SqlClient repository uses automated workflows to manage inactive pull requests and maintain repository hygiene. @@ -111,6 +197,7 @@ One or more Microsoft team members will review every PR prior to merge. They wil - Address all review comments before the PR can be merged. - Feel free to ask questions if feedback is unclear. - Push additional commits to address feedback (these will be squashed later). +- When a reviewer leaves feedback that needs your action, they will set Status to `Waiting for customer` and apply the **`Author attention needed`** label. After you push updates, **remove the `Author attention needed` label** or post a comment `/ready` to do so, so the reviewers know the PR is ready for another pass. ## Merging Pull Requests diff --git a/roadmap.md b/roadmap.md index 4c080e7242..e41d19dc79 100644 --- a/roadmap.md +++ b/roadmap.md @@ -1,30 +1,92 @@ # Microsoft.Data.SqlClient Roadmap -The Microsoft.Data.SqlClient roadmap communicates project priorities for evolving and extending the scope of the product. We encourage the community to work with us to improve SqlClient driver for these scenarios and extend it for others. +The Microsoft.Data.SqlClient roadmap communicates project priorities for evolving and extending the scope of the product. We encourage the community to work with us to improve the SqlClient driver for these scenarios and extend it for others. -## Released Versions +> **Last updated:** May 2026 +> +> This roadmap is a living document. Priorities and timelines may shift based on community feedback, engineering constraints, and business needs. We update this page regularly to reflect the current state of development. + +--- + +## Release Milestones + +For active release milestones, their target dates, and the changes included, see [SqlClient milestones](https://github.com/dotnet/SqlClient/milestones). + +--- + +## Current Focus Areas + +Our team is actively working on the following high-level themes. Features are tracked via [GitHub issues](https://github.com/dotnet/SqlClient/issues) where applicable — see the linked milestones for associated issue details. + +- **Active** — Currently in development +- **Planned** — Committed for a future milestone with estimated delivery +- **Backlog** — On our radar for future months, not yet scheduled + +### Performance & Reliability + +| Work Item(s) | Feature | Status | ETA | +| ------------ | ------- | ------ | --- | +| [#3356](https://github.com/dotnet/SqlClient/issues/3356) | Connection pool performance improvements | Active | July 2026 | +| N/A | Performance benchmarking suite | Active | July 2026 | +| [#422](https://github.com/dotnet/SqlClient/issues/422) | Phase 1 - Unix async performance — thread starvation in parallel `ExecuteReaderAsync` | Active | September 2026 | +| TBD | Phase 2 - Async usage analysis and optimization | Planned | — | -- [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. +### New Data Type Support -## Upcoming Ship Dates +| Work Item(s) | Feature | Status | ETA | +| ------------ | ------- | ------ | --- | +| TBD | Vector subtype support — `float16` (`Half`) | Active | August 2026 | -For upcoming release dates, see [SqlClient milestones](https://github.com/dotnet/SqlClient/milestones). +### Observability & Diagnostics -[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. +| Work Item(s) | Feature | Status | ETA | +| ------------ | ------- | ------ | --- | +| [#2210](https://github.com/dotnet/SqlClient/issues/2210) [#2211](https://github.com/dotnet/SqlClient/issues/2211) | OpenTelemetry support | Planned | — | +| N/A | Logging improvements | Planned | — | +| TBD | Integrate with / expose MSAL logging | Planned | — | -## Feedback +### API Improvements + +| Work Item(s) | Feature | Status | ETA | +| ------------ | ------- | ------ | --- | +| [#2353](https://github.com/dotnet/SqlClient/issues/2353) | Expose connection encryption information to clients | Planned | September 2026 | +| [#26](https://github.com/dotnet/SqlClient/issues/26) | Throw `TaskCanceledException` instead of `SqlException` for cancellations | Planned | September 2026 | +| [#113](https://github.com/dotnet/SqlClient/issues/113) | `BeginTransactionAsync` API on `SqlConnection` | Planned | — | + +### Security & Architecture + +| Feature | Status | ETA | +| ------- | ------ | --- | +| Security hardening activities | Active | Ongoing internally | + +### AI & Developer Tooling + +| Feature | Status | ETA | +| ------- | ------ | --- | +| `System.Data.SqlClient` → `Microsoft.Data.SqlClient` migration via Modernize with Copilot | Active | September 2026 | +| Modernize SqlClient repository with AI | Active | Ongoing | + +### Engineering & Infrastructure + +| Feature | Status | ETA | +| ------- | ------ | --- | +| CI/CD pipeline redesign | Active | August 2026 | +| Add SQL Server 2025 to test matrix | Planned | August 2026 | +| Add .NET 10 to test matrix | Planned | August 2026 | +| Converting existing traditional pipelines to YAML | Active | August 2026 | +| Performance benchmarking pipeline (Internal) | Planned | September 2026 | + +--- + +## Released Versions -The best way to give feedback is to create issues in the [dotnet/sqlclient](https://github.com/dotnet/sqlclient) repo. +- [Release Notes](release-notes/README.md) — Detailed release notes summarizing all changes and features released. +- [GitHub Releases](https://github.com/dotnet/SqlClient/releases) — NuGet packages and changelog notes for each release. -Although mostly obvious, please give us feedback that will give us insight on the following points: +--- -- Existing features are missing some capability or otherwise don't work well enough. -- Missing features that should be added to the product. -- Design choices for a feature that is currently in-progress. +## Community Contributions & Feedback -Some important caveats / notes: +For information on how to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md). For details on the PR tracking workflow, see [contributing-workflow.md](contributing-workflow.md). -- It is best to give design feedback quickly for improvements that are in-development. We're unlikely to hold a feature being part of a release on late feedback. -- We are most likely to include improvements that either have a positive impact on a broad scenario or have very significant positive impact on a niche scenario. This means that we are unlikely to prioritize modest improvements to niche scenarios. -- Compatibility will almost always be given a higher priority than improvements. +The best way to give feedback is to create issues in the [dotnet/SqlClient](https://github.com/dotnet/SqlClient) repo. From 0764a5c0f4a372d5e6b7f5cda5a0b3452f1011c6 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 15 Jun 2026 16:11:54 -0300 Subject: [PATCH 149/298] Fix macOS CI: trust microsoft/mssql-release tap for Homebrew 5.2+ (#4363) * Fix macOS CI: trust microsoft/mssql-release tap for Homebrew 5.2+ Homebrew 5.2.0 made untrusted-tap enforcement the default. After 'brew update' pulls in the new version, 'brew install mssql-tools18' is refused because the microsoft/mssql-release tap is untrusted. This caused sqlcmd to never be installed (exit code 127), so the SQL Server connectivity check looped for 72 attempts and failed. Add 'brew trust microsoft/mssql-release' after tapping and before 'brew update' to explicitly trust the Microsoft tap. * Address PR #4363 review feedback - Move 'brew trust' after 'brew update' so it works even when the runner image starts with a Homebrew version older than 5.2. - Add a fail-fast check after 'brew install mssql-tools18' to immediately abort with a clear error if sqlcmd is not on PATH, avoiding the 6-minute timeout loop. --- .../steps/configure-sql-server-macos-step.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml index a989bf19bf..6e1541f939 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml @@ -34,8 +34,19 @@ steps: brew install docker brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release brew update + # Homebrew 5.2+ requires explicit trust for third-party taps. Run this + # after 'brew update' so the trust command is available even if the runner + # image shipped an older Homebrew version. + brew trust microsoft/mssql-release HOMEBREW_ACCEPT_EULA=Y brew install mssql-tools18 + # Fail fast if sqlcmd was not installed (e.g. tap-trust or formula error). + # Without this check the script would loop for ~6 minutes trying to connect. + if ! command -v sqlcmd &>/dev/null; then + echo "ERROR: sqlcmd is not on PATH after brew install. Check the mssql-tools18 installation above." + exit 1 + fi + # Start Colima with Virtualization.framework for x86_64 binary translation # on Apple Silicon. Rosetta/binfmt emulation is enabled by default in # Colima >= 0.8 when using --vm-type vz, which is dramatically faster than From 47f4e52cc70f571a150a723a7a106dc6ec74e1a6 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:05:40 -0300 Subject: [PATCH 150/298] Switch SDK rollForward from disable to patch for automatic servicing updates (#4364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Switch SDK rollForward from disable to patch Allow automatic roll-forward within the 10.0.3xx feature band so that servicing patches (e.g. security fixes like 10.0.301) are picked up by CI agents and developers without requiring a dedicated PR for each bump. The base version remains 10.0.300 — this is the minimum required SDK. Any installed 10.0.3xx patch will be used automatically. Updated both global.json files: - /global.json (repo root) - /tools/PackageCompatibility/global.json * Re-worded SDK comment. --- global.json | 5 +++-- tools/PackageCompatibility/global.json | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/global.json b/global.json index 5d6e4f2f39..650a5c0ddf 100644 --- a/global.json +++ b/global.json @@ -4,8 +4,9 @@ // We currently require the .NET 10 SDK to build and test the project. "version": "10.0.300", - // Lock to the above version so that we can control when we take breaking sdk changes. - "rollForward": "disable", + // Allow roll-forward within the 10.0.3xx feature band so servicing patches + // are picked up automatically without requiring a PR for each bump. + "rollForward": "patch", // Do not allow pre-release versions. // diff --git a/tools/PackageCompatibility/global.json b/tools/PackageCompatibility/global.json index 7c7fca5caa..af3f080d1f 100644 --- a/tools/PackageCompatibility/global.json +++ b/tools/PackageCompatibility/global.json @@ -7,9 +7,9 @@ "sdk": { // global.json lookup stops at the nearest file and does not merge with the repo-root file. // This subtree therefore needs to repeat the root SDK settings so commands run from - // tools/PackageCompatibility keep using the same pinned SDK behavior. + // tools/PackageCompatibility keep using the same SDK behavior. "version": "10.0.300", - "rollForward": "disable", + "rollForward": "patch", "allowPrerelease": false } } From df09e3a9b9aa766ba211ab7e910822a4540add2d Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 17 Jun 2026 08:17:35 -0300 Subject: [PATCH 151/298] Replace Newtonsoft.Json with System.Text.Json in tests and samples (#4323) --- Directory.Packages.props | 1 - .../AzureKeyVaultProviderLegacyExample_2_0.cs | 380 ------------------ .../Microsoft.Data.SqlClient.Samples.csproj | 1 - ...soft.Data.SqlClient.FunctionalTests.csproj | 2 - .../tests/FunctionalTests/SqlExceptionTest.cs | 59 +-- ...icrosoft.Data.SqlClient.ManualTests.csproj | 2 - .../SQL/JsonTest/JsonBulkCopyTest.cs | 21 +- .../SQL/JsonTest/JsonStreamTest.cs | 19 +- .../SQL/JsonTest/JsonTestHelper.cs | 86 ++++ 9 files changed, 119 insertions(+), 452 deletions(-) delete mode 100644 doc/samples/AzureKeyVaultProviderLegacyExample_2_0.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTestHelper.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index c35ad35a59..70a2c00ac6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -96,7 +96,6 @@ - diff --git a/doc/samples/AzureKeyVaultProviderLegacyExample_2_0.cs b/doc/samples/AzureKeyVaultProviderLegacyExample_2_0.cs deleted file mode 100644 index d397de6441..0000000000 --- a/doc/samples/AzureKeyVaultProviderLegacyExample_2_0.cs +++ /dev/null @@ -1,380 +0,0 @@ -/** - * TODO: This sample file should be deleted as the AKV Provider Ctor API is no longer supported with supported versions of AKV provider and MDS. - * Depends on: Delete documentation and sample reference in MS Docs first: https://learn.microsoft.com/en-us/sql/connect/ado-net/sql/azure-key-vault-example?view=sql-server-ver17#legacy-callback-implementation-design-example-with-v20 - * - -//< Snippet1> -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Net.Http; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core; -using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Microsoft.Data.SqlClient.Samples -{ - public class AzureKeyVaultProviderLegacyExample_2_0 - { - const string s_algorithm = "RSA_OAEP"; - - // ********* Provide details here *********** - static readonly string s_akvUrl = "https://{KeyVaultName}.vault.azure.net/keys/{Key}/{KeyIdentifier}"; - static readonly string s_clientId = "{Application_Client_ID}"; - static readonly string s_clientSecret = "{Application_Client_Secret}"; - static readonly string s_connectionString = "Server={Server}; Database={database}; Integrated Security=true; Column Encryption Setting=Enabled;"; - // ****************************************** - - public static void Main() - { - // Initialize AKV provider - SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new LegacyAuthCallbackTokenCredential()); - - // Register AKV provider - SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders: new Dictionary(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase) - { - { SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, akvProvider} - }); - Console.WriteLine("AKV provider Registered"); - - // Create connection to database - using (SqlConnection sqlConnection = new SqlConnection(s_connectionString)) - { - string cmkName = "CMK_WITH_AKV"; - string cekName = "CEK_WITH_AKV"; - string tblName = "AKV_TEST_TABLE"; - - CustomerRecord customer = new CustomerRecord(1, @"Microsoft", @"Corporation"); - - try - { - sqlConnection.Open(); - - // Drop Objects if exists - dropObjects(sqlConnection, cmkName, cekName, tblName); - - // Create Column Master Key with AKV Url - createCMK(sqlConnection, cmkName); - Console.WriteLine("Column Master Key created."); - - // Create Column Encryption Key - createCEK(sqlConnection, cmkName, cekName, akvProvider); - Console.WriteLine("Column Encryption Key created."); - - // Create Table with Encrypted Columns - createTbl(sqlConnection, cekName, tblName); - Console.WriteLine("Table created with Encrypted columns."); - - // Insert Customer Record in table - insertData(sqlConnection, tblName, customer); - Console.WriteLine("Encryted data inserted."); - - // Read data from table - verifyData(sqlConnection, tblName, customer); - Console.WriteLine("Data validated successfully."); - } - finally - { - // Drop table and keys - dropObjects(sqlConnection, cmkName, cekName, tblName); - Console.WriteLine("Dropped Table, CEK and CMK"); - } - - Console.WriteLine("Completed AKV provider Sample."); - } - } - - private static void createCMK(SqlConnection sqlConnection, string cmkName) - { - string KeyStoreProviderName = SqlColumnEncryptionAzureKeyVaultProvider.ProviderName; - - string sql = - $@"CREATE COLUMN MASTER KEY [{cmkName}] - WITH ( - KEY_STORE_PROVIDER_NAME = N'{KeyStoreProviderName}', - KEY_PATH = N'{s_akvUrl}' - );"; - - using (SqlCommand command = sqlConnection.CreateCommand()) - { - command.CommandText = sql; - command.ExecuteNonQuery(); - } - } - - private static void createCEK(SqlConnection sqlConnection, string cmkName, string cekName, SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider) - { - string sql = - $@"CREATE COLUMN ENCRYPTION KEY [{cekName}] - WITH VALUES ( - COLUMN_MASTER_KEY = [{cmkName}], - ALGORITHM = '{s_algorithm}', - ENCRYPTED_VALUE = {GetEncryptedValue(sqlColumnEncryptionAzureKeyVaultProvider)} - )"; - - using (SqlCommand command = sqlConnection.CreateCommand()) - { - command.CommandText = sql; - command.ExecuteNonQuery(); - } - } - - private static string GetEncryptedValue(SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider) - { - byte[] plainTextColumnEncryptionKey = new byte[32]; - RandomNumberGenerator rng = RandomNumberGenerator.Create(); - rng.GetBytes(plainTextColumnEncryptionKey); - - byte[] encryptedColumnEncryptionKey = sqlColumnEncryptionAzureKeyVaultProvider.EncryptColumnEncryptionKey(s_akvUrl, s_algorithm, plainTextColumnEncryptionKey); - string EncryptedValue = string.Concat("0x", BitConverter.ToString(encryptedColumnEncryptionKey).Replace("-", string.Empty)); - return EncryptedValue; - } - - private static void createTbl(SqlConnection sqlConnection, string cekName, string tblName) - { - string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA_256"; - - string sql = - $@"CREATE TABLE [dbo].[{tblName}] - ( - [CustomerId] [int] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'), - [FirstName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'), - [LastName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}') - )"; - - using (SqlCommand command = sqlConnection.CreateCommand()) - { - command.CommandText = sql; - command.ExecuteNonQuery(); - } - } - - private static void insertData(SqlConnection sqlConnection, string tblName, CustomerRecord customer) - { - string insertSql = $"INSERT INTO [{tblName}] (CustomerId, FirstName, LastName) VALUES (@CustomerId, @FirstName, @LastName);"; - - using (SqlTransaction sqlTransaction = sqlConnection.BeginTransaction()) - using (SqlCommand sqlCommand = new SqlCommand(insertSql, - connection: sqlConnection, transaction: sqlTransaction, - columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled)) - { - sqlCommand.Parameters.AddWithValue(@"CustomerId", customer.Id); - sqlCommand.Parameters.AddWithValue(@"FirstName", customer.FirstName); - sqlCommand.Parameters.AddWithValue(@"LastName", customer.LastName); - - sqlCommand.ExecuteNonQuery(); - sqlTransaction.Commit(); - } - } - - private static void verifyData(SqlConnection sqlConnection, string tblName, CustomerRecord customer) - { - // Test INPUT parameter on an encrypted parameter - using (SqlCommand sqlCommand = new SqlCommand($"SELECT CustomerId, FirstName, LastName FROM [{tblName}] WHERE FirstName = @firstName", - sqlConnection)) - { - SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); - customerFirstParam.Direction = System.Data.ParameterDirection.Input; - customerFirstParam.ForceColumnEncryption = true; - - using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader()) - { - ValidateResultSet(sqlDataReader); - } - } - } - - private static void ValidateResultSet(SqlDataReader sqlDataReader) - { - Console.WriteLine(" * Row available: " + sqlDataReader.HasRows); - - while (sqlDataReader.Read()) - { - if (sqlDataReader.GetInt32(0) == 1) - { - Console.WriteLine(" * Employee Id received as sent: " + sqlDataReader.GetInt32(0)); - } - else - { - Console.WriteLine("Employee Id didn't match"); - } - - if (sqlDataReader.GetString(1) == @"Microsoft") - { - Console.WriteLine(" * Employee Firstname received as sent: " + sqlDataReader.GetString(1)); - } - else - { - Console.WriteLine("Employee FirstName didn't match."); - } - - if (sqlDataReader.GetString(2) == @"Corporation") - { - Console.WriteLine(" * Employee LastName received as sent: " + sqlDataReader.GetString(2)); - } - else - { - Console.WriteLine("Employee LastName didn't match."); - } - } - } - - private static void dropObjects(SqlConnection sqlConnection, string cmkName, string cekName, string tblName) - { - using (SqlCommand cmd = sqlConnection.CreateCommand()) - { - cmd.CommandText = $@"IF EXISTS (select * from sys.objects where name = '{tblName}') BEGIN DROP TABLE [{tblName}] END"; - cmd.ExecuteNonQuery(); - cmd.CommandText = $@"IF EXISTS (select * from sys.column_encryption_keys where name = '{cekName}') BEGIN DROP COLUMN ENCRYPTION KEY [{cekName}] END"; - cmd.ExecuteNonQuery(); - cmd.CommandText = $@"IF EXISTS (select * from sys.column_master_keys where name = '{cmkName}') BEGIN DROP COLUMN MASTER KEY [{cmkName}] END"; - cmd.ExecuteNonQuery(); - } - } - - private class CustomerRecord - { - internal int Id { get; set; } - internal string FirstName { get; set; } - internal string LastName { get; set; } - - public CustomerRecord(int id, string fName, string lName) - { - Id = id; - FirstName = fName; - LastName = lName; - } - } - - private class LegacyAuthCallbackTokenCredential : TokenCredential - { - string _authority = ""; - string _resource = ""; - string _akvUrl = ""; - - public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) => - AcquireTokenAsync().GetAwaiter().GetResult(); - - public override async ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) => - await AcquireTokenAsync(); - - private async Task AcquireTokenAsync() - { - // Added to reduce HttpClient calls. - // For multi-user support, a better design can be implemented as needed. - if (_akvUrl != s_akvUrl) - { - using (HttpClient httpClient = new HttpClient()) - { - HttpResponseMessage response = await httpClient.GetAsync(s_akvUrl); - string challenge = response?.Headers.WwwAuthenticate.FirstOrDefault()?.ToString(); - string trimmedChallenge = ValidateChallenge(challenge); - string[] pairs = trimmedChallenge.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); - - if (pairs != null && pairs.Length > 0) - { - for (int i = 0; i < pairs.Length; i++) - { - string[] pair = pairs[i]?.Split('='); - - if (pair.Length == 2) - { - string key = pair[0]?.Trim().Trim(new char[] { '\"' }); - string value = pair[1]?.Trim().Trim(new char[] { '\"' }); - - if (!string.IsNullOrEmpty(key)) - { - if (key.Equals("authorization", StringComparison.InvariantCultureIgnoreCase)) - { - _authority = value; - } - else if (key.Equals("resource", StringComparison.InvariantCultureIgnoreCase)) - { - _resource = value; - } - } - } - } - } - } - _akvUrl = s_akvUrl; - } - - string strAccessToken = await AzureActiveDirectoryAuthenticationCallback(_authority, _resource); - DateTime expiryTime = InterceptAccessTokenForExpiry(strAccessToken); - return new AccessToken(strAccessToken, new DateTimeOffset(expiryTime)); - } - - private DateTime InterceptAccessTokenForExpiry(string accessToken) - { - if (null == accessToken) - { - throw new ArgumentNullException(accessToken); - } - - var jwtHandler = new JwtSecurityTokenHandler(); - var jwtOutput = string.Empty; - - // Check Token Format - if (!jwtHandler.CanReadToken(accessToken)) - throw new FormatException(accessToken); - - JwtSecurityToken token = jwtHandler.ReadJwtToken(accessToken); - - // Re-serialize the Token Headers to just Key and Values - var jwtHeader = JsonConvert.SerializeObject(token.Header.Select(h => new { h.Key, h.Value })); - jwtOutput = $"{{\r\n\"Header\":\r\n{JToken.Parse(jwtHeader)},"; - - // Re-serialize the Token Claims to just Type and Values - var jwtPayload = JsonConvert.SerializeObject(token.Claims.Select(c => new { c.Type, c.Value })); - jwtOutput += $"\r\n\"Payload\":\r\n{JToken.Parse(jwtPayload)}\r\n}}"; - - // Output the whole thing to pretty JSON object formatted. - string jToken = JToken.Parse(jwtOutput).ToString(Formatting.Indented); - JToken payload = JObject.Parse(jToken).GetValue("Payload"); - - return new DateTime(1970, 1, 1).AddSeconds((long)payload[4]["Value"]); - } - - private static string ValidateChallenge(string challenge) - { - string Bearer = "Bearer "; - if (string.IsNullOrEmpty(challenge)) - throw new ArgumentNullException(nameof(challenge)); - - string trimmedChallenge = challenge.Trim(); - - if (!trimmedChallenge.StartsWith(Bearer)) - throw new ArgumentException("Challenge is not Bearer", nameof(challenge)); - - return trimmedChallenge.Substring(Bearer.Length); - } - - /// - /// Legacy implementation of Authentication Callback, used by Azure Key Vault provider 1.0. - /// This can be leveraged to support multi-user authentication support in the same Azure Key Vault Provider. - /// - /// Authorization URL - /// Resource - /// - public static async Task AzureActiveDirectoryAuthenticationCallback(string authority, string resource) - { - var authContext = new AuthenticationContext(authority); - ClientCredential clientCred = new ClientCredential(s_clientId, s_clientSecret); - AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred); - if (result == null) - { - throw new InvalidOperationException($"Failed to retrieve an access token for {resource}"); - } - return result.AccessToken; - } - } - } -} -// -*/ diff --git a/doc/samples/Microsoft.Data.SqlClient.Samples.csproj b/doc/samples/Microsoft.Data.SqlClient.Samples.csproj index 44a153f6f2..1887c9b73e 100644 --- a/doc/samples/Microsoft.Data.SqlClient.Samples.csproj +++ b/doc/samples/Microsoft.Data.SqlClient.Samples.csproj @@ -23,7 +23,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 13673697db..9098091ad8 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -67,7 +67,6 @@ - @@ -92,7 +91,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs index 2fe9e63aed..421bba1c71 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs @@ -3,9 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; -using Newtonsoft.Json; +using System.Text.Json; using Xunit; namespace Microsoft.Data.SqlClient.Tests @@ -18,46 +16,27 @@ public class SqlExceptionTest public void SerializationTest() { SqlException e = CreateException(); - string json = JsonConvert.SerializeObject(e); - var settings = new JsonSerializerSettings(); - var sqlEx = JsonConvert.DeserializeObject(json, settings); - - Assert.Equal(e.ClientConnectionId, sqlEx.ClientConnectionId); - Assert.Equal(e.StackTrace, sqlEx.StackTrace); - } + // Serialize the properties we want to validate round-trip through JSON. + // SqlException cannot be directly serialized by System.Text.Json because + // Exception.TargetSite (MethodBase) is not supported. + string json = JsonSerializer.Serialize(new + { + e.Message, + ClientConnectionId = e.ClientConnectionId.ToString(), + e.Number, + e.Class, + e.State, + }); - [Fact] - public void JSONSerializationTest() - { - string clientConnectionId = "90cdab4d-2145-4c24-a354-c8ccff903542"; - 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""," - + @"""HelpLink.EvtID"":""0""," - + @"""HelpLink.BaseHelpUrl"":""http://go.microsoft.com/fwlink""," - + @"""HelpLink.LinkId"":""20476""," - + @"""SqlError 1"":""Microsoft.Data.SqlClient.SqlError: 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)""," - + @"""$type"":""System.Collections.ListDictionaryInternal, System.Private.CoreLib""}," - + @"""InnerException"":null," - + @"""HelpURL"":null," - + @"""StackTraceString"":"" at Microsoft.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken)\\n at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, SqlConnectionOptions userOptions)\\n at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, SqlConnectionOptions options, DbConnectionPoolKey poolKey, SqlConnectionOptions userOptions)\\n at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, SqlConnectionOptions userOptions, DbConnectionInternal oldConnection)\\n at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, SqlConnectionOptions userOptions, DbConnectionInternal& connection)\\n at System.Data.ProviderBase.DbConnectionPool.WaitForPendingOpen()\\n""," - + @"""RemoteStackTraceString"":null," - + @"""RemoteStackIndex"":0," - + @"""ExceptionMethod"":null," - + @"""HResult"":-2146232060," - + @"""Source"":""Core .Net SqlClient Data Provider""," - + @"""WatsonBuckets"":null," - + @"""Errors"":null," - + @"""ClientConnectionId"":""90cdab4d-2145-4c24-a354-c8ccff903542""" - + @"}"; + using JsonDocument doc = JsonDocument.Parse(json); + JsonElement root = doc.RootElement; - var settings = new JsonSerializerSettings(); - var sqlEx = JsonConvert.DeserializeObject(json, settings); - Assert.IsType(sqlEx); - Assert.Equal(clientConnectionId, sqlEx.ClientConnectionId.ToString()); + Assert.Equal(e.Message, root.GetProperty("Message").GetString()); + Assert.Equal(e.ClientConnectionId.ToString(), root.GetProperty("ClientConnectionId").GetString()); + Assert.Equal(e.Number, root.GetProperty("Number").GetInt32()); + Assert.Equal(e.Class, root.GetProperty("Class").GetByte()); + Assert.Equal(e.State, root.GetProperty("State").GetByte()); } private static SqlException CreateException() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index ce159f7821..4b0dfb6ddd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -96,7 +96,6 @@ - @@ -124,7 +123,6 @@ - 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 e9ad8be557..4e5fa29c7d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs @@ -6,12 +6,11 @@ using System.Collections.Generic; using System.Data; using System.IO; +using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json; -using Xunit.Abstractions; using Xunit; -using System.Collections; +using Xunit.Abstractions; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.JsonTest { @@ -68,7 +67,7 @@ private void GenerateJsonFile(int noOfRecords, string filename) }); } - string json = JsonConvert.SerializeObject(records, Formatting.Indented); + string json = JsonSerializer.Serialize(records, new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); File.WriteAllText(filename, json); Assert.True(File.Exists(filename)); _output.WriteLine("Generated JSON file " + filename); @@ -76,15 +75,9 @@ private void GenerateJsonFile(int noOfRecords, string filename) private void CompareJsonFiles() { - using (var stream1 = File.OpenText(_generatedJsonFile)) - using (var stream2 = File.OpenText(_outputFile)) - using (var reader1 = new JsonTextReader(stream1)) - using (var reader2 = new JsonTextReader(stream2)) - { - var jToken1 = JToken.ReadFrom(reader1); - var jToken2 = JToken.ReadFrom(reader2); - Assert.True(JToken.DeepEquals(jToken1, jToken2)); - } + using JsonDocument doc1 = JsonDocument.Parse(File.ReadAllText(_generatedJsonFile)); + using JsonDocument doc2 = JsonDocument.Parse(File.ReadAllText(_outputFile)); + Assert.True(JsonTestHelper.JsonDeepEquals(doc1.RootElement, doc2.RootElement)); } private void PrintJsonDataToFileAndCompare(SqlConnection connection) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs index eef2902932..4aa92e28ef 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonStreamTest.cs @@ -8,11 +8,12 @@ using System.Data; using System.Linq; using System.Text; +using System.Text.Encodings.Web; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json; using Xunit; using Xunit.Abstractions; -using Newtonsoft.Json.Linq; +using Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.JsonTest; using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; @@ -52,7 +53,7 @@ private void GenerateJsonFile(int noOfRecords, string filename) }); } - string json = JsonConvert.SerializeObject(records, Formatting.Indented); + string json = JsonSerializer.Serialize(records, new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); File.WriteAllText(filename, json); Assert.True(File.Exists(filename)); _output.WriteLine("Generated JSON file "+filename); @@ -60,15 +61,9 @@ private void GenerateJsonFile(int noOfRecords, string filename) private void CompareJsonFiles() { - using (var stream1 = File.OpenText(_jsonFile)) - using (var stream2 = File.OpenText(_outputFile)) - using (var reader1 = new JsonTextReader(stream1)) - using (var reader2 = new JsonTextReader(stream2)) - { - var jToken1 = JToken.ReadFrom(reader1); - var jToken2 = JToken.ReadFrom(reader2); - Assert.True(JToken.DeepEquals(jToken1, jToken2)); - } + using JsonDocument doc1 = JsonDocument.Parse(File.ReadAllText(_jsonFile)); + using JsonDocument doc2 = JsonDocument.Parse(File.ReadAllText(_outputFile)); + Assert.True(JsonTestHelper.JsonDeepEquals(doc1.RootElement, doc2.RootElement)); } private void PrintJsonDataToFile(SqlConnection connection, string tableName) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTestHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTestHelper.cs new file mode 100644 index 0000000000..03d9841610 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTestHelper.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. + +using System; +using System.Linq; +using System.Text.Json; +#if NET9_0_OR_GREATER +using System.Text.Json.Nodes; +#endif + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.JsonTest +{ + internal static class JsonTestHelper + { + // Test data is Array → Object → Value (3 levels). Use 64 as a safe ceiling. + private const int MaxDepth = 64; + + /// + /// Performs a deep structural comparison of two values. + /// On .NET 9+ this delegates to ; on earlier + /// runtimes it uses a recursive comparison over the element trees. + /// + internal static bool JsonDeepEquals(JsonElement a, JsonElement b) + { +#if NET9_0_OR_GREATER + return JsonNode.DeepEquals( + JsonNode.Parse(a.GetRawText()), + JsonNode.Parse(b.GetRawText())); +#else + return DeepEqualsCore(a, b, depth: 0); +#endif + } + + private static bool DeepEqualsCore(JsonElement a, JsonElement b, int depth) + { + if (depth > MaxDepth) + { + throw new InvalidOperationException($"JSON comparison exceeded maximum depth of {MaxDepth}."); + } + + if (a.ValueKind != b.ValueKind) + { + return false; + } + + switch (a.ValueKind) + { + case JsonValueKind.Object: + int countA = a.EnumerateObject().Count(); + int countB = b.EnumerateObject().Count(); + if (countA != countB) + { + return false; + } + foreach (JsonProperty prop in a.EnumerateObject()) + { + if (!b.TryGetProperty(prop.Name, out JsonElement bValue) || + !DeepEqualsCore(prop.Value, bValue, depth + 1)) + { + return false; + } + } + return true; + + case JsonValueKind.Array: + JsonElement.ArrayEnumerator arrA = a.EnumerateArray(); + JsonElement.ArrayEnumerator arrB = b.EnumerateArray(); + while (arrA.MoveNext()) + { + if (!arrB.MoveNext() || !DeepEqualsCore(arrA.Current, arrB.Current, depth + 1)) + { + return false; + } + } + return !arrB.MoveNext(); + + case JsonValueKind.String: + return a.GetString() == b.GetString(); + + default: + return a.GetRawText() == b.GetRawText(); + } + } + } +} From 9deb1daf88205093126549b0a413c6fbd15fc72d Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 17 Jun 2026 08:57:50 -0300 Subject: [PATCH 152/298] Fix stress pipeline pool name detection and config file mismatch (#4368) --- eng/pipelines/stress/stress-tests-job.yml | 4 ++-- eng/pipelines/stress/stress-tests-pipeline.yml | 12 ++++++++++++ eng/pipelines/stress/stress-tests-stage.yml | 17 ++++++----------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/eng/pipelines/stress/stress-tests-job.yml b/eng/pipelines/stress/stress-tests-job.yml index a02a0a4f4b..dd75602d6f 100644 --- a/eng/pipelines/stress/stress-tests-job.yml +++ b/eng/pipelines/stress/stress-tests-job.yml @@ -210,7 +210,7 @@ jobs: condition: and(succeededOrFailed(), eq(variables['buildSucceeded'], 'true')) continueOnError: ${{ parameters.warnOnTestFailure }} env: - STRESS_CONFIG_FILE: config.json + STRESS_CONFIG_FILE: config.jsonc inputs: command: run projects: $(project) @@ -223,7 +223,7 @@ jobs: condition: and(succeededOrFailed(), eq(variables['buildSucceeded'], 'true')) continueOnError: ${{ parameters.warnOnTestFailure }} env: - STRESS_CONFIG_FILE: config.json + STRESS_CONFIG_FILE: config.jsonc inputs: command: run projects: $(project) diff --git a/eng/pipelines/stress/stress-tests-pipeline.yml b/eng/pipelines/stress/stress-tests-pipeline.yml index edd962b911..c1eb87faab 100644 --- a/eng/pipelines/stress/stress-tests-pipeline.yml +++ b/eng/pipelines/stress/stress-tests-pipeline.yml @@ -92,6 +92,17 @@ parameters: - detailed - diagnostic +# Pipeline-level variables evaluated at compile time. +variables: + # The 1ES pool name, determined automatically by ADO project: + # - ADO.Net -> ADO-1ES-Pool + # - public -> ADO-CI-1ES-Pool + - name: poolName + ${{ if eq(variables['System.TeamProject'], 'ADO.Net') }}: + value: ADO-1ES-Pool + ${{ else }}: + value: ADO-CI-1ES-Pool + # The stages to run. stages: @@ -105,5 +116,6 @@ stages: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} + poolName: ${{ variables.poolName }} warnOnTestFailure: ${{ parameters.warnOnTestFailure }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} diff --git a/eng/pipelines/stress/stress-tests-stage.yml b/eng/pipelines/stress/stress-tests-stage.yml index 0ac192a601..1f223c5142 100644 --- a/eng/pipelines/stress/stress-tests-stage.yml +++ b/eng/pipelines/stress/stress-tests-stage.yml @@ -57,6 +57,10 @@ parameters: type: object default: [net8.0, net9.0, net10.0] + # The 1ES pool name for Linux and Windows jobs. macOS always uses 'Azure Pipelines'. + - name: poolName + type: string + stages: - stage: stress_tests_stage displayName: Run Stress Tests @@ -68,15 +72,6 @@ stages: - name: saPassword value: $[stageDependencies.secrets_stage.secrets_job.outputs['SaPassword.Value']] - # The 1ES pool name, determined automatically by ADO project: - # - ADO.Net -> ADO-1ES-Pool - # - public -> ADO-CI-1ES-Pool - - name: poolName - ${{ if eq(variables['System.TeamProject'], 'ADO.Net') }}: - value: ADO-1ES-Pool - ${{ else }}: - value: ADO-CI-1ES-Pool - jobs: # ---------------------------------------------------------------------------------------------- @@ -93,7 +88,7 @@ stages: # No .NET Framework runtimes on Linux. netFrameworkTestRuntimes: [] netTestRuntimes: ${{ parameters.netTestRuntimes }} - poolName: ${{ variables.poolName }} + poolName: ${{ parameters.poolName }} saPassword: $(saPassword) sqlSetupStep: template: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml@self @@ -115,7 +110,7 @@ stages: # Note that we include the .NET Framework runtimes for test runs on Windows. netFrameworkTestRuntimes: ${{ parameters.netFrameworkTestRuntimes }} netTestRuntimes: ${{ parameters.netTestRuntimes }} - poolName: ${{ variables.poolName }} + poolName: ${{ parameters.poolName }} saPassword: $(saPassword) sqlSetupStep: template: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml@self From 6a0153aa32b1689b78a8b8e3212b1515ad5bd516 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 18 Jun 2026 02:32:02 -0700 Subject: [PATCH 153/298] Fix SqlCommand.Cancell null ref exception (#4372) --- .../src/Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- .../tests/FunctionalTests/SqlCommandTest.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index ec26b955e1..76399749a0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -1068,7 +1068,7 @@ public override void Cancel() "SqlCommand.Cancel | API | Correlation | " + $"Object Id {ObjectID}, " + $"Activity Id {ActivityCorrelator.Current}, " + - $"Client Connection Id {_activeConnection.ClientConnectionId}, " + + $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); SqlStatistics statistics = null; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs index 80ceba25af..59f35b4941 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs @@ -562,6 +562,20 @@ public void ParameterCollectionTest() } } + /// + /// Verifies that SqlCommand.Cancel() is a no-op when Connection is null, + /// rather than throwing a NullReferenceException. Regression test for #4327. + /// + [Fact] + public void Cancel_WithNullConnection_DoesNotThrow() + { + using SqlCommand cmd = new SqlCommand(); + Assert.Null(cmd.Connection); + + // Should be a no-op, not throw NullReferenceException + cmd.Cancel(); + } + private static SqlConnection GetNonConnectingConnection() => new SqlConnection("Initial Catalog=a;Server=b;User ID=c;Password=d"); } From c0f341ad0020aa5e7d58958f8d7a4cc20e3553eb Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:56:52 -0700 Subject: [PATCH 154/298] Enable WAM Broker support for Entra ID Auth modes (#4288) --- Directory.Packages.props | 3 +- .../AzureSqlConnector.csproj | 34 + doc/apps/AzureSqlConnector/IdentityQuery.cs | 25 + .../AzureSqlConnector/MainForm.Designer.cs | 394 ++++++++++++ doc/apps/AzureSqlConnector/MainForm.cs | 590 +++++++++++++++++ .../MainFormWorker.Designer.cs | 383 +++++++++++ doc/apps/AzureSqlConnector/MainFormWorker.cs | 598 ++++++++++++++++++ .../AzureSqlConnector/ModeSelectorForm.cs | 116 ++++ doc/apps/AzureSqlConnector/Program.cs | 39 ++ doc/apps/AzureSqlConnector/README.md | 137 ++++ .../ActiveDirectoryAuthenticationProvider.xml | 14 +- .../src/SqlAuthenticationProvider.Internal.cs | 2 +- .../ActiveDirectoryAuthenticationProvider.xml | 49 +- ...DirectoryAuthenticationProviderOptions.xml | 34 + ...DirectoryAuthenticationProvider.Windows.cs | 108 ++++ .../ActiveDirectoryAuthenticationProvider.cs | 184 +++++- ...eDirectoryAuthenticationProviderOptions.cs | 21 + .../Azure/src/Azure.csproj | 1 + .../Azure/src/Interop/Interop.GetAncestor.cs | 42 ++ .../src/Interop/Interop.GetConsoleWindow.cs | 27 + .../Azure/test/AADAuthenticationTests.cs | 1 + .../Azure/test/AADConnectionTest.cs | 114 ---- .../Azure/test/Config.cs | 1 + .../Azure/test/DefaultAuthProviderTests.cs | 1 + .../SqlAuthenticationProviderCollection.cs | 14 + .../Azure/test/WamBrokerTests.cs | 316 +++++++++ src/Microsoft.Data.SqlClient.slnx | 3 + .../Connection/SqlConnectionInternal.cs | 9 +- .../SqlAuthenticationProviderManager.cs | 147 ++++- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 5 + .../src/Resources/Strings.Designer.cs | 9 + .../src/Resources/Strings.resx | 3 + .../SqlAuthenticationProviderManagerTests.cs | 196 ++++++ 33 files changed, 3451 insertions(+), 169 deletions(-) create mode 100644 doc/apps/AzureSqlConnector/AzureSqlConnector.csproj create mode 100644 doc/apps/AzureSqlConnector/IdentityQuery.cs create mode 100644 doc/apps/AzureSqlConnector/MainForm.Designer.cs create mode 100644 doc/apps/AzureSqlConnector/MainForm.cs create mode 100644 doc/apps/AzureSqlConnector/MainFormWorker.Designer.cs create mode 100644 doc/apps/AzureSqlConnector/MainFormWorker.cs create mode 100644 doc/apps/AzureSqlConnector/ModeSelectorForm.cs create mode 100644 doc/apps/AzureSqlConnector/Program.cs create mode 100644 doc/apps/AzureSqlConnector/README.md create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProviderOptions.xml create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.Windows.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProviderOptions.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/src/Interop/Interop.GetAncestor.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/src/Interop/Interop.GetConsoleWindow.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/test/SqlAuthenticationProviderCollection.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/test/WamBrokerTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 70a2c00ac6..59839f2351 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -95,7 +95,7 @@ - + @@ -126,6 +126,7 @@ + diff --git a/doc/apps/AzureSqlConnector/AzureSqlConnector.csproj b/doc/apps/AzureSqlConnector/AzureSqlConnector.csproj new file mode 100644 index 0000000000..0ea12bd7d2 --- /dev/null +++ b/doc/apps/AzureSqlConnector/AzureSqlConnector.csproj @@ -0,0 +1,34 @@ + + + + + net481;net10.0-windows + net10.0-windows + + true + WinExe + Microsoft.Data.SqlClient.Samples.AzureSqlConnector + AzureSqlConnector + true + latest + disable + AnyCPU + true + false + + + + + + + + diff --git a/doc/apps/AzureSqlConnector/IdentityQuery.cs b/doc/apps/AzureSqlConnector/IdentityQuery.cs new file mode 100644 index 0000000000..6c6cff84a1 --- /dev/null +++ b/doc/apps/AzureSqlConnector/IdentityQuery.cs @@ -0,0 +1,25 @@ +namespace Microsoft.Data.SqlClient.Samples.AzureSqlConnector +{ + /// + /// Shared SQL text used by both (UI-thread variant) and + /// (worker-thread variant). Keeping the literal in one place + /// avoids drift when one variant gains a new column. + /// + internal static class IdentityQuery + { + public const string CommandText = + "SELECT " + + " SUSER_SNAME() AS LoggedInUser, " + + " ORIGINAL_LOGIN() AS OriginalLogin, " + + " USER_NAME() AS DatabaseUser, " + + " SUSER_ID() AS LoginSid, " + + " DB_NAME() AS DatabaseName, " + + " @@SERVERNAME AS ServerName, " + + " HOST_NAME() AS ClientHost, " + + " APP_NAME() AS AppName, " + + " SESSION_USER AS SessionUser, " + + " CURRENT_USER AS CurrentUser, " + + " @@SPID AS SessionId, " + + " @@VERSION AS ServerVersion;"; + } +} diff --git a/doc/apps/AzureSqlConnector/MainForm.Designer.cs b/doc/apps/AzureSqlConnector/MainForm.Designer.cs new file mode 100644 index 0000000000..4dd6c5a047 --- /dev/null +++ b/doc/apps/AzureSqlConnector/MainForm.Designer.cs @@ -0,0 +1,394 @@ +namespace Microsoft.Data.SqlClient.Samples.AzureSqlConnector +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lblServer = new System.Windows.Forms.Label(); + this.txtServer = new System.Windows.Forms.TextBox(); + this.lblDatabase = new System.Windows.Forms.Label(); + this.txtDatabase = new System.Windows.Forms.TextBox(); + this.lblAuthentication = new System.Windows.Forms.Label(); + this.cmbAuthentication = new System.Windows.Forms.ComboBox(); + this.lblUserId = new System.Windows.Forms.Label(); + this.txtUserId = new System.Windows.Forms.TextBox(); + this.lblPassword = new System.Windows.Forms.Label(); + this.txtPassword = new System.Windows.Forms.TextBox(); + this.lblEncrypt = new System.Windows.Forms.Label(); + this.cmbEncrypt = new System.Windows.Forms.ComboBox(); + this.chkTrustServerCertificate = new System.Windows.Forms.CheckBox(); + this.lblTimeout = new System.Windows.Forms.Label(); + this.numTimeout = new System.Windows.Forms.NumericUpDown(); + this.lblOpenMode = new System.Windows.Forms.Label(); + this.cmbOpenMode = new System.Windows.Forms.ComboBox(); + this.lblConnectionString = new System.Windows.Forms.Label(); + this.txtConnectionString = new System.Windows.Forms.TextBox(); + this.btnBuild = new System.Windows.Forms.Button(); + this.btnTest = new System.Windows.Forms.Button(); + this.btnCopy = new System.Windows.Forms.Button(); + this.btnClear = new System.Windows.Forms.Button(); + this.btnWhoAmI = new System.Windows.Forms.Button(); + this.lblStatus = new System.Windows.Forms.Label(); + this.txtStatus = new System.Windows.Forms.TextBox(); + this.statusStrip = new System.Windows.Forms.StatusStrip(); + this.statusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + ((System.ComponentModel.ISupportInitialize)(this.numTimeout)).BeginInit(); + this.statusStrip.SuspendLayout(); + this.SuspendLayout(); + // + // lblServer + // + this.lblServer.AutoSize = true; + this.lblServer.Location = new System.Drawing.Point(16, 18); + this.lblServer.Name = "lblServer"; + this.lblServer.Size = new System.Drawing.Size(75, 13); + this.lblServer.TabIndex = 0; + this.lblServer.Text = "&Server name:"; + // + // txtServer + // + this.txtServer.Location = new System.Drawing.Point(150, 15); + this.txtServer.Name = "txtServer"; + this.txtServer.Size = new System.Drawing.Size(400, 20); + this.txtServer.TabIndex = 1; + // + // lblDatabase + // + this.lblDatabase.AutoSize = true; + this.lblDatabase.Location = new System.Drawing.Point(16, 48); + this.lblDatabase.Name = "lblDatabase"; + this.lblDatabase.Size = new System.Drawing.Size(86, 13); + this.lblDatabase.TabIndex = 2; + this.lblDatabase.Text = "&Database name:"; + // + // txtDatabase + // + this.txtDatabase.Location = new System.Drawing.Point(150, 45); + this.txtDatabase.Name = "txtDatabase"; + this.txtDatabase.Size = new System.Drawing.Size(400, 20); + this.txtDatabase.TabIndex = 3; + // + // lblAuthentication + // + this.lblAuthentication.AutoSize = true; + this.lblAuthentication.Location = new System.Drawing.Point(16, 78); + this.lblAuthentication.Name = "lblAuthentication"; + this.lblAuthentication.Size = new System.Drawing.Size(80, 13); + this.lblAuthentication.TabIndex = 4; + this.lblAuthentication.Text = "&Authentication:"; + // + // cmbAuthentication + // + this.cmbAuthentication.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbAuthentication.FormattingEnabled = true; + this.cmbAuthentication.Location = new System.Drawing.Point(150, 75); + this.cmbAuthentication.Name = "cmbAuthentication"; + this.cmbAuthentication.Size = new System.Drawing.Size(400, 21); + this.cmbAuthentication.TabIndex = 5; + this.cmbAuthentication.SelectedIndexChanged += new System.EventHandler(this.cmbAuthentication_SelectedIndexChanged); + // + // lblUserId + // + this.lblUserId.AutoSize = true; + this.lblUserId.Location = new System.Drawing.Point(16, 108); + this.lblUserId.Name = "lblUserId"; + this.lblUserId.Size = new System.Drawing.Size(45, 13); + this.lblUserId.TabIndex = 6; + this.lblUserId.Text = "&User ID:"; + // + // txtUserId + // + this.txtUserId.Location = new System.Drawing.Point(150, 105); + this.txtUserId.Name = "txtUserId"; + this.txtUserId.Size = new System.Drawing.Size(400, 20); + this.txtUserId.TabIndex = 7; + // + // lblPassword + // + this.lblPassword.AutoSize = true; + this.lblPassword.Location = new System.Drawing.Point(16, 138); + this.lblPassword.Name = "lblPassword"; + this.lblPassword.Size = new System.Drawing.Size(56, 13); + this.lblPassword.TabIndex = 8; + this.lblPassword.Text = "&Password:"; + // + // txtPassword + // + this.txtPassword.Location = new System.Drawing.Point(150, 135); + this.txtPassword.Name = "txtPassword"; + this.txtPassword.Size = new System.Drawing.Size(400, 20); + this.txtPassword.TabIndex = 9; + this.txtPassword.UseSystemPasswordChar = true; + // + // lblEncrypt + // + this.lblEncrypt.AutoSize = true; + this.lblEncrypt.Location = new System.Drawing.Point(16, 168); + this.lblEncrypt.Name = "lblEncrypt"; + this.lblEncrypt.Size = new System.Drawing.Size(46, 13); + this.lblEncrypt.TabIndex = 10; + this.lblEncrypt.Text = "&Encrypt:"; + // + // cmbEncrypt + // + this.cmbEncrypt.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbEncrypt.FormattingEnabled = true; + this.cmbEncrypt.Location = new System.Drawing.Point(150, 165); + this.cmbEncrypt.Name = "cmbEncrypt"; + this.cmbEncrypt.Size = new System.Drawing.Size(200, 21); + this.cmbEncrypt.TabIndex = 11; + // + // chkTrustServerCertificate + // + this.chkTrustServerCertificate.AutoSize = true; + this.chkTrustServerCertificate.Location = new System.Drawing.Point(370, 167); + this.chkTrustServerCertificate.Name = "chkTrustServerCertificate"; + this.chkTrustServerCertificate.Size = new System.Drawing.Size(149, 17); + this.chkTrustServerCertificate.TabIndex = 12; + this.chkTrustServerCertificate.Text = "&Trust server certificate"; + this.chkTrustServerCertificate.UseVisualStyleBackColor = true; + // + // lblTimeout + // + this.lblTimeout.AutoSize = true; + this.lblTimeout.Location = new System.Drawing.Point(16, 198); + this.lblTimeout.Name = "lblTimeout"; + this.lblTimeout.Size = new System.Drawing.Size(101, 13); + this.lblTimeout.TabIndex = 13; + this.lblTimeout.Text = "Connect timeout (s):"; + // + // numTimeout + // + this.numTimeout.Location = new System.Drawing.Point(150, 196); + this.numTimeout.Maximum = new decimal(new int[] { 600, 0, 0, 0 }); + this.numTimeout.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); + this.numTimeout.Name = "numTimeout"; + this.numTimeout.Size = new System.Drawing.Size(80, 20); + this.numTimeout.TabIndex = 14; + this.numTimeout.Value = new decimal(new int[] { 30, 0, 0, 0 }); + // + // lblOpenMode + // + this.lblOpenMode.AutoSize = true; + this.lblOpenMode.Location = new System.Drawing.Point(260, 198); + this.lblOpenMode.Name = "lblOpenMode"; + this.lblOpenMode.Size = new System.Drawing.Size(67, 13); + this.lblOpenMode.TabIndex = 25; + this.lblOpenMode.Text = "&Open mode:"; + // + // cmbOpenMode + // + this.cmbOpenMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbOpenMode.FormattingEnabled = true; + this.cmbOpenMode.Location = new System.Drawing.Point(350, 195); + this.cmbOpenMode.Name = "cmbOpenMode"; + this.cmbOpenMode.Size = new System.Drawing.Size(200, 21); + this.cmbOpenMode.TabIndex = 26; + // + // lblConnectionString + // + this.lblConnectionString.AutoSize = true; + this.lblConnectionString.Location = new System.Drawing.Point(16, 230); + this.lblConnectionString.Name = "lblConnectionString"; + this.lblConnectionString.Size = new System.Drawing.Size(94, 13); + this.lblConnectionString.TabIndex = 15; + this.lblConnectionString.Text = "Connection string:"; + // + // txtConnectionString + // + this.txtConnectionString.Location = new System.Drawing.Point(16, 246); + this.txtConnectionString.Multiline = true; + this.txtConnectionString.Name = "txtConnectionString"; + this.txtConnectionString.ReadOnly = true; + this.txtConnectionString.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtConnectionString.Size = new System.Drawing.Size(534, 60); + this.txtConnectionString.TabIndex = 16; + this.txtConnectionString.BackColor = System.Drawing.SystemColors.Info; + // + // btnBuild + // + this.btnBuild.Location = new System.Drawing.Point(16, 316); + this.btnBuild.Name = "btnBuild"; + this.btnBuild.Size = new System.Drawing.Size(140, 26); + this.btnBuild.TabIndex = 17; + this.btnBuild.Text = "&Build Connection String"; + this.btnBuild.UseVisualStyleBackColor = true; + this.btnBuild.Click += new System.EventHandler(this.btnBuild_Click); + // + // btnTest + // + this.btnTest.Location = new System.Drawing.Point(166, 316); + this.btnTest.Name = "btnTest"; + this.btnTest.Size = new System.Drawing.Size(120, 26); + this.btnTest.TabIndex = 18; + this.btnTest.Text = "Te&st Connection"; + this.btnTest.UseVisualStyleBackColor = true; + this.btnTest.Click += new System.EventHandler(this.btnTest_Click); + // + // btnCopy + // + this.btnCopy.Location = new System.Drawing.Point(296, 316); + this.btnCopy.Name = "btnCopy"; + this.btnCopy.Size = new System.Drawing.Size(120, 26); + this.btnCopy.TabIndex = 19; + this.btnCopy.Text = "Cop&y to Clipboard"; + this.btnCopy.UseVisualStyleBackColor = true; + this.btnCopy.Click += new System.EventHandler(this.btnCopy_Click); + // + // btnClear + // + this.btnClear.Location = new System.Drawing.Point(426, 316); + this.btnClear.Name = "btnClear"; + this.btnClear.Size = new System.Drawing.Size(124, 26); + this.btnClear.TabIndex = 20; + this.btnClear.Text = "Cl&ear All"; + this.btnClear.UseVisualStyleBackColor = true; + this.btnClear.Click += new System.EventHandler(this.btnClear_Click); + // + // btnWhoAmI + // + this.btnWhoAmI.Location = new System.Drawing.Point(16, 348); + this.btnWhoAmI.Name = "btnWhoAmI"; + this.btnWhoAmI.Size = new System.Drawing.Size(534, 26); + this.btnWhoAmI.TabIndex = 21; + this.btnWhoAmI.Text = "&Who Am I? (run identity query on the database)"; + this.btnWhoAmI.UseVisualStyleBackColor = true; + this.btnWhoAmI.Click += new System.EventHandler(this.btnWhoAmI_Click); + // + // lblStatus + // + this.lblStatus.AutoSize = true; + this.lblStatus.Location = new System.Drawing.Point(16, 386); + this.lblStatus.Name = "lblStatus"; + this.lblStatus.Size = new System.Drawing.Size(40, 13); + this.lblStatus.TabIndex = 22; + this.lblStatus.Text = "Result:"; + // + // txtStatus + // + this.txtStatus.Location = new System.Drawing.Point(16, 402); + this.txtStatus.Multiline = true; + this.txtStatus.Name = "txtStatus"; + this.txtStatus.ReadOnly = true; + this.txtStatus.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtStatus.Size = new System.Drawing.Size(534, 160); + this.txtStatus.TabIndex = 23; + this.txtStatus.WordWrap = false; + this.txtStatus.Font = new System.Drawing.Font("Consolas", 9F); + // + // statusStrip + // + this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.statusLabel}); + this.statusStrip.Location = new System.Drawing.Point(0, 578); + this.statusStrip.Name = "statusStrip"; + this.statusStrip.Size = new System.Drawing.Size(566, 22); + this.statusStrip.TabIndex = 24; + // + // statusLabel + // + this.statusLabel.Name = "statusLabel"; + this.statusLabel.Size = new System.Drawing.Size(39, 17); + this.statusLabel.Text = "Ready"; + // + // MainForm + // + this.AcceptButton = this.btnTest; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(566, 600); + this.Controls.Add(this.statusStrip); + this.Controls.Add(this.txtStatus); + this.Controls.Add(this.lblStatus); + this.Controls.Add(this.btnWhoAmI); + this.Controls.Add(this.btnClear); + this.Controls.Add(this.btnCopy); + this.Controls.Add(this.btnTest); + this.Controls.Add(this.btnBuild); + this.Controls.Add(this.txtConnectionString); + this.Controls.Add(this.lblConnectionString); + this.Controls.Add(this.cmbOpenMode); + this.Controls.Add(this.lblOpenMode); + this.Controls.Add(this.numTimeout); + this.Controls.Add(this.lblTimeout); + this.Controls.Add(this.chkTrustServerCertificate); + this.Controls.Add(this.cmbEncrypt); + this.Controls.Add(this.lblEncrypt); + this.Controls.Add(this.txtPassword); + this.Controls.Add(this.lblPassword); + this.Controls.Add(this.txtUserId); + this.Controls.Add(this.lblUserId); + this.Controls.Add(this.cmbAuthentication); + this.Controls.Add(this.lblAuthentication); + this.Controls.Add(this.txtDatabase); + this.Controls.Add(this.lblDatabase); + this.Controls.Add(this.txtServer); + this.Controls.Add(this.lblServer); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.Name = "MainForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Azure SQL Connector"; + ((System.ComponentModel.ISupportInitialize)(this.numTimeout)).EndInit(); + this.statusStrip.ResumeLayout(false); + this.statusStrip.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label lblServer; + private System.Windows.Forms.TextBox txtServer; + private System.Windows.Forms.Label lblDatabase; + private System.Windows.Forms.TextBox txtDatabase; + private System.Windows.Forms.Label lblAuthentication; + private System.Windows.Forms.ComboBox cmbAuthentication; + private System.Windows.Forms.Label lblUserId; + private System.Windows.Forms.TextBox txtUserId; + private System.Windows.Forms.Label lblPassword; + private System.Windows.Forms.TextBox txtPassword; + private System.Windows.Forms.Label lblEncrypt; + private System.Windows.Forms.ComboBox cmbEncrypt; + private System.Windows.Forms.CheckBox chkTrustServerCertificate; + private System.Windows.Forms.Label lblTimeout; + private System.Windows.Forms.NumericUpDown numTimeout; + private System.Windows.Forms.Label lblOpenMode; + private System.Windows.Forms.ComboBox cmbOpenMode; + private System.Windows.Forms.Label lblConnectionString; + private System.Windows.Forms.TextBox txtConnectionString; + private System.Windows.Forms.Button btnBuild; + private System.Windows.Forms.Button btnTest; + private System.Windows.Forms.Button btnCopy; + private System.Windows.Forms.Button btnClear; + private System.Windows.Forms.Button btnWhoAmI; + private System.Windows.Forms.Label lblStatus; + private System.Windows.Forms.TextBox txtStatus; + private System.Windows.Forms.StatusStrip statusStrip; + private System.Windows.Forms.ToolStripStatusLabel statusLabel; + } +} diff --git a/doc/apps/AzureSqlConnector/MainForm.cs b/doc/apps/AzureSqlConnector/MainForm.cs new file mode 100644 index 0000000000..9cc6d0648c --- /dev/null +++ b/doc/apps/AzureSqlConnector/MainForm.cs @@ -0,0 +1,590 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.Data.SqlClient; +using Microsoft.Identity.Client; + +namespace Microsoft.Data.SqlClient.Samples.AzureSqlConnector +{ + /// + /// "UI-thread" variant of the connector form. Opens the SQL connection via + /// on the UI thread; the WinForms + /// keeps the message pump alive while + /// the async I/O completes, so the form remains responsive and MSAL.NET's embedded sign-in + /// browser (for ActiveDirectoryInteractive) parents itself correctly. + /// + public partial class MainForm : Form + { + // ────────────────────────────────────────────────────────────────── + #region Construction + + public MainForm() + { + InitializeComponent(); + this.Text = "Azure SQL Connector — UI thread"; + PopulateAuthenticationMethods(); + PopulateEncryptOptions(); + PopulateOpenModes(); + UpdateCredentialFieldsAvailability(); + + // Force the underlying Win32 window to be created NOW (on the UI thread) so we can + // safely hand its HWND to MSAL later. Even in async mode, MSAL.NET may invoke the + // parent-window callback from a worker thread (e.g. when the driver blocks on a + // synchronous Open()), and touching Form.Handle from a non-UI thread throws + // InvalidOperationException ("Cross-thread operation not valid"). + _ownerHwnd = this.Handle; + + RegisterActiveDirectoryProvider(); + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region UI Initialization + + private void PopulateAuthenticationMethods() + { + foreach (SqlAuthenticationMethod method in Enum.GetValues(typeof(SqlAuthenticationMethod))) + { + cmbAuthentication.Items.Add(method); + } + + cmbAuthentication.SelectedItem = SqlAuthenticationMethod.SqlPassword; + } + + private void PopulateEncryptOptions() + { + cmbEncrypt.Items.Add(EncryptDisplay.Mandatory); + cmbEncrypt.Items.Add(EncryptDisplay.Optional); + cmbEncrypt.Items.Add(EncryptDisplay.Strict); + cmbEncrypt.SelectedIndex = 0; + } + + private void PopulateOpenModes() + { + cmbOpenMode.Items.Add(OpenModeDisplay.Async); + cmbOpenMode.Items.Add(OpenModeDisplay.Sync); + cmbOpenMode.SelectedIndex = 0; + } + + /// + /// Registers a single for every + /// Entra ID authentication method and gives it the form's captured HWND as the parent + /// window owner. Both callbacks intentionally use the HWND captured in the constructor + /// () rather than this.Handle, because MSAL.NET can invoke + /// them from a worker thread (e.g. when the driver blocks on a synchronous Open() + /// or when its internal continuations resume off-UI). + /// + private void RegisterActiveDirectoryProvider() + { + ActiveDirectoryAuthenticationProvider provider = new ActiveDirectoryAuthenticationProvider(); + IntPtr ownerHwnd = _ownerHwnd; + +#if NETFRAMEWORK + // .NET Framework: parent the embedded WebView via the legacy IWin32Window API. + provider.SetIWin32WindowFunc(() => new Win32WindowHandle(ownerHwnd)); +#endif + + // Modern API: works on both .NET Framework and .NET 8+, and is the one MSAL's WAM + // broker consults on Windows. + provider.SetParentActivityOrWindowFunc(() => ownerHwnd); + + // Without this, MSAL's default device-code callback writes the prompt to + // Console.WriteLine, which is invisible in a WinForms host — the connection + // appears to hang while MSAL polls for a code the user never sees. + provider.SetDeviceCodeFlowCallback(DeviceCodeFlowCallback); + + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, provider); + #pragma warning disable CS0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, provider); + #pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// Device Code Flow callback. MSAL invokes this on a worker thread before it begins + /// polling the token endpoint. We surface the user code three ways so the user always + /// sees it: (1) appended to the log textbox via BeginInvoke (works whenever the UI + /// thread is pumping — async OpenAsync), (2) the verification URL launched in + /// the default browser, and (3) a modal owned by the MSAL worker thread (works even + /// when the UI thread is blocked by a synchronous Open()). MSAL polling waits + /// for the returned Task to complete, so dismissing the dialog also resumes polling. + /// + private Task DeviceCodeFlowCallback(DeviceCodeResult result) + { + string message = result.Message; + string url = result.VerificationUrl; + string code = result.UserCode; + + if (IsHandleCreated) + { + try + { + BeginInvoke((Action)(() => + { + AppendStatus(string.Empty); + AppendStatus("=== Device Code Flow ==="); + AppendStatus(message); + })); + } + catch (InvalidOperationException) + { + // Form is closing or handle was destroyed; fall through to the modal. + } + } + + try + { + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } + catch + { + // Best-effort; the modal below still shows the URL and code. + } + + MessageBox.Show( + "Sign in to complete Device Code Flow:" + Environment.NewLine + Environment.NewLine + + " URL : " + url + Environment.NewLine + + " Code: " + code + Environment.NewLine + Environment.NewLine + + "A browser window has been opened. Enter the code above, complete sign-in," + + Environment.NewLine + "then click OK to resume the connection.", + "Device Code Flow", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + + return Task.CompletedTask; + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region Event Handlers + + private void cmbAuthentication_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateCredentialFieldsAvailability(); + } + + private void btnBuild_Click(object sender, EventArgs e) + { + try + { + SqlConnectionStringBuilder builder = BuildConnectionString(); + txtConnectionString.Text = MaskPassword(builder); + SetStatus("Connection string built successfully.", isError: false); + AppendStatus("Connection string built:\r\n" + MaskPassword(builder)); + } + catch (Exception ex) + { + txtConnectionString.Text = string.Empty; + SetStatus("Failed to build connection string.", isError: true); + AppendStatus("ERROR: " + ex.Message); + } + } + + private async void btnTest_Click(object sender, EventArgs e) + { + SqlConnectionStringBuilder builder; + try + { + builder = BuildConnectionString(); + txtConnectionString.Text = MaskPassword(builder); + } + catch (Exception ex) + { + SetStatus("Failed to build connection string.", isError: true); + AppendStatus("ERROR: " + ex.Message); + return; + } + + bool useAsync = IsAsyncOpenSelected(); + SetBusy(true, useAsync ? "Testing connection (OpenAsync)..." : "Testing connection (Open)..."); + AppendStatus(string.Empty); + AppendStatus("Testing connectivity to " + builder.DataSource + " (" + + (useAsync ? "OpenAsync" : "sync Open") + ") ..."); + + try + { + string serverVersion; + using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) + { + await OpenConnectionAsync(connection, useAsync).ConfigureAwait(true); + serverVersion = connection.ServerVersion; + } + + SetStatus("Connected successfully.", isError: false); + AppendStatus("Connected successfully! Server version: " + serverVersion); + } + catch (SqlException ex) + { + SetStatus("Connection failed (SqlException).", isError: true); + AppendStatus("SqlException [" + ex.Number + "]: " + ex.Message + "\r\n" + ex.StackTrace); + } + catch (Exception ex) + { + SetStatus("Connection failed.", isError: true); + AppendStatus(ex.GetType().Name + ": " + ex.Message + "\r\n" + ex.StackTrace); + } + finally + { + SetBusy(false, null); + } + } + + private async void btnWhoAmI_Click(object sender, EventArgs e) + { + SqlConnectionStringBuilder builder; + try + { + builder = BuildConnectionString(); + txtConnectionString.Text = MaskPassword(builder); + } + catch (Exception ex) + { + SetStatus("Failed to build connection string.", isError: true); + AppendStatus("ERROR: " + ex.Message); + return; + } + + bool useAsync = IsAsyncOpenSelected(); + SetBusy(true, useAsync + ? "Querying logged-in identity (OpenAsync)..." + : "Querying logged-in identity (Open)..."); + AppendStatus(string.Empty); + AppendStatus("Running identity query against " + builder.DataSource + " (" + + (useAsync ? "OpenAsync" : "sync Open") + ") ..."); + + try + { + // Same UI-thread reasoning as btnTest_Click — keep the message pump alive for any + // ActiveDirectoryInteractive sign-in that may be required. + using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) + { + await OpenConnectionAsync(connection, useAsync).ConfigureAwait(true); + + using (SqlCommand command = connection.CreateCommand()) + { + command.CommandText = IdentityQuery.CommandText; + + using (SqlDataReader reader = await command.ExecuteReaderAsync().ConfigureAwait(true)) + { + if (await reader.ReadAsync().ConfigureAwait(true)) + { + AppendStatus("Identity:"); + for (int i = 0; i < reader.FieldCount; i++) + { + string name = reader.GetName(i); + object value = reader.IsDBNull(i) ? "(null)" : reader.GetValue(i); + AppendStatus(" " + name.PadRight(16) + ": " + value); + } + SetStatus("Identity query succeeded.", isError: false); + } + else + { + SetStatus("Identity query returned no rows.", isError: true); + AppendStatus("(no rows returned)"); + } + } + } + } + } + catch (SqlException ex) + { + SetStatus("Identity query failed (SqlException).", isError: true); + AppendStatus("SqlException [" + ex.Number + "]: " + ex.Message); + } + catch (Exception ex) + { + SetStatus("Identity query failed.", isError: true); + AppendStatus(ex.GetType().Name + ": " + ex.Message); + } + finally + { + SetBusy(false, null); + } + } + + private void btnCopy_Click(object sender, EventArgs e) + { + if (string.IsNullOrEmpty(txtConnectionString.Text)) + { + SetStatus("Nothing to copy. Build the connection string first.", isError: true); + return; + } + + try + { + Clipboard.SetText(BuildConnectionString().ConnectionString); + SetStatus("Connection string copied to clipboard.", isError: false); + } + catch (Exception ex) + { + SetStatus("Failed to copy to clipboard.", isError: true); + AppendStatus("ERROR: " + ex.Message); + } + } + + private void btnClear_Click(object sender, EventArgs e) + { + txtServer.Clear(); + txtDatabase.Clear(); + txtUserId.Clear(); + txtPassword.Clear(); + txtConnectionString.Clear(); + txtStatus.Clear(); + cmbAuthentication.SelectedItem = SqlAuthenticationMethod.SqlPassword; + cmbEncrypt.SelectedIndex = 0; + cmbOpenMode.SelectedIndex = 0; + chkTrustServerCertificate.Checked = false; + numTimeout.Value = 30; + SetStatus("Ready", isError: false); + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region Connection String Construction + + private SqlConnectionStringBuilder BuildConnectionString() + { + string server = (txtServer.Text ?? string.Empty).Trim(); + if (string.IsNullOrEmpty(server)) + { + throw new InvalidOperationException("Server name is required."); + } + + SqlAuthenticationMethod authMethod = (SqlAuthenticationMethod)cmbAuthentication.SelectedItem; + + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder + { + DataSource = server, + ConnectTimeout = (int)numTimeout.Value, + }; + + string database = (txtDatabase.Text ?? string.Empty).Trim(); + if (!string.IsNullOrEmpty(database)) + { + builder.InitialCatalog = database; + } + + if (authMethod != SqlAuthenticationMethod.NotSpecified) + { + builder.Authentication = authMethod; + } + + if (RequiresUserAndPassword(authMethod)) + { + string userId = (txtUserId.Text ?? string.Empty).Trim(); + if (string.IsNullOrEmpty(userId)) + { + throw new InvalidOperationException( + "User ID is required for " + authMethod + " authentication."); + } + + builder.UserID = userId; + builder.Password = txtPassword.Text ?? string.Empty; + } + else if (authMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + || authMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity + || authMethod == SqlAuthenticationMethod.ActiveDirectoryMSI + || authMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive + || authMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow + || authMethod == SqlAuthenticationMethod.ActiveDirectoryDefault + || authMethod == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity) + { + string userId = (txtUserId.Text ?? string.Empty).Trim(); + if (!string.IsNullOrEmpty(userId)) + { + builder.UserID = userId; + } + + if (authMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + && !string.IsNullOrEmpty(txtPassword.Text)) + { + builder.Password = txtPassword.Text; + } + } + + string encryptValue = cmbEncrypt.SelectedItem as string ?? EncryptDisplay.Mandatory; + switch (encryptValue) + { + case EncryptDisplay.Mandatory: + builder.Encrypt = SqlConnectionEncryptOption.Mandatory; + break; + case EncryptDisplay.Optional: + builder.Encrypt = SqlConnectionEncryptOption.Optional; + break; + case EncryptDisplay.Strict: + builder.Encrypt = SqlConnectionEncryptOption.Strict; + break; + } + + builder.TrustServerCertificate = chkTrustServerCertificate.Checked; + + return builder; + } + + private static bool RequiresUserAndPassword(SqlAuthenticationMethod method) + { + switch (method) + { + case SqlAuthenticationMethod.SqlPassword: +#pragma warning disable CS0618 // Type or member is obsolete + case SqlAuthenticationMethod.ActiveDirectoryPassword: +#pragma warning restore CS0618 + return true; + default: + return false; + } + } + + private static string MaskPassword(SqlConnectionStringBuilder builder) + { + if (string.IsNullOrEmpty(builder.Password)) + { + return builder.ConnectionString; + } + + SqlConnectionStringBuilder copy = new SqlConnectionStringBuilder(builder.ConnectionString) + { + Password = "********", + }; + return copy.ConnectionString; + } + + /// + /// Returns when the user picked Async (OpenAsync) in the + /// open-mode selector. Defaults to async if the selector has not been initialized yet. + /// + private bool IsAsyncOpenSelected() + { + return cmbOpenMode.SelectedItem as string != OpenModeDisplay.Sync; + } + + /// + /// Opens on the calling thread using either + /// or the synchronous + /// based on . The method itself is always async-returning so + /// callers can await uniformly; for the sync case it runs Open() inline on + /// the UI thread (which is supported with WAM broker because the broker dialog is hosted + /// by a separate process and does not need this thread's message pump). + /// + private static Task OpenConnectionAsync(SqlConnection connection, bool useAsync) + { + if (useAsync) + { + return connection.OpenAsync(); + } + + connection.Open(); + return Task.CompletedTask; + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region UI Helpers + + private void UpdateCredentialFieldsAvailability() + { + if (cmbAuthentication.SelectedItem == null) + { + return; + } + + SqlAuthenticationMethod method = (SqlAuthenticationMethod)cmbAuthentication.SelectedItem; + + bool userEnabled = method != SqlAuthenticationMethod.ActiveDirectoryIntegrated; + bool passwordEnabled = RequiresUserAndPassword(method) + || method == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; + + txtUserId.Enabled = userEnabled; + txtPassword.Enabled = passwordEnabled; + + if (!passwordEnabled) + { + txtPassword.Clear(); + } + } + + private void SetStatus(string text, bool isError) + { + statusLabel.Text = text; + statusLabel.ForeColor = isError ? System.Drawing.Color.Firebrick : System.Drawing.Color.Black; + } + + private void AppendStatus(string line) + { + if (txtStatus.TextLength > 0) + { + txtStatus.AppendText(Environment.NewLine); + } + txtStatus.AppendText(line ?? string.Empty); + } + + private void SetBusy(bool busy, string statusText) + { + btnBuild.Enabled = !busy; + btnTest.Enabled = !busy; + btnCopy.Enabled = !busy; + btnClear.Enabled = !busy; + btnWhoAmI.Enabled = !busy; + cmbOpenMode.Enabled = !busy; + Cursor = busy ? Cursors.WaitCursor : Cursors.Default; + + if (statusText != null) + { + SetStatus(statusText, isError: false); + } + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region Nested Types + + private static class EncryptDisplay + { + public const string Mandatory = "Mandatory"; + public const string Optional = "Optional"; + public const string Strict = "Strict"; + } + + private static class OpenModeDisplay + { + public const string Async = "Async (OpenAsync)"; + public const string Sync = "Sync (Open)"; + } + +#if NETFRAMEWORK + // Tiny IWin32Window wrapper around a raw HWND captured on the UI thread so MSAL.NET's + // legacy IWin32WindowFunc callback can safely return a window owner from a worker thread + // without ever touching Control.Handle off-UI. + private sealed class Win32WindowHandle : IWin32Window + { + private readonly IntPtr _hwnd; + public Win32WindowHandle(IntPtr hwnd) => _hwnd = hwnd; + public IntPtr Handle => _hwnd; + } +#endif + + #endregion + + // ─────────────────────────────────────────────────────────────── + #region Private Fields + + // The form's Win32 window handle, captured on the UI thread in the constructor. + // Read from worker threads by the Entra ID provider callbacks to parent MSAL's sign-in + // / WAM broker UI without illegally touching Control.Handle. + private readonly IntPtr _ownerHwnd; + + #endregion + } +} diff --git a/doc/apps/AzureSqlConnector/MainFormWorker.Designer.cs b/doc/apps/AzureSqlConnector/MainFormWorker.Designer.cs new file mode 100644 index 0000000000..c872ee38ab --- /dev/null +++ b/doc/apps/AzureSqlConnector/MainFormWorker.Designer.cs @@ -0,0 +1,383 @@ +namespace Microsoft.Data.SqlClient.Samples.AzureSqlConnector +{ + partial class MainFormWorker + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lblServer = new System.Windows.Forms.Label(); + this.txtServer = new System.Windows.Forms.TextBox(); + this.lblDatabase = new System.Windows.Forms.Label(); + this.txtDatabase = new System.Windows.Forms.TextBox(); + this.lblAuthentication = new System.Windows.Forms.Label(); + this.cmbAuthentication = new System.Windows.Forms.ComboBox(); + this.lblUserId = new System.Windows.Forms.Label(); + this.txtUserId = new System.Windows.Forms.TextBox(); + this.lblPassword = new System.Windows.Forms.Label(); + this.txtPassword = new System.Windows.Forms.TextBox(); + this.lblEncrypt = new System.Windows.Forms.Label(); + this.cmbEncrypt = new System.Windows.Forms.ComboBox(); + this.chkTrustServerCertificate = new System.Windows.Forms.CheckBox(); + this.lblTimeout = new System.Windows.Forms.Label(); + this.numTimeout = new System.Windows.Forms.NumericUpDown(); + this.chkClearTokenCache = new System.Windows.Forms.CheckBox(); + this.lblConnectionString = new System.Windows.Forms.Label(); + this.txtConnectionString = new System.Windows.Forms.TextBox(); + this.btnBuild = new System.Windows.Forms.Button(); + this.btnTest = new System.Windows.Forms.Button(); + this.btnCopy = new System.Windows.Forms.Button(); + this.btnClear = new System.Windows.Forms.Button(); + this.btnWhoAmI = new System.Windows.Forms.Button(); + this.lblStatus = new System.Windows.Forms.Label(); + this.txtStatus = new System.Windows.Forms.TextBox(); + this.statusStrip = new System.Windows.Forms.StatusStrip(); + this.statusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + ((System.ComponentModel.ISupportInitialize)(this.numTimeout)).BeginInit(); + this.statusStrip.SuspendLayout(); + this.SuspendLayout(); + // + // lblServer + // + this.lblServer.AutoSize = true; + this.lblServer.Location = new System.Drawing.Point(16, 18); + this.lblServer.Name = "lblServer"; + this.lblServer.Size = new System.Drawing.Size(75, 13); + this.lblServer.TabIndex = 0; + this.lblServer.Text = "&Server name:"; + // + // txtServer + // + this.txtServer.Location = new System.Drawing.Point(150, 15); + this.txtServer.Name = "txtServer"; + this.txtServer.Size = new System.Drawing.Size(400, 20); + this.txtServer.TabIndex = 1; + // + // lblDatabase + // + this.lblDatabase.AutoSize = true; + this.lblDatabase.Location = new System.Drawing.Point(16, 48); + this.lblDatabase.Name = "lblDatabase"; + this.lblDatabase.Size = new System.Drawing.Size(86, 13); + this.lblDatabase.TabIndex = 2; + this.lblDatabase.Text = "&Database name:"; + // + // txtDatabase + // + this.txtDatabase.Location = new System.Drawing.Point(150, 45); + this.txtDatabase.Name = "txtDatabase"; + this.txtDatabase.Size = new System.Drawing.Size(400, 20); + this.txtDatabase.TabIndex = 3; + // + // lblAuthentication + // + this.lblAuthentication.AutoSize = true; + this.lblAuthentication.Location = new System.Drawing.Point(16, 78); + this.lblAuthentication.Name = "lblAuthentication"; + this.lblAuthentication.Size = new System.Drawing.Size(80, 13); + this.lblAuthentication.TabIndex = 4; + this.lblAuthentication.Text = "&Authentication:"; + // + // cmbAuthentication + // + this.cmbAuthentication.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbAuthentication.FormattingEnabled = true; + this.cmbAuthentication.Location = new System.Drawing.Point(150, 75); + this.cmbAuthentication.Name = "cmbAuthentication"; + this.cmbAuthentication.Size = new System.Drawing.Size(400, 21); + this.cmbAuthentication.TabIndex = 5; + this.cmbAuthentication.SelectedIndexChanged += new System.EventHandler(this.cmbAuthentication_SelectedIndexChanged); + // + // lblUserId + // + this.lblUserId.AutoSize = true; + this.lblUserId.Location = new System.Drawing.Point(16, 108); + this.lblUserId.Name = "lblUserId"; + this.lblUserId.Size = new System.Drawing.Size(45, 13); + this.lblUserId.TabIndex = 6; + this.lblUserId.Text = "&User ID:"; + // + // txtUserId + // + this.txtUserId.Location = new System.Drawing.Point(150, 105); + this.txtUserId.Name = "txtUserId"; + this.txtUserId.Size = new System.Drawing.Size(400, 20); + this.txtUserId.TabIndex = 7; + // + // lblPassword + // + this.lblPassword.AutoSize = true; + this.lblPassword.Location = new System.Drawing.Point(16, 138); + this.lblPassword.Name = "lblPassword"; + this.lblPassword.Size = new System.Drawing.Size(56, 13); + this.lblPassword.TabIndex = 8; + this.lblPassword.Text = "&Password:"; + // + // txtPassword + // + this.txtPassword.Location = new System.Drawing.Point(150, 135); + this.txtPassword.Name = "txtPassword"; + this.txtPassword.Size = new System.Drawing.Size(400, 20); + this.txtPassword.TabIndex = 9; + this.txtPassword.UseSystemPasswordChar = true; + // + // lblEncrypt + // + this.lblEncrypt.AutoSize = true; + this.lblEncrypt.Location = new System.Drawing.Point(16, 168); + this.lblEncrypt.Name = "lblEncrypt"; + this.lblEncrypt.Size = new System.Drawing.Size(46, 13); + this.lblEncrypt.TabIndex = 10; + this.lblEncrypt.Text = "&Encrypt:"; + // + // cmbEncrypt + // + this.cmbEncrypt.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbEncrypt.FormattingEnabled = true; + this.cmbEncrypt.Location = new System.Drawing.Point(150, 165); + this.cmbEncrypt.Name = "cmbEncrypt"; + this.cmbEncrypt.Size = new System.Drawing.Size(200, 21); + this.cmbEncrypt.TabIndex = 11; + // + // chkTrustServerCertificate + // + this.chkTrustServerCertificate.AutoSize = true; + this.chkTrustServerCertificate.Location = new System.Drawing.Point(370, 167); + this.chkTrustServerCertificate.Name = "chkTrustServerCertificate"; + this.chkTrustServerCertificate.Size = new System.Drawing.Size(149, 17); + this.chkTrustServerCertificate.TabIndex = 12; + this.chkTrustServerCertificate.Text = "&Trust server certificate"; + this.chkTrustServerCertificate.UseVisualStyleBackColor = true; + // + // lblTimeout + // + this.lblTimeout.AutoSize = true; + this.lblTimeout.Location = new System.Drawing.Point(16, 198); + this.lblTimeout.Name = "lblTimeout"; + this.lblTimeout.Size = new System.Drawing.Size(101, 13); + this.lblTimeout.TabIndex = 13; + this.lblTimeout.Text = "Connect timeout (s):"; + // + // numTimeout + // + this.numTimeout.Location = new System.Drawing.Point(150, 196); + this.numTimeout.Maximum = new decimal(new int[] { 600, 0, 0, 0 }); + this.numTimeout.Minimum = new decimal(new int[] { 1, 0, 0, 0 }); + this.numTimeout.Name = "numTimeout"; + this.numTimeout.Size = new System.Drawing.Size(80, 20); + this.numTimeout.TabIndex = 14; + this.numTimeout.Value = new decimal(new int[] { 30, 0, 0, 0 }); + // + // chkClearTokenCache + // + this.chkClearTokenCache.AutoSize = true; + this.chkClearTokenCache.Location = new System.Drawing.Point(260, 198); + this.chkClearTokenCache.Name = "chkClearTokenCache"; + this.chkClearTokenCache.Size = new System.Drawing.Size(290, 17); + this.chkClearTokenCache.TabIndex = 15; + this.chkClearTokenCache.Text = "Clear MSAL token &cache before connect (force prompt)"; + this.chkClearTokenCache.UseVisualStyleBackColor = true; + // + // lblConnectionString + // + this.lblConnectionString.AutoSize = true; + this.lblConnectionString.Location = new System.Drawing.Point(16, 230); + this.lblConnectionString.Name = "lblConnectionString"; + this.lblConnectionString.Size = new System.Drawing.Size(94, 13); + this.lblConnectionString.TabIndex = 15; + this.lblConnectionString.Text = "Connection string:"; + // + // txtConnectionString + // + this.txtConnectionString.Location = new System.Drawing.Point(16, 246); + this.txtConnectionString.Multiline = true; + this.txtConnectionString.Name = "txtConnectionString"; + this.txtConnectionString.ReadOnly = true; + this.txtConnectionString.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtConnectionString.Size = new System.Drawing.Size(534, 60); + this.txtConnectionString.TabIndex = 16; + this.txtConnectionString.BackColor = System.Drawing.SystemColors.Info; + // + // btnBuild + // + this.btnBuild.Location = new System.Drawing.Point(16, 316); + this.btnBuild.Name = "btnBuild"; + this.btnBuild.Size = new System.Drawing.Size(140, 26); + this.btnBuild.TabIndex = 17; + this.btnBuild.Text = "&Build Connection String"; + this.btnBuild.UseVisualStyleBackColor = true; + this.btnBuild.Click += new System.EventHandler(this.btnBuild_Click); + // + // btnTest + // + this.btnTest.Location = new System.Drawing.Point(166, 316); + this.btnTest.Name = "btnTest"; + this.btnTest.Size = new System.Drawing.Size(120, 26); + this.btnTest.TabIndex = 18; + this.btnTest.Text = "Te&st Connection"; + this.btnTest.UseVisualStyleBackColor = true; + this.btnTest.Click += new System.EventHandler(this.btnTest_Click); + // + // btnCopy + // + this.btnCopy.Location = new System.Drawing.Point(296, 316); + this.btnCopy.Name = "btnCopy"; + this.btnCopy.Size = new System.Drawing.Size(120, 26); + this.btnCopy.TabIndex = 19; + this.btnCopy.Text = "Cop&y to Clipboard"; + this.btnCopy.UseVisualStyleBackColor = true; + this.btnCopy.Click += new System.EventHandler(this.btnCopy_Click); + // + // btnClear + // + this.btnClear.Location = new System.Drawing.Point(426, 316); + this.btnClear.Name = "btnClear"; + this.btnClear.Size = new System.Drawing.Size(124, 26); + this.btnClear.TabIndex = 20; + this.btnClear.Text = "Cl&ear All"; + this.btnClear.UseVisualStyleBackColor = true; + this.btnClear.Click += new System.EventHandler(this.btnClear_Click); + // + // btnWhoAmI + // + this.btnWhoAmI.Location = new System.Drawing.Point(16, 348); + this.btnWhoAmI.Name = "btnWhoAmI"; + this.btnWhoAmI.Size = new System.Drawing.Size(534, 26); + this.btnWhoAmI.TabIndex = 21; + this.btnWhoAmI.Text = "&Who Am I? (run identity query on the database)"; + this.btnWhoAmI.UseVisualStyleBackColor = true; + this.btnWhoAmI.Click += new System.EventHandler(this.btnWhoAmI_Click); + // + // lblStatus + // + this.lblStatus.AutoSize = true; + this.lblStatus.Location = new System.Drawing.Point(16, 386); + this.lblStatus.Name = "lblStatus"; + this.lblStatus.Size = new System.Drawing.Size(40, 13); + this.lblStatus.TabIndex = 22; + this.lblStatus.Text = "Result:"; + // + // txtStatus + // + this.txtStatus.Location = new System.Drawing.Point(16, 402); + this.txtStatus.Multiline = true; + this.txtStatus.Name = "txtStatus"; + this.txtStatus.ReadOnly = true; + this.txtStatus.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtStatus.Size = new System.Drawing.Size(534, 160); + this.txtStatus.TabIndex = 23; + this.txtStatus.WordWrap = false; + this.txtStatus.Font = new System.Drawing.Font("Consolas", 9F); + // + // statusStrip + // + this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.statusLabel}); + this.statusStrip.Location = new System.Drawing.Point(0, 578); + this.statusStrip.Name = "statusStrip"; + this.statusStrip.Size = new System.Drawing.Size(566, 22); + this.statusStrip.TabIndex = 24; + // + // statusLabel + // + this.statusLabel.Name = "statusLabel"; + this.statusLabel.Size = new System.Drawing.Size(39, 17); + this.statusLabel.Text = "Ready"; + // + // MainForm + // + this.AcceptButton = this.btnTest; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(566, 600); + this.Controls.Add(this.statusStrip); + this.Controls.Add(this.txtStatus); + this.Controls.Add(this.lblStatus); + this.Controls.Add(this.btnWhoAmI); + this.Controls.Add(this.btnClear); + this.Controls.Add(this.btnCopy); + this.Controls.Add(this.btnTest); + this.Controls.Add(this.btnBuild); + this.Controls.Add(this.txtConnectionString); + this.Controls.Add(this.lblConnectionString); + this.Controls.Add(this.numTimeout); + this.Controls.Add(this.lblTimeout); + this.Controls.Add(this.chkClearTokenCache); + this.Controls.Add(this.chkTrustServerCertificate); + this.Controls.Add(this.cmbEncrypt); + this.Controls.Add(this.lblEncrypt); + this.Controls.Add(this.txtPassword); + this.Controls.Add(this.lblPassword); + this.Controls.Add(this.txtUserId); + this.Controls.Add(this.lblUserId); + this.Controls.Add(this.cmbAuthentication); + this.Controls.Add(this.lblAuthentication); + this.Controls.Add(this.txtDatabase); + this.Controls.Add(this.lblDatabase); + this.Controls.Add(this.txtServer); + this.Controls.Add(this.lblServer); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.Name = "MainFormWorker"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Azure SQL Connector — Worker thread (Task.Run + Open)"; + ((System.ComponentModel.ISupportInitialize)(this.numTimeout)).EndInit(); + this.statusStrip.ResumeLayout(false); + this.statusStrip.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label lblServer; + private System.Windows.Forms.TextBox txtServer; + private System.Windows.Forms.Label lblDatabase; + private System.Windows.Forms.TextBox txtDatabase; + private System.Windows.Forms.Label lblAuthentication; + private System.Windows.Forms.ComboBox cmbAuthentication; + private System.Windows.Forms.Label lblUserId; + private System.Windows.Forms.TextBox txtUserId; + private System.Windows.Forms.Label lblPassword; + private System.Windows.Forms.TextBox txtPassword; + private System.Windows.Forms.Label lblEncrypt; + private System.Windows.Forms.ComboBox cmbEncrypt; + private System.Windows.Forms.CheckBox chkTrustServerCertificate; + private System.Windows.Forms.Label lblTimeout; + private System.Windows.Forms.NumericUpDown numTimeout; + private System.Windows.Forms.CheckBox chkClearTokenCache; + private System.Windows.Forms.Label lblConnectionString; + private System.Windows.Forms.TextBox txtConnectionString; + private System.Windows.Forms.Button btnBuild; + private System.Windows.Forms.Button btnTest; + private System.Windows.Forms.Button btnCopy; + private System.Windows.Forms.Button btnClear; + private System.Windows.Forms.Button btnWhoAmI; + private System.Windows.Forms.Label lblStatus; + private System.Windows.Forms.TextBox txtStatus; + private System.Windows.Forms.StatusStrip statusStrip; + private System.Windows.Forms.ToolStripStatusLabel statusLabel; + } +} diff --git a/doc/apps/AzureSqlConnector/MainFormWorker.cs b/doc/apps/AzureSqlConnector/MainFormWorker.cs new file mode 100644 index 0000000000..8d344cf6c4 --- /dev/null +++ b/doc/apps/AzureSqlConnector/MainFormWorker.cs @@ -0,0 +1,598 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.Data.SqlClient; +using Microsoft.Identity.Client; + +namespace Microsoft.Data.SqlClient.Samples.AzureSqlConnector +{ + /// + /// "Worker thread" variant of the connector form. Opens the SQL connection synchronously + /// inside a call so the UI thread never blocks. + /// + /// + /// + /// The form's HWND is captured on the UI thread in the constructor and stashed in + /// . Both Entra ID parent-window callbacks return that captured + /// handle, so they are safe to invoke from the worker thread (touching Form.Handle + /// from a non-UI thread is illegal). + /// + /// + /// Compare with , which keeps Open on the UI thread and relies on + /// for responsiveness. + /// + /// + public partial class MainFormWorker : Form + { + // ────────────────────────────────────────────────────────────────── + #region Construction + + public MainFormWorker() + { + InitializeComponent(); + PopulateAuthenticationMethods(); + PopulateEncryptOptions(); + UpdateCredentialFieldsAvailability(); + + // Force the underlying Win32 window to be created NOW (on the UI thread) so we can + // safely capture its HWND for MSAL to use later from a worker thread. Touching + // Form.Handle from a non-UI thread is illegal, so we read it here once and stash it. + _ownerHwnd = this.Handle; + + RegisterActiveDirectoryProvider(); + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region UI Initialization + + private void PopulateAuthenticationMethods() + { + foreach (SqlAuthenticationMethod method in Enum.GetValues(typeof(SqlAuthenticationMethod))) + { + cmbAuthentication.Items.Add(method); + } + + cmbAuthentication.SelectedItem = SqlAuthenticationMethod.SqlPassword; + } + + private void PopulateEncryptOptions() + { + cmbEncrypt.Items.Add(EncryptDisplay.Mandatory); + cmbEncrypt.Items.Add(EncryptDisplay.Optional); + cmbEncrypt.Items.Add(EncryptDisplay.Strict); + cmbEncrypt.SelectedIndex = 0; + } + + /// + /// Registers a single for every + /// Entra ID authentication method and gives it the form's captured HWND as the parent + /// window owner. Both callbacks intentionally use the HWND captured in the constructor + /// () rather than this.Handle; they are invoked by MSAL on + /// the worker thread that called . + /// + private void RegisterActiveDirectoryProvider() + { + ActiveDirectoryAuthenticationProvider provider = new ActiveDirectoryAuthenticationProvider(); + IntPtr ownerHwnd = _ownerHwnd; + +#if NETFRAMEWORK + // .NET Framework: parent the embedded WebView via the legacy IWin32Window API. + provider.SetIWin32WindowFunc(() => new Win32WindowHandle(ownerHwnd)); +#endif + + // Modern API: works on both .NET Framework and .NET 8+, and is the one MSAL's WAM + // broker consults on Windows. + provider.SetParentActivityOrWindowFunc(() => ownerHwnd); + + // Without this, MSAL's default device-code callback writes the prompt to + // Console.WriteLine, which is invisible in a WinForms host — the connection + // appears to hang while MSAL polls for a code the user never sees. + provider.SetDeviceCodeFlowCallback(DeviceCodeFlowCallback); + + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, provider); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, provider); + #pragma warning disable CS0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, provider); + #pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// Device Code Flow callback. MSAL invokes this on a worker thread before it begins + /// polling the token endpoint. We surface the user code three ways so the user always + /// sees it: (1) appended to the log textbox via BeginInvoke (the UI thread is free in + /// this variant because Open() runs on a Task.Run worker), (2) the verification URL + /// launched in the default browser, and (3) a modal owned by the MSAL worker thread. + /// MSAL polling waits for the returned Task to complete, so dismissing the dialog + /// also resumes polling. + /// + private Task DeviceCodeFlowCallback(DeviceCodeResult result) + { + string message = result.Message; + string url = result.VerificationUrl; + string code = result.UserCode; + + if (IsHandleCreated) + { + try + { + BeginInvoke((Action)(() => + { + AppendStatus(string.Empty); + AppendStatus("=== Device Code Flow ==="); + AppendStatus(message); + })); + } + catch (InvalidOperationException) + { + // Form is closing or handle was destroyed; fall through to the modal. + } + } + + try + { + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } + catch + { + // Best-effort; the modal below still shows the URL and code. + } + + MessageBox.Show( + "Sign in to complete Device Code Flow:" + Environment.NewLine + Environment.NewLine + + " URL : " + url + Environment.NewLine + + " Code: " + code + Environment.NewLine + Environment.NewLine + + "A browser window has been opened. Enter the code above, complete sign-in," + + Environment.NewLine + "then click OK to resume the connection.", + "Device Code Flow", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + + return Task.CompletedTask; + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region Event Handlers + + private void cmbAuthentication_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateCredentialFieldsAvailability(); + } + + private void btnBuild_Click(object sender, EventArgs e) + { + try + { + SqlConnectionStringBuilder builder = BuildConnectionString(); + txtConnectionString.Text = MaskPassword(builder); + SetStatus("Connection string built successfully.", isError: false); + AppendStatus("Connection string built:\r\n" + MaskPassword(builder)); + } + catch (Exception ex) + { + txtConnectionString.Text = string.Empty; + SetStatus("Failed to build connection string.", isError: true); + AppendStatus("ERROR: " + ex.Message); + } + } + + private async void btnTest_Click(object sender, EventArgs e) + { + SqlConnectionStringBuilder builder; + try + { + builder = BuildConnectionString(); + txtConnectionString.Text = MaskPassword(builder); + } + catch (Exception ex) + { + SetStatus("Failed to build connection string.", isError: true); + AppendStatus("ERROR: " + ex.Message); + return; + } + + SetBusy(true, "Testing connection..."); + AppendStatus(string.Empty); + AppendStatus("Testing connectivity to " + builder.DataSource + " ..."); + + MaybeClearTokenCache(); + + try + { + // Run Open() on a thread-pool worker so the UI thread never blocks. The await + // continuation hops back onto the UI thread automatically (the awaiter captures + // the current SynchronizationContext), so it is safe to touch the form's controls + // after the await. + // + // The Entra ID interactive / WAM flows still find a parent window because we + // captured the form's HWND on the UI thread in the constructor and the callbacks + // registered in RegisterActiveDirectoryProvider return that captured handle (no + // UI-thread-only Form.Handle access from the worker thread). + string connectionString = builder.ConnectionString; + string serverVersion = await Task.Run(() => + { + using (SqlConnection connection = new SqlConnection(connectionString)) + { + connection.Open(); + return connection.ServerVersion; + } + }).ConfigureAwait(true); + + SetStatus("Connected successfully.", isError: false); + AppendStatus("Connected successfully! Server version: " + serverVersion); + } + catch (SqlException ex) + { + SetStatus("Connection failed (SqlException).", isError: true); + AppendStatus("SqlException [" + ex.Number + "]: " + ex.Message); + } + catch (Exception ex) + { + SetStatus("Connection failed.", isError: true); + AppendStatus(ex.GetType().Name + ": " + ex.Message); + } + finally + { + SetBusy(false, null); + } + } + + private async void btnWhoAmI_Click(object sender, EventArgs e) + { + SqlConnectionStringBuilder builder; + try + { + builder = BuildConnectionString(); + txtConnectionString.Text = MaskPassword(builder); + } + catch (Exception ex) + { + SetStatus("Failed to build connection string.", isError: true); + AppendStatus("ERROR: " + ex.Message); + return; + } + + SetBusy(true, "Querying logged-in identity..."); + AppendStatus(string.Empty); + AppendStatus("Running identity query against " + builder.DataSource + " ..."); + + MaybeClearTokenCache(); + + try + { + // Run the whole open + query + read on a worker thread so the UI never blocks. + // We materialize the single result row into a List<(name, value)> on the worker + // and then format it on the UI thread once the await returns. + string connectionString = builder.ConnectionString; + List<(string Name, object Value)> row = await Task.Run(() => + { + using (SqlConnection connection = new SqlConnection(connectionString)) + { + connection.Open(); + + using (SqlCommand command = connection.CreateCommand()) + { + command.CommandText = IdentityQuery.CommandText; + + using (SqlDataReader reader = command.ExecuteReader()) + { + if (!reader.Read()) + { + return null; + } + + var fields = new List<(string, object)>(reader.FieldCount); + for (int i = 0; i < reader.FieldCount; i++) + { + object value = reader.IsDBNull(i) ? "(null)" : reader.GetValue(i); + fields.Add((reader.GetName(i), value)); + } + return fields; + } + } + } + }).ConfigureAwait(true); + + if (row is null) + { + SetStatus("Identity query returned no rows.", isError: true); + AppendStatus("(no rows returned)"); + } + else + { + AppendStatus("Identity:"); + foreach (var (name, value) in row) + { + AppendStatus(" " + name.PadRight(16) + ": " + value); + } + SetStatus("Identity query succeeded.", isError: false); + } + } + catch (SqlException ex) + { + SetStatus("Identity query failed (SqlException).", isError: true); + AppendStatus("SqlException [" + ex.Number + "]: " + ex.Message); + } + catch (Exception ex) + { + SetStatus("Identity query failed.", isError: true); + AppendStatus(ex.GetType().Name + ": " + ex.Message); + } + finally + { + SetBusy(false, null); + } + } + + private void btnCopy_Click(object sender, EventArgs e) + { + if (string.IsNullOrEmpty(txtConnectionString.Text)) + { + SetStatus("Nothing to copy. Build the connection string first.", isError: true); + return; + } + + try + { + Clipboard.SetText(BuildConnectionString().ConnectionString); + SetStatus("Connection string copied to clipboard.", isError: false); + } + catch (Exception ex) + { + SetStatus("Failed to copy to clipboard.", isError: true); + AppendStatus("ERROR: " + ex.Message); + } + } + + private void btnClear_Click(object sender, EventArgs e) + { + txtServer.Clear(); + txtDatabase.Clear(); + txtUserId.Clear(); + txtPassword.Clear(); + txtConnectionString.Clear(); + txtStatus.Clear(); + cmbAuthentication.SelectedItem = SqlAuthenticationMethod.SqlPassword; + cmbEncrypt.SelectedIndex = 0; + chkTrustServerCertificate.Checked = false; + numTimeout.Value = 30; + SetStatus("Ready", isError: false); + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region Connection String Construction + + private SqlConnectionStringBuilder BuildConnectionString() + { + string server = (txtServer.Text ?? string.Empty).Trim(); + if (string.IsNullOrEmpty(server)) + { + throw new InvalidOperationException("Server name is required."); + } + + SqlAuthenticationMethod authMethod = (SqlAuthenticationMethod)cmbAuthentication.SelectedItem; + + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder + { + DataSource = server, + ConnectTimeout = (int)numTimeout.Value, + }; + + string database = (txtDatabase.Text ?? string.Empty).Trim(); + if (!string.IsNullOrEmpty(database)) + { + builder.InitialCatalog = database; + } + + if (authMethod != SqlAuthenticationMethod.NotSpecified) + { + builder.Authentication = authMethod; + } + + if (RequiresUserAndPassword(authMethod)) + { + string userId = (txtUserId.Text ?? string.Empty).Trim(); + if (string.IsNullOrEmpty(userId)) + { + throw new InvalidOperationException( + "User ID is required for " + authMethod + " authentication."); + } + + builder.UserID = userId; + builder.Password = txtPassword.Text ?? string.Empty; + } + else if (authMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + || authMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity + || authMethod == SqlAuthenticationMethod.ActiveDirectoryMSI + || authMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive + || authMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow + || authMethod == SqlAuthenticationMethod.ActiveDirectoryDefault + || authMethod == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity) + { + string userId = (txtUserId.Text ?? string.Empty).Trim(); + if (!string.IsNullOrEmpty(userId)) + { + builder.UserID = userId; + } + + if (authMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + && !string.IsNullOrEmpty(txtPassword.Text)) + { + builder.Password = txtPassword.Text; + } + } + + string encryptValue = cmbEncrypt.SelectedItem as string ?? EncryptDisplay.Mandatory; + switch (encryptValue) + { + case EncryptDisplay.Mandatory: + builder.Encrypt = SqlConnectionEncryptOption.Mandatory; + break; + case EncryptDisplay.Optional: + builder.Encrypt = SqlConnectionEncryptOption.Optional; + break; + case EncryptDisplay.Strict: + builder.Encrypt = SqlConnectionEncryptOption.Strict; + break; + } + + builder.TrustServerCertificate = chkTrustServerCertificate.Checked; + + return builder; + } + + private static bool RequiresUserAndPassword(SqlAuthenticationMethod method) + { + switch (method) + { + case SqlAuthenticationMethod.SqlPassword: +#pragma warning disable CS0618 // Type or member is obsolete + case SqlAuthenticationMethod.ActiveDirectoryPassword: +#pragma warning restore CS0618 + return true; + default: + return false; + } + } + + private static string MaskPassword(SqlConnectionStringBuilder builder) + { + if (string.IsNullOrEmpty(builder.Password)) + { + return builder.ConnectionString; + } + + SqlConnectionStringBuilder copy = new SqlConnectionStringBuilder(builder.ConnectionString) + { + Password = "********", + }; + return copy.ConnectionString; + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region UI Helpers + + private void UpdateCredentialFieldsAvailability() + { + if (cmbAuthentication.SelectedItem == null) + { + return; + } + + SqlAuthenticationMethod method = (SqlAuthenticationMethod)cmbAuthentication.SelectedItem; + + bool userEnabled = method != SqlAuthenticationMethod.ActiveDirectoryIntegrated; + bool passwordEnabled = RequiresUserAndPassword(method) + || method == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; + + txtUserId.Enabled = userEnabled; + txtPassword.Enabled = passwordEnabled; + + if (!passwordEnabled) + { + txtPassword.Clear(); + } + } + + private void SetStatus(string text, bool isError) + { + statusLabel.Text = text; + statusLabel.ForeColor = isError ? System.Drawing.Color.Firebrick : System.Drawing.Color.Black; + } + + private void AppendStatus(string line) + { + if (txtStatus.TextLength > 0) + { + txtStatus.AppendText(Environment.NewLine); + } + txtStatus.AppendText(line ?? string.Empty); + } + + private void SetBusy(bool busy, string statusText) + { + btnBuild.Enabled = !busy; + btnTest.Enabled = !busy; + btnCopy.Enabled = !busy; + btnClear.Enabled = !busy; + btnWhoAmI.Enabled = !busy; + Cursor = busy ? Cursors.WaitCursor : Cursors.Default; + + if (statusText != null) + { + SetStatus(statusText, isError: false); + } + } + + // Only drops the in-process PCA / TokenCredential maps; MSAL's persistent on-disk cache + // and WAM broker accounts are untouched. Sufficient to demo a worker-thread interactive + // prompt when the persistent cache has already been cleared (fresh run or no WAM account + // bound), and a useful reset between back-to-back connects within a single session. + private void MaybeClearTokenCache() + { + if (!chkClearTokenCache.Checked) + { + return; + } + + ActiveDirectoryAuthenticationProvider.ClearUserTokenCache(); + AppendStatus("Cleared in-process MSAL token cache."); + } + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region Nested Types + + private static class EncryptDisplay + { + public const string Mandatory = "Mandatory"; + public const string Optional = "Optional"; + public const string Strict = "Strict"; + } + + /// + /// Tiny wrapper around a raw HWND captured on the UI thread. + /// Used so that MSAL.NET's IWin32WindowFunc callback can safely return a window + /// owner from a worker thread without ever touching off-UI. + /// Only needed on .NET Framework where the legacy SetIWin32WindowFunc API is used. + /// +#if NETFRAMEWORK + private sealed class Win32WindowHandle : IWin32Window + { + private readonly IntPtr _hwnd; + public Win32WindowHandle(IntPtr hwnd) => _hwnd = hwnd; + public IntPtr Handle => _hwnd; + } +#endif + + #endregion + + // ────────────────────────────────────────────────────────────────── + #region Private Fields + + /// + /// The form's Win32 window handle, captured on the UI thread in the constructor. + /// Read from worker threads by the Entra ID provider callbacks to parent MSAL's + /// sign-in / WAM broker UI without illegally touching . + /// + private readonly IntPtr _ownerHwnd; + + #endregion + } +} diff --git a/doc/apps/AzureSqlConnector/ModeSelectorForm.cs b/doc/apps/AzureSqlConnector/ModeSelectorForm.cs new file mode 100644 index 0000000000..651412de32 --- /dev/null +++ b/doc/apps/AzureSqlConnector/ModeSelectorForm.cs @@ -0,0 +1,116 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace Microsoft.Data.SqlClient.Samples.AzureSqlConnector +{ + /// + /// Choice exposed by . + /// + internal enum ConnectionMode + { + /// + /// Use , which calls SqlConnection.OpenAsync() on the UI + /// thread. Relies on the WinForms SynchronizationContext to keep the message pump alive. + /// + UiThreadOpenAsync, + + /// + /// Use , which calls SqlConnection.Open() inside + /// Task.Run on a thread-pool worker. The captured form HWND is passed to MSAL. + /// + WorkerThreadOpen, + } + + /// + /// Tiny modal dialog shown at startup that lets the user pick which connector form + /// (UI-thread async or worker-thread sync) to launch. + /// + internal sealed class ModeSelectorForm : Form + { + private readonly RadioButton _rdoUiThread; + private readonly RadioButton _rdoWorker; + + internal ConnectionMode SelectedMode => + _rdoWorker.Checked ? ConnectionMode.WorkerThreadOpen : ConnectionMode.UiThreadOpenAsync; + + internal ModeSelectorForm() + { + Text = "Azure SQL Connector — Choose Mode"; + FormBorderStyle = FormBorderStyle.FixedDialog; + StartPosition = FormStartPosition.CenterScreen; + MaximizeBox = false; + MinimizeBox = false; + ClientSize = new Size(460, 200); + + Label lblHeader = new Label + { + AutoSize = false, + Text = "Select how SqlConnection.Open should be invoked:", + Location = new Point(16, 14), + Size = new Size(420, 20), + Font = new Font(Font, FontStyle.Bold), + }; + + _rdoUiThread = new RadioButton + { + Text = "&UI thread", + Location = new Point(20, 42), + Size = new Size(420, 20), + Checked = true, + }; + + Label lblUiHint = new Label + { + AutoSize = false, + Text = " Async/Sync open on the UI thread; SynchronizationContext keeps the form responsive.", + Location = new Point(20, 62), + Size = new Size(420, 18), + ForeColor = SystemColors.GrayText, + }; + + _rdoWorker = new RadioButton + { + Text = "&Worker thread", + Location = new Point(20, 90), + Size = new Size(420, 20), + }; + + Label lblWorkerHint = new Label + { + AutoSize = false, + Text = " Sync open on a thread-pool worker; HWND is captured up-front for MSAL.", + Location = new Point(20, 110), + Size = new Size(420, 18), + ForeColor = SystemColors.GrayText, + }; + + Button btnOk = new Button + { + Text = "&Launch", + DialogResult = DialogResult.OK, + Location = new Point(268, 152), + Size = new Size(82, 28), + }; + + Button btnCancel = new Button + { + Text = "Cancel", + DialogResult = DialogResult.Cancel, + Location = new Point(358, 152), + Size = new Size(82, 28), + }; + + AcceptButton = btnOk; + CancelButton = btnCancel; + + Controls.AddRange(new Control[] + { + lblHeader, + _rdoUiThread, lblUiHint, + _rdoWorker, lblWorkerHint, + btnOk, btnCancel, + }); + } + } +} diff --git a/doc/apps/AzureSqlConnector/Program.cs b/doc/apps/AzureSqlConnector/Program.cs new file mode 100644 index 0000000000..c4bfad836c --- /dev/null +++ b/doc/apps/AzureSqlConnector/Program.cs @@ -0,0 +1,39 @@ +using System; +using System.Windows.Forms; + +namespace Microsoft.Data.SqlClient.Samples.AzureSqlConnector +{ + /// + /// Application entry point for the Azure SQL Connector WinForms test app. + /// + internal static class Program + { + /// + /// The main entry point for the application. Shows a small chooser dialog at startup so + /// the user can pick between the UI-thread and the worker-thread + /// variant of the connector. + /// + [STAThread] + private static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + ConnectionMode mode; + using (ModeSelectorForm selector = new ModeSelectorForm()) + { + if (selector.ShowDialog() != DialogResult.OK) + { + return; + } + mode = selector.SelectedMode; + } + + Form main = mode == ConnectionMode.WorkerThreadOpen + ? (Form)new MainFormWorker() + : new MainForm(); + + Application.Run(main); + } + } +} diff --git a/doc/apps/AzureSqlConnector/README.md b/doc/apps/AzureSqlConnector/README.md new file mode 100644 index 0000000000..62f100e966 --- /dev/null +++ b/doc/apps/AzureSqlConnector/README.md @@ -0,0 +1,137 @@ +# Azure SQL Connector (WinForms) + +A small Windows Forms test application that lets a user fill in Azure SQL Database connection +parameters in a UI, builds the corresponding ADO.NET connection string via +`SqlConnectionStringBuilder`, and tests connectivity using `Microsoft.Data.SqlClient`. + +It is intended as a quick, repeatable scratch tool for manually validating connection-string +combinations (server / database / authentication mode / encryption / etc.) against an Azure SQL DB +or SQL Server instance, **and as a manual repro** for the WAM-broker behavior added in this +branch's `ActiveDirectoryAuthenticationProvider`. + +The sample multi-targets: + +| TFM | Purpose | +| ---------------- | ------------------------------------------------------------------------------------------------ | +| `net481` | Exercises the legacy `SetIWin32WindowFunc` API used by .NET Framework callers with WinForms. | +| `net10.0-windows` | Exercises the modern `SetParentActivityOrWindowFunc` API used on .NET 8+. | + +`net10.0-windows` restores and builds cleanly on Linux/macOS hosts even though the resulting +binary only runs on Windows, so the project no longer needs a separate no-op cross-platform +fallback. + +> **Note:** `SetParentActivityOrWindowFunc` is also available on `net481` and is the +> recommended API for new code on any framework. The sample wires `net481` up to +> `SetIWin32WindowFunc` only to keep coverage of that legacy code path; replacing the +> `SetIWin32WindowFunc(() => this)` call with `SetParentActivityOrWindowFunc(() => this.Handle)` +> on `net481` works the same way. + +## Mode selector + +When the app launches it shows a small `ModeSelectorForm` that picks between two top-level forms: + +| Mode | Form | What it exercises | +| ---------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------- | +| **UI thread (`OpenAsync`)** | `MainForm` | Calls `SqlConnection.OpenAsync()` on the UI thread so the Windows Forms message pump stays alive during MSAL sign-in. | +| **Worker thread (`Open`, sync)** | `MainFormWorker` | Calls `SqlConnection.Open()` on a background worker thread; the parent window handle is captured up-front on the UI thread. | + +Both forms demonstrate the supported patterns for parenting the WAM broker (or the legacy +embedded WebView on .NET Framework). + +## Form inputs + +| Field | Maps to connection string keyword | +| -------------------------- | ----------------------------------------------- | +| Server name | `Data Source` | +| Database name | `Initial Catalog` *(only added when non-empty)* | +| Authentication | `Authentication` *(SqlAuthenticationMethod)* | +| User ID | `User ID` | +| Password | `Password` | +| Encrypt | `Encrypt` *(Mandatory / Optional / Strict)* | +| Trust server certificate | `TrustServerCertificate` | +| Connect timeout (s) | `Connect Timeout` | + +The **Authentication** dropdown is populated from every member of +`Microsoft.Data.SqlClient.SqlAuthenticationMethod`. The User ID and Password fields are enabled / +disabled automatically based on the selected method: + +- **SqlPassword** / **ActiveDirectoryPassword** — both User ID and Password are required. +- **ActiveDirectoryServicePrincipal** — User ID = App (Client) ID, Password = client secret. +- **ActiveDirectoryManagedIdentity / MSI / Default / Interactive / DeviceCodeFlow / WorkloadIdentity** + — User ID is optional (e.g. user-assigned MI client id), Password is disabled. +- **ActiveDirectoryIntegrated** — credentials come from the OS, both fields disabled. + +## Buttons + +| Button | Action | +| ----------------------- | ---------------------------------------------------------------------- | +| Build Connection String | Builds the connection string from the form values and displays it. | +| Test Connection | Builds the connection string and opens the connection. | +| Copy to Clipboard | Copies the currently-built connection string to the clipboard. | +| Clear All | Resets every input field to its default state. | +| Who Am I? | Connects and runs an identity query (`SUSER_SNAME()`, `ORIGINAL_LOGIN()`, `USER_NAME()`, `DB_NAME()`, `@@SPID`, etc.) and prints the results. | + +The result pane shows the built connection string with the password masked, the test connection +outcome (including SQL error number when applicable), and the server version on success. + +## Prerequisites + +- Visual Studio 2026 (or any IDE / SDK with .NET Framework **4.8.1** Developer Pack installed) for + the `net481` target. The `net10.0-windows` target only needs the .NET 10 SDK. +- Network connectivity to your Azure SQL Database (server firewall must allow your client IP). +- For Entra ID authentication modes, valid credentials available through Azure CLI / environment + variables / managed identity / the WAM broker, depending on the chosen method. + +## Build & run + +From the project folder: + +```pwsh +dotnet build .\AzureSqlConnector.csproj +dotnet run --project .\AzureSqlConnector.csproj -f net10.0-windows # modern WAM API +dotnet run --project .\AzureSqlConnector.csproj -f net481 # legacy IWin32Window API +``` + +Or load `src\Microsoft.Data.SqlClient.slnx` in Visual Studio, set **AzureSqlConnector** as the +startup project, and press **F5**. + +## Example + +1. **Server name:** `myserver.database.windows.net` +2. **Database name:** `MyDb` +3. **Authentication:** `SqlPassword` +4. **User ID:** `sqladmin` +5. **Password:** *your password* +6. **Encrypt:** `Mandatory` +7. **Trust server certificate:** unchecked +8. Click **Test Connection** — the result pane should display + `Connected successfully! Server version: 12.00.xxxx`. + +## Entra ID parent-window plumbing + +For any `ActiveDirectory*` authentication method (especially **ActiveDirectoryInteractive**) the +app installs an `ActiveDirectoryAuthenticationProvider` and tells it which window should host the +sign-in UI: + +- On **`net481`** the form calls `provider.SetIWin32WindowFunc(() => this)`. This is the legacy + API used by .NET Framework callers with the embedded WebView. +- On **`net10.0-windows`** the form calls + `provider.SetParentActivityOrWindowFunc(() => this.Handle)`. This is the modern API that also + integrates with the WAM broker on Windows. + +The provider is registered for every `SqlAuthenticationMethod.ActiveDirectory*` value at startup. + +### Threading patterns + +| Form | Open mode | Parent window callback | +| ----------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `MainForm` | `OpenAsync` on UI thread | Callback runs on the UI thread when MSAL invokes it, so `this`/`this.Handle` is naturally safe to access. | +| `MainFormWorker` | `Open` (sync) on worker | The form captures `this.Handle` into a field on the UI thread before kicking off the worker; the callback closes over that captured value so it never needs to marshal back. | + +Without one of these patterns the WAM broker (or the embedded WebView on .NET Framework) can fail +to render or stay unresponsive while it waits for the user. + +## Notes + +- This is a sample / diagnostic tool, **not** a product. It does not persist credentials. +- From the repo root: `dotnet run --project .\doc\apps\AzureSqlConnector\AzureSqlConnector.csproj` diff --git a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml index 36f10a6aab..4dd9f61d5a 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml @@ -1,4 +1,9 @@ - + + @@ -74,7 +79,12 @@ Clears cached user tokens from the token provider. - This will cause interactive authentication prompts to appear again if tokens were previously being obtained from the cache. + + This will cause interactive authentication prompts to appear again if tokens were previously being obtained from the cache. + + + The driver's per-pool federated-authentication token cache is also cleared, so subsequent calls will reacquire fed-auth tokens instead of reusing cached entries. + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs index e0adf34e49..5007384465 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs @@ -59,7 +59,7 @@ static Internal() // Look for the manager class. const string className = "Microsoft.Data.SqlClient.SqlAuthenticationProviderManager"; - var manager = assembly.GetType(className); + Type? manager = assembly.GetType(className); if (manager is null) { diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProvider.xml b/src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProvider.xml index 84b6343497..2b58a8676f 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProvider.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProvider.xml @@ -1,4 +1,4 @@ - + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Interop/Interop.GetAncestor.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Interop/Interop.GetAncestor.cs new file mode 100644 index 0000000000..af255844fc --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Interop/Interop.GetAncestor.cs @@ -0,0 +1,42 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.Data.SqlClient; + +/// +/// Win32 P/Invoke wrappers used by for +/// console-window discovery. Follows the .NET runtime's Interop convention: one Win32 +/// import per file, grouped into a nested Interop.<module> static class that mirrors +/// the Win32 DLL it targets. Only the internal helper is exposed; the raw +/// DllImport stays private. +/// +internal static partial class Interop +{ + internal static partial class User32 + { + /// + /// GA_ROOTOWNER flag value for GetAncestor — "Retrieves the owned root + /// window by walking the chain of parent and owner windows returned by GetParent." + /// + private const uint GA_ROOTOWNER = 3; + + /// + /// Raw user32!GetAncestor P/Invoke. Documented by Windows to return + /// rather than throw when the input handle is invalid. + /// + [DllImport("user32.dll")] + private static extern IntPtr GetAncestor(IntPtr hwnd, uint gaFlags); + + /// + /// Walks the parent/owner chain of and returns the root owner + /// window, or when none can be found. + /// + internal static IntPtr GetRootOwner(IntPtr hwnd) + { + return GetAncestor(hwnd, GA_ROOTOWNER); + } + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Interop/Interop.GetConsoleWindow.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Interop/Interop.GetConsoleWindow.cs new file mode 100644 index 0000000000..4638e1458d --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Interop/Interop.GetConsoleWindow.cs @@ -0,0 +1,27 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.Data.SqlClient; + +/// +/// Win32 P/Invoke wrappers used by for +/// console-window discovery. Follows the .NET runtime's Interop convention: one Win32 +/// import per file, grouped into a nested Interop.<module> static class that mirrors +/// the Win32 DLL it targets. +/// +internal static partial class Interop +{ + internal static partial class Kernel32 + { + /// + /// Raw kernel32!GetConsoleWindow P/Invoke. Documented by Windows to return + /// when the calling process is not attached to a console + /// (and to never throw). + /// + [DllImport("kernel32.dll")] + internal static extern IntPtr GetConsoleWindow(); + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs index 5c7572ec84..e90dcce506 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; // These tests were moved from MDS FunctionalTests AADAuthenticationTests.cs. +[Collection("SqlAuthenticationProvider")] public class AADAuthenticationTests { [Fact] diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs index 8c4006c5b4..5dd3ac336e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs @@ -26,120 +26,6 @@ public static void KustoDatabaseTest() Assert.Equal(System.Data.ConnectionState.Open, connection.State); } - [ConditionalFact( - typeof(Config), - nameof(Config.HasPasswordConnectionString))] - public static void AADPasswordWithWrongPassword() - { - string[] credKeys = { "Password", "PWD" }; - string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, credKeys) + "Password=TestPassword;"; - - Assert.Throws(() => ConnectAndDisconnect(connStr)); - - // We cannot verify error message with certainty as driver may cache token from other tests for current user - // and error message may change accordingly. - } - - [ConditionalFact( - typeof(Config), - nameof(Config.HasPasswordConnectionString))] - public static void TestADPasswordAuthentication() - { - // Connect to Azure DB with password and retrieve user name. - using (SqlConnection conn = new SqlConnection(Config.PasswordConnectionString)) - { - conn.Open(); - using (SqlCommand sqlCommand = new SqlCommand - ( - cmdText: $"SELECT SUSER_SNAME();", - connection: conn, - transaction: null - )) - { - string customerId = (string)sqlCommand.ExecuteScalar(); - string expected = RetrieveValueFromConnStr(Config.PasswordConnectionString, new string[] { "User ID", "UID" }); - Assert.Equal(expected, customerId); - } - } - } - - [ConditionalFact( - typeof(Config), - nameof(Config.HasPasswordConnectionString))] - public static void EmptyPasswordInConnStrAADPassword() - { - // connection fails with expected error message. - string[] pwdKey = { "Password", "PWD" }; - string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, pwdKey) + "Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string? user = FetchKeyInConnStr(Config.PasswordConnectionString, new string[] { "User Id", "UID" }); - string expectedMessage = string.Format("Failed to authenticate the user {0} in Active Directory (Authentication=ActiveDirectoryPassword).", user); - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact( - typeof(Config), - nameof(Config.OnWindows), - nameof(Config.HasPasswordConnectionString))] - public static void EmptyCredInConnStrAADPassword() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "Password", "UID", "PWD" }; - string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + "User ID=; Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = "Failed to authenticate the user in Active Directory (Authentication=ActiveDirectoryPassword)."; - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact( - typeof(Config), - nameof(Config.OnUnix), - nameof(Config.HasPasswordConnectionString))] - public static void EmptyCredInConnStrAADPasswordAnyUnix() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "Password", "UID", "PWD" }; - string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + "User ID=; Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = "MSAL cannot determine the username (UPN) of the currently logged in user.For Integrated Windows Authentication and Username/Password flows, please use .WithUsername() before calling ExecuteAsync()."; - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact( - typeof(Config), - nameof(Config.HasPasswordConnectionString))] - public static void AADPasswordWithInvalidUser() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "UID" }; - string user = "testdotnet@domain.com"; - string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + $"User ID={user}"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = string.Format("Failed to authenticate the user {0} in Active Directory (Authentication=ActiveDirectoryPassword).", user); - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact( - typeof(Config), - nameof(Config.HasPasswordConnectionString))] - public static void NoCredentialsActiveDirectoryPassword() - { - // test Passes with correct connection string. - ConnectAndDisconnect(Config.PasswordConnectionString); - - // connection fails with expected error message. - string[] credKeys = { "User ID", "Password", "UID", "PWD" }; - string connStrWithNoCred = RemoveKeysInConnStr(Config.PasswordConnectionString, credKeys); - InvalidOperationException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - - string expectedMessage = "Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication=Active Directory Password'."; - Assert.Contains(expectedMessage, e.Message); - } - [ConditionalFact( typeof(Config), nameof(Config.HasPasswordConnectionString), diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs index cec4721b95..1f0587685e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs @@ -40,6 +40,7 @@ internal static class Config internal static bool DebugEmit { get; } = false; internal static bool IntegratedSecuritySupported { get; } = false; internal static bool ManagedIdentitySupported { get; } = false; + // @TODO Remove PasswordConnectionString from config; AAD Password auth is deprecated internal static string PasswordConnectionString { get; } = string.Empty; internal static string ServicePrincipalId { get; } = string.Empty; internal static string ServicePrincipalSecret { get; } = string.Empty; diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs index 84d32651d5..fa426141c9 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs @@ -4,6 +4,7 @@ namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; +[Collection("SqlAuthenticationProvider")] public class DefaultAuthProviderTests { // Verify that our auth provider has been installed for all AAD/Entra diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/SqlAuthenticationProviderCollection.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/SqlAuthenticationProviderCollection.cs new file mode 100644 index 0000000000..2bf23f7873 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/SqlAuthenticationProviderCollection.cs @@ -0,0 +1,14 @@ +// 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.Extensions.Azure.Test; + +/// +/// Defines a test collection that serializes execution of test classes +/// which mutate the global registry. +/// +[CollectionDefinition("SqlAuthenticationProvider")] +public class SqlAuthenticationProviderCollection +{ +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WamBrokerTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WamBrokerTests.cs new file mode 100644 index 0000000000..9b2aa5dcf7 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WamBrokerTests.cs @@ -0,0 +1,316 @@ +// 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.Reflection; + +namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; + +[Collection("SqlAuthenticationProvider")] +public class WamBrokerTests +{ + // The SqlClient first-party application client id that is hard-coded in the provider. + private const string SqlClientApplicationId = "2fd908ad-0664-4344-b9be-cd3e8b574c38"; + + // A fixed, deterministic stand-in for a caller-supplied application id. Hard-coded (instead + // of Guid.NewGuid()) so test outcomes don't depend on RNG and so a single point asserts + // that this value differs from the SqlClient first-party id. + private const string TestCustomAppId = "11111111-2222-3333-4444-555555555555"; + + // Reads the private _parentActivityOrWindowFunc field. Used to assert downstream effects + // of SetParentActivityOrWindowFunc without triggering a live MSAL flow. + private static Func? GetParentActivityOrWindowFunc(ActiveDirectoryAuthenticationProvider provider) + { + FieldInfo? field = typeof(ActiveDirectoryAuthenticationProvider).GetField( + "_parentActivityOrWindowFunc", + BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field); + return (Func?)field!.GetValue(provider); + } + + /// + /// A callback is treated as "clear any previously installed callback" + /// and must not throw. This is a deliberate API contract change from the original + /// behavior so callers can opt out without recreating + /// the provider. Asserts the underlying field is reset to so the + /// provider's downstream consumer (MSAL parameters builder) sees the cleared state. + /// + [Fact] + public void SetParentActivityOrWindowFunc_Null_ClearsCallback() + { + var provider = new ActiveDirectoryAuthenticationProvider(); + Func first = () => IntPtr.Zero; + provider.SetParentActivityOrWindowFunc(first); + Assert.Same(first, GetParentActivityOrWindowFunc(provider)); + + provider.SetParentActivityOrWindowFunc(null); + Assert.Null(GetParentActivityOrWindowFunc(provider)); + } + + /// + /// The constructor uses the SqlClient first-party application id, which always + /// enables WAM broker mode regardless of any opt-in flag. + /// + [Fact] + public void Ctor_ApplicationClientId_EnablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider(SqlClientApplicationId); + Assert.Equal(SqlClientApplicationId, provider.ApplicationClientId); + Assert.True(provider.UseWamBroker, + "Constructor with SqlClient first-party application id must enable WAM broker."); + } + + /// + /// The parameterless constructor uses the SqlClient first-party application id, which always + /// enables WAM broker mode regardless of any opt-in flag. + /// + [Fact] + public void Ctor_Default_EnablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider(); + Assert.Equal(SqlClientApplicationId, provider.ApplicationClientId); + Assert.True(provider.UseWamBroker, + "Default ctor must enable WAM broker (uses SqlClient first-party application id)."); + } + + /// A caller-supplied application id without explicit opt-in must NOT enable WAM broker. + [Fact] + public void Ctor_AppClientId_DefaultsUseWamBrokerToFalse() + { + var provider = new ActiveDirectoryAuthenticationProvider(TestCustomAppId); + + Assert.Equal(TestCustomAppId, provider.ApplicationClientId); + Assert.False(provider.UseWamBroker, + "Custom application id without useWamBroker=true must keep WAM broker disabled."); + } + + /// + /// Mirrors the previous test for the + /// constructor: a caller (or app.config) that sets only ApplicationClientId and skips + /// UseWamBroker must get the documented default of . This is + /// the contract SqlAuthenticationProviderManager relies on when reflecting onto the + /// Options ctor and only forwarding the properties that were explicitly configured. + /// + [Fact] + public void Ctor_Options_AppClientIdOnly_DefaultsUseWamBrokerToFalse() + { + var provider = new ActiveDirectoryAuthenticationProvider( + new ActiveDirectoryAuthenticationProviderOptions + { + ApplicationClientId = TestCustomAppId, + // UseWamBroker intentionally left at its default (false). + }); + + Assert.Equal(TestCustomAppId, provider.ApplicationClientId); + Assert.False(provider.UseWamBroker, + "Options ctor with ApplicationClientId set and UseWamBroker omitted must keep WAM broker disabled."); + } + + /// + /// Passing the SqlClient first-party application id to the single-string constructor must + /// enable WAM broker. The first-party app id is hard-wired to the WAM broker redirect URI, + /// so callers that opt into it explicitly should get the same behavior as the parameterless + /// constructor. + /// + [Fact] + public void Ctor_AppClientId_SqlClientId_EnablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider(SqlClientApplicationId); + + Assert.Equal(SqlClientApplicationId, provider.ApplicationClientId); + Assert.True(provider.UseWamBroker, + "Single-string ctor with the SqlClient first-party id must enable WAM broker."); + } + + /// A caller-supplied application id with explicit opt-in must enable WAM broker. + [Fact] + public void Ctor_AppClientId_UseWamBrokerTrue_EnablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider( + new ActiveDirectoryAuthenticationProviderOptions + { + ApplicationClientId = TestCustomAppId, + UseWamBroker = true, + }); + + Assert.Equal(TestCustomAppId, provider.ApplicationClientId); + Assert.True(provider.UseWamBroker, + "Custom application id with UseWamBroker=true must enable WAM broker."); + } + + /// A caller-supplied application id with explicit opt-out keeps WAM broker disabled. + [Fact] + public void Ctor_AppClientId_UseWamBrokerFalse_DisablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider( + new ActiveDirectoryAuthenticationProviderOptions + { + ApplicationClientId = TestCustomAppId, + UseWamBroker = false, + }); + + Assert.Equal(TestCustomAppId, provider.ApplicationClientId); + Assert.False(provider.UseWamBroker); + } + + /// + /// Even when the SqlClient first-party application id is passed explicitly with + /// UseWamBroker=false, WAM broker mode must remain enabled because the first-party + /// app id is hard-wired to the WAM broker redirect URI. This guards the OR-condition in + /// the provider's constructor. + /// + [Fact] + public void Ctor_SqlClientAppIdExplicit_UseWamBrokerFalse_StillEnablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider( + new ActiveDirectoryAuthenticationProviderOptions + { + ApplicationClientId = SqlClientApplicationId, + UseWamBroker = false, + }); + + Assert.Equal(SqlClientApplicationId, provider.ApplicationClientId); + Assert.True(provider.UseWamBroker, + "SqlClient first-party application id must always enable WAM broker, regardless of the UseWamBroker option."); + } + + /// + /// Passing a device-code callback together with a custom application id and + /// UseWamBroker=true via + /// must enable WAM broker mode. + /// + [Fact] + public void Ctor_WithDeviceCodeCallback_UseWamBrokerTrue_EnablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider( + new ActiveDirectoryAuthenticationProviderOptions + { + DeviceCodeFlowCallback = static _ => Task.CompletedTask, + ApplicationClientId = TestCustomAppId, + UseWamBroker = true, + }); + + Assert.Equal(TestCustomAppId, provider.ApplicationClientId); + Assert.True(provider.UseWamBroker); + } + + /// + /// The two-arg device-code constructor (deviceCodeCallback, applicationClientId) must default + /// useWamBroker to for caller-supplied application ids. + /// + [Fact] + public void Ctor_WithDeviceCodeCallback_AppClientIdOnly_DefaultsUseWamBrokerToFalse() + { + var provider = new ActiveDirectoryAuthenticationProvider( + deviceCodeFlowCallbackMethod: static _ => Task.CompletedTask, + applicationClientId: TestCustomAppId); + + Assert.False(provider.UseWamBroker); + Assert.NotEqual(SqlClientApplicationId, provider.ApplicationClientId); + } + + /// + /// When the device-code callback constructor is invoked without an application id, the + /// provider falls back to the SqlClient first-party id and must enable WAM broker. + /// + [Fact] + public void Ctor_WithDeviceCodeCallback_NoAppClientId_EnablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider( + deviceCodeFlowCallbackMethod: static _ => Task.CompletedTask); + + Assert.True(provider.UseWamBroker); + Assert.Equal(SqlClientApplicationId, provider.ApplicationClientId); + } + + /// + /// The -based constructor + /// is the recommended overload for new code. It must honor + /// the same way the positional-argument overloads do. + /// + [Fact] + public void Ctor_Options_CustomAppId_UseWamBrokerTrue_EnablesWamBroker() + { + var provider = new ActiveDirectoryAuthenticationProvider( + new ActiveDirectoryAuthenticationProviderOptions + { + ApplicationClientId = TestCustomAppId, + UseWamBroker = true, + }); + + Assert.Equal(TestCustomAppId, provider.ApplicationClientId); + Assert.True(provider.UseWamBroker); + } + + /// + /// Options with ApplicationClientId = null falls back to the SqlClient first-party + /// id, which always enables WAM broker, regardless of UseWamBroker. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Ctor_Options_NullAppId_AlwaysEnablesWamBroker(bool useWamBroker) + { + var provider = new ActiveDirectoryAuthenticationProvider( + new ActiveDirectoryAuthenticationProviderOptions + { + ApplicationClientId = null, + UseWamBroker = useWamBroker, + }); + + Assert.Equal(SqlClientApplicationId, provider.ApplicationClientId); + Assert.True(provider.UseWamBroker); + } + + /// + /// The Options-based constructor must reject a options instance with + /// so misuse fails fast at construction. + /// + [Fact] + public void Ctor_Options_Null_ThrowsArgumentNullException() + { + Assert.Throws( + () => new ActiveDirectoryAuthenticationProvider((ActiveDirectoryAuthenticationProviderOptions)null!)); + } + + /// + /// Registering an instance via must not + /// wrap or replace the instance, so its WAM broker setting survives registration. + /// + /// + /// Provider registration mutates global state shared across this test class collection + /// (and any other test that depends on the default provider being installed). Save and + /// restore the original provider in a finally block to keep cross-test isolation. + /// + [Fact] + public void Ctor_RegisteredAsProvider_PreservesUseWamBrokerSetting() + { + var provider = new ActiveDirectoryAuthenticationProvider( + new ActiveDirectoryAuthenticationProviderOptions + { + ApplicationClientId = TestCustomAppId, + UseWamBroker = true, + }); + + SqlAuthenticationProvider? original = + SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive); + try + { + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, provider); + + var retrieved = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive) + as ActiveDirectoryAuthenticationProvider; + Assert.NotNull(retrieved); + Assert.Same(provider, retrieved); + Assert.Equal(TestCustomAppId, retrieved!.ApplicationClientId); + Assert.True(retrieved.UseWamBroker); + } + finally + { + if (original is not null) + { + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, original); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index a578a82db6..c0c12132e7 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -10,6 +10,9 @@ + + + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 3d67df236e..d87333c05b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -2226,7 +2226,6 @@ private void AttemptOneLogin( // TODO: if this call timed out, what reason do we have to believe some other call succeeded? why not just fail? private bool AttemptRetryADAuthWithTimeoutError( SqlException sqlex, - SqlConnectionOptions connectionOptions, // @TODO: this is not used TimeoutTimer timeout) { if (!_activeDirectoryAuthTimeoutRetryHelper.CanRetryWithSqlException(sqlex)) @@ -2236,8 +2235,10 @@ private bool AttemptRetryADAuthWithTimeoutError( // Reset client-side timeout. timeout.Reset(); - // When server timeout, the auth context key was already created. Clean it up here. + // Clear fed-auth state captured by the failed attempt so OnFedAuthInfo's cache-reuse branch starts from null on the retry. _dbConnectionPoolAuthenticationContextKey = null; + _fedAuthToken = null; + _newDbConnectionPoolAuthenticationContext = null; // When server timeouts, connection is doomed. Reset here to allow reconnection. UnDoomThisConnection(); @@ -3335,7 +3336,7 @@ private void LoginNoFailover( } catch (SqlException sqlex) { - if (AttemptRetryADAuthWithTimeoutError(sqlex, connectionOptions, timeout)) + if (AttemptRetryADAuthWithTimeoutError(sqlex, timeout)) { continue; } @@ -3648,7 +3649,7 @@ private void LoginWithFailover( } catch (SqlException sqlex) { - if (AttemptRetryADAuthWithTimeoutError(sqlex, connectionOptions, timeout)) + if (AttemptRetryADAuthWithTimeoutError(sqlex, timeout)) { continue; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index 653063b68e..4c77fbe8df 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -32,7 +32,7 @@ internal sealed class SqlAuthenticationProviderManager // The public key token of our Azure extension assembly, used to avoid loading imposter // assemblies. - private static readonly byte[] azurePublicKeyToken = [ 0x23, 0xec, 0x7f, 0xc2, 0xd6, 0xea, 0xa4, 0xa5 ]; + private static readonly byte[] s_azurePublicKeyToken = [ 0x23, 0xec, 0x7f, 0xc2, 0xd6, 0xea, 0xa4, 0xa5 ]; static SqlAuthenticationProviderManager() { @@ -70,10 +70,10 @@ static SqlAuthenticationProviderManager() nameof(SqlAuthenticationProviderManager) + $": Attempting to load Azure extension assembly={azureAssemblyName} with " + "expected public key token=" + - BitConverter.ToString(azurePublicKeyToken).Replace("-", "")); + BitConverter.ToString(s_azurePublicKeyToken).Replace("-", "")); var qualifiedName = new AssemblyName(azureAssemblyName); - qualifiedName.SetPublicKeyToken(azurePublicKeyToken); + qualifiedName.SetPublicKeyToken(s_azurePublicKeyToken); // The .NET Framework runtime will enforce the token during binding, causing Load() // to throw. This prevents an untrusted assembly from being loaded and having its @@ -92,7 +92,7 @@ static SqlAuthenticationProviderManager() { byte[]? actualToken = assembly.GetName().GetPublicKeyToken(); - if (actualToken is null || !actualToken.AsSpan().SequenceEqual(azurePublicKeyToken)) + if (actualToken is null || !actualToken.AsSpan().SequenceEqual(s_azurePublicKeyToken)) { SqlClientEventSource.Log.TryTraceEvent( nameof(SqlAuthenticationProviderManager) + @@ -132,7 +132,7 @@ static SqlAuthenticationProviderManager() // Look for the authentication provider class. const string className = "Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider"; - var type = assembly.GetType(className); + Type? type = assembly.GetType(className); if (type is null) { @@ -144,11 +144,28 @@ static SqlAuthenticationProviderManager() return; } - // Try to instantiate it. - var instance = Activator.CreateInstance( + // Try to instantiate it. Behavior depends on what the app + // configured in : + // * Neither applicationClientId nor useWamBroker -> use the + // parameterless constructor (defaults to the SqlClient + // first-party app id and enables WAM brokering on Windows). + // * applicationClientId only -> prefer the + // (ActiveDirectoryAuthenticationProviderOptions) constructor + // when the Azure extension exposes it; otherwise fall back + // to the legacy (string applicationClientId) constructor so + // older Azure extension versions keep working. + // * useWamBroker (with or without applicationClientId) -> + // requires the (Options) constructor because there is no + // positional analog. If the Azure extension is too old to + // expose Options, throw to surface the misconfiguration. + const string optionsTypeName = "Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions"; + Type? optionsType = assembly.GetType(optionsTypeName); + + SqlAuthenticationProvider? instance = CreateAzureAuthenticationProvider( type, - [Instance._applicationClientId]) - as SqlAuthenticationProvider; + optionsType, + Instance._applicationClientId, + Instance._useWamBroker); if (instance is null) { @@ -189,6 +206,7 @@ static SqlAuthenticationProviderManager() // attempt to use Active Directory authentication. catch (Exception ex) when (ex is + AmbiguousMatchException or ArgumentException or BadImageFormatException or FileLoadException or @@ -198,6 +216,7 @@ MethodAccessException or MissingMethodException or NotSupportedException or TargetInvocationException or + TypeInitializationException or TypeLoadException) { SqlClientEventSource.Log.TryTraceEvent( @@ -216,6 +235,13 @@ TargetInvocationException or private readonly SqlClientLogger _sqlAuthLogger = new SqlClientLogger(); private readonly string? _applicationClientId = null; + // Optional override for ActiveDirectoryAuthenticationProviderOptions.UseWamBroker + // read from the app.config attribute. + // null means the app did not configure the value, in which case we leave the + // provider's default behavior (WAM is implied by the SqlClient first-party app id and + // off otherwise) untouched. + private readonly bool? _useWamBroker = null; + /// /// Constructor. /// @@ -239,6 +265,23 @@ private SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationS _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "No user-defined Application Client Id found."); } + if (!string.IsNullOrEmpty(configSection.UseWamBroker)) + { + if (bool.TryParse(configSection.UseWamBroker, out bool useWamBroker)) + { + _useWamBroker = useWamBroker; + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, $"Received user-defined UseWamBroker={useWamBroker}."); + } + else + { + _sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Ignoring user-defined UseWamBroker='{configSection.UseWamBroker}': not a valid boolean."); + } + } + else + { + _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, "No user-defined UseWamBroker found."); + } + // Create user-defined auth initializer, if any. if (!string.IsNullOrEmpty(configSection.InitializerType)) { @@ -312,8 +355,81 @@ private SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationS /// Authentication provider or null if not found. internal static SqlAuthenticationProvider? GetProvider(SqlAuthenticationMethod authenticationMethod) { - SqlAuthenticationProvider? value; - return Instance._providers.TryGetValue(authenticationMethod, out value) ? value : null; + return Instance._providers.TryGetValue(authenticationMethod, out SqlAuthenticationProvider? value) ? value : null; + } + + // Reflectively constructs the Azure extension's ActiveDirectoryAuthenticationProvider, + // selecting the constructor that matches what the app configured. Extracted from the + // static initializer so it can be unit-tested with stub provider/options shapes. + // + // Returns null when no compatible constructor is available (e.g. a custom assembly + // that lacks both the (Options) and (string) ctors). + // + // Throws InvalidOperationException when useWamBroker is configured but the Azure + // extension is too old to expose ActiveDirectoryAuthenticationProviderOptions; that + // signals user-actionable misconfiguration and intentionally escapes the static ctor's + // catch-when filter so it surfaces as a TypeInitializationException. + internal static SqlAuthenticationProvider? CreateAzureAuthenticationProvider( + Type providerType, + Type? optionsType, + string? applicationClientId, + bool? useWamBroker) + { + if (applicationClientId is null && useWamBroker is null) + { + return Activator.CreateInstance(providerType) as SqlAuthenticationProvider; + } + + ConstructorInfo? optionsCtor = optionsType is null + ? null + : providerType.GetConstructor([optionsType]); + + if (useWamBroker is bool useWam) + { + if (optionsType is null || optionsCtor is null) + { + throw SQL.UseWamBrokerRequiresAzureExtensionUpgrade(); + } + + var options = Activator.CreateInstance(optionsType); + if (options is null) + { + return null; + } + + if (applicationClientId is not null) + { + optionsType.GetProperty("ApplicationClientId") + ?.SetValue(options, applicationClientId); + } + optionsType.GetProperty("UseWamBroker") + ?.SetValue(options, useWam); + + return optionsCtor.Invoke([options]) as SqlAuthenticationProvider; + } + + // applicationClientId-only: prefer Options when the extension exposes it, + // otherwise fall back to the legacy (string) ctor for backward compatibility + // with older Azure extension versions. + if (optionsType is not null && optionsCtor is not null) + { + var options = Activator.CreateInstance(optionsType); + if (options is null) + { + return null; + } + optionsType.GetProperty("ApplicationClientId") + ?.SetValue(options, applicationClientId); + return optionsCtor.Invoke([options]) as SqlAuthenticationProvider; + } + + ConstructorInfo? legacyCtor = providerType.GetConstructor([typeof(string)]); + if (legacyCtor is not null) + { + return legacyCtor.Invoke([applicationClientId]) as SqlAuthenticationProvider; + } + + return null; } /// @@ -448,6 +564,15 @@ internal class SqlAuthenticationProviderConfigurationSection : ConfigurationSect /// [ConfigurationProperty("applicationClientId", IsRequired = false)] public string ApplicationClientId => this["applicationClientId"] as string ?? string.Empty; + + /// + /// Forwarded to ActiveDirectoryAuthenticationProviderOptions.UseWamBroker + /// when the Azure extension's default provider is auto-installed. Stored as a string so + /// that an unset attribute can be distinguished from useWamBroker="false"; the + /// runtime parses it with . + /// + [ConfigurationProperty("useWamBroker", IsRequired = false)] + public string UseWamBroker => this["useWamBroker"] as string ?? string.Empty; } /// 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 84667d99e7..049734993e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -511,6 +511,11 @@ internal static Exception UnsupportedAuthenticationByProvider(string authenticat return ADP.NotSupported(StringsHelper.GetString(Strings.SQL_UnsupportedAuthenticationByProvider, type, authentication)); } + internal static Exception UseWamBrokerRequiresAzureExtensionUpgrade() + { + return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_UseWamBrokerRequiresAzureExtensionUpgrade)); + } + internal static Exception CannotFindAuthProvider(SqlAuthenticationMethod authentication) { string authName = authentication.ToString(); diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs index 041b9aa8aa..eed5c58a69 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs @@ -3048,6 +3048,15 @@ internal static string SQL_CannotFindActiveDirectoryAuthProvider { } } + /// + /// Looks up a localized string similar to Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to a version that includes this type.. + /// + internal static string SQL_UseWamBrokerRequiresAzureExtensionUpgrade { + get { + return ResourceManager.GetString("SQL_UseWamBrokerRequiresAzureExtensionUpgrade", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot find an authentication provider for '{0}'.. /// diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx index ca9dcf0a93..0a65febfde 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx @@ -606,6 +606,9 @@ Cannot find an authentication provider for '{0}'. Install the 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet package (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) to use Active Directory (Entra ID) authentication methods. + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Parameter '{0}' cannot be null or empty. diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs index 778d69991b..08089abe98 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs @@ -73,4 +73,200 @@ public void Abstractions_And_Manager_GetSetProvider_Equivalent() SqlAuthenticationProvider.GetProvider( SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } + + // Regression: the manager's static initializer reflectively constructs the Azure extension's + // ActiveDirectoryAuthenticationProvider. That class has overlapping 1-arg constructors + // ((string) and (ProviderOptions)), so calling Activator.CreateInstance(type, [null]) used + // to throw AmbiguousMatchException -- which surfaced as TypeInitializationException from + // GetProvider and broke every AD-authenticated connection. Calling GetProvider for an AD + // method must succeed (returning either the registered provider or null) and must not throw. + [Fact] + public void GetProvider_ForActiveDirectoryMethod_DoesNotThrow() + { + foreach (SqlAuthenticationMethod method in new[] + { + SqlAuthenticationMethod.ActiveDirectoryIntegrated, + #pragma warning disable CS0618 // ActiveDirectoryPassword is obsolete. + SqlAuthenticationMethod.ActiveDirectoryPassword, + #pragma warning restore CS0618 + SqlAuthenticationMethod.ActiveDirectoryInteractive, + SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, + SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, + SqlAuthenticationMethod.ActiveDirectoryMSI, + SqlAuthenticationMethod.ActiveDirectoryDefault, + SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, + }) + { + // No assertion on the value -- the provider may or may not be installed depending on + // whether the Azure extension is on disk. We only assert no throw (which is what a + // TypeInitializationException from the static initializer would do). + _ = SqlAuthenticationProviderManager.GetProvider(method); + } + } + + // CreateAzureAuthenticationProvider tests ---------------------------------------------- + // + // Each Stub* container mimics one shape the real Azure extension might expose: + // * StubModern - both a (string) ctor and an (Options) ctor. + // * StubLegacy - only the (string) ctor; no Options type at all. + // * StubMinimal - only a parameterless ctor. + // + // The helper takes a Type directly, so these stubs do not need any particular full name. + + public class StubProviderBase : SqlAuthenticationProvider + { + public string? CapturedApplicationClientId; + public bool? CapturedUseWamBroker; + public bool ParameterlessCtorUsed; + public bool StringCtorUsed; + public bool OptionsCtorUsed; + + public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + => Task.FromResult(new SqlAuthenticationToken("stub", DateTimeOffset.UtcNow.AddMinutes(5))); + + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) => true; + } + + public static class StubModern + { + public sealed class ActiveDirectoryAuthenticationProviderOptions + { + public string? ApplicationClientId { get; set; } + public bool UseWamBroker { get; set; } + } + + public sealed class ActiveDirectoryAuthenticationProvider : StubProviderBase + { + public ActiveDirectoryAuthenticationProvider() { ParameterlessCtorUsed = true; } + + public ActiveDirectoryAuthenticationProvider(string applicationClientId) + { + StringCtorUsed = true; + CapturedApplicationClientId = applicationClientId; + } + + public ActiveDirectoryAuthenticationProvider(ActiveDirectoryAuthenticationProviderOptions options) + { + OptionsCtorUsed = true; + CapturedApplicationClientId = options.ApplicationClientId; + CapturedUseWamBroker = options.UseWamBroker; + } + } + } + + public static class StubLegacy + { + // No Options type defined -- mimics older Azure extension versions. + public sealed class ActiveDirectoryAuthenticationProvider : StubProviderBase + { + public ActiveDirectoryAuthenticationProvider() { ParameterlessCtorUsed = true; } + + public ActiveDirectoryAuthenticationProvider(string applicationClientId) + { + StringCtorUsed = true; + CapturedApplicationClientId = applicationClientId; + } + } + } + + public static class StubMinimal + { + // Parameterless only -- mimics a hypothetical extension with no 1-arg ctors at all. + public sealed class ActiveDirectoryAuthenticationProvider : StubProviderBase + { + public ActiveDirectoryAuthenticationProvider() { ParameterlessCtorUsed = true; } + } + } + + [Fact] + public void CreateAzureAuthenticationProvider_NeitherConfigured_UsesParameterlessCtor() + { + var instance = SqlAuthenticationProviderManager.CreateAzureAuthenticationProvider( + typeof(StubModern.ActiveDirectoryAuthenticationProvider), + typeof(StubModern.ActiveDirectoryAuthenticationProviderOptions), + applicationClientId: null, + useWamBroker: null); + + var stub = Assert.IsType(instance); + Assert.True(stub.ParameterlessCtorUsed); + Assert.False(stub.StringCtorUsed); + Assert.False(stub.OptionsCtorUsed); + Assert.Null(stub.CapturedApplicationClientId); + Assert.Null(stub.CapturedUseWamBroker); + } + + [Fact] + public void CreateAzureAuthenticationProvider_AppIdOnly_OptionsAvailable_UsesOptionsCtor() + { + var instance = SqlAuthenticationProviderManager.CreateAzureAuthenticationProvider( + typeof(StubModern.ActiveDirectoryAuthenticationProvider), + typeof(StubModern.ActiveDirectoryAuthenticationProviderOptions), + applicationClientId: "app-123", + useWamBroker: null); + + var stub = Assert.IsType(instance); + Assert.True(stub.OptionsCtorUsed); + Assert.False(stub.StringCtorUsed); + Assert.Equal("app-123", stub.CapturedApplicationClientId); + Assert.Equal(false, stub.CapturedUseWamBroker); + } + + [Fact] + public void CreateAzureAuthenticationProvider_AppIdOnly_OptionsMissing_FallsBackToStringCtor() + { + var instance = SqlAuthenticationProviderManager.CreateAzureAuthenticationProvider( + typeof(StubLegacy.ActiveDirectoryAuthenticationProvider), + optionsType: null, + applicationClientId: "legacy-456", + useWamBroker: null); + + var stub = Assert.IsType(instance); + Assert.True(stub.StringCtorUsed); + Assert.False(stub.OptionsCtorUsed); + Assert.False(stub.ParameterlessCtorUsed); + Assert.Equal("legacy-456", stub.CapturedApplicationClientId); + Assert.Null(stub.CapturedUseWamBroker); + } + + [Fact] + public void CreateAzureAuthenticationProvider_AppIdOnly_NoCompatibleCtor_ReturnsNull() + { + var instance = SqlAuthenticationProviderManager.CreateAzureAuthenticationProvider( + typeof(StubMinimal.ActiveDirectoryAuthenticationProvider), + optionsType: null, + applicationClientId: "no-ctor", + useWamBroker: null); + + Assert.Null(instance); + } + + [Fact] + public void CreateAzureAuthenticationProvider_UseWamBroker_OptionsMissing_Throws() + { + InvalidOperationException ex = Assert.Throws(() => + SqlAuthenticationProviderManager.CreateAzureAuthenticationProvider( + typeof(StubLegacy.ActiveDirectoryAuthenticationProvider), + optionsType: null, + applicationClientId: null, + useWamBroker: true)); + + Assert.Contains("ActiveDirectoryAuthenticationProviderOptions", ex.Message); + Assert.Contains("Microsoft.Data.SqlClient.Extensions.Azure", ex.Message); + } + + [Fact] + public void CreateAzureAuthenticationProvider_UseWamBroker_OptionsAvailable_UsesOptionsCtor() + { + var instance = SqlAuthenticationProviderManager.CreateAzureAuthenticationProvider( + typeof(StubModern.ActiveDirectoryAuthenticationProvider), + typeof(StubModern.ActiveDirectoryAuthenticationProviderOptions), + applicationClientId: "app-789", + useWamBroker: true); + + var stub = Assert.IsType(instance); + Assert.True(stub.OptionsCtorUsed); + Assert.Equal("app-789", stub.CapturedApplicationClientId); + Assert.Equal(true, stub.CapturedUseWamBroker); + } } From 44e1b725a255cd39d2b95b3bc6a67131da4babb5 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Mon, 22 Jun 2026 11:50:13 -0500 Subject: [PATCH 155/298] PR Pipeline - Code Coverage and Triggers (#4360) * First crack at adding test result publishing to non-manual test jobs. # Conflicts: # eng/pipelines/pr/jobs/test-buildproj-job.yml # eng/pipelines/pr/stages/test-stages.yml * If statement syntax? * Ok forget the if statement. * Version * Unique job names... could be better, but let's start here. * Tree in linux? * Ok forget it, we don't need do emit the tree.... * Wire up test result uploading to sqlclient manual test jobs * Give a test display name to sql manual tests * First attempt at code coverage report publishing * :robot: Code coverage takes a dynamic dependency on the platforms to ensure all tests are first. * We only need *one* set of parameters * We only need *one* pool name * Add triggers and fix code coverage verbosity thingy * :robot: fixes for codecov CLI * :curly_haired_man: and :robot: comments * :robot: Use dotnet-tools.json * Comments for general variables Rename testResultsArtifactsName to testResultsArtifactsBaseName Remove codecov uploads Ensure folder exists --- eng/pipelines/pr/jobs/test-buildproj-job.yml | 18 +++ .../pr/jobs/test-sqlclientmanual-job.yml | 19 +++ eng/pipelines/pr/pr-pipeline.yml | 94 ----------- eng/pipelines/pr/sqlclient-pr-pipeline.yml | 147 ++++++++++++++++++ .../pr/stages/collect-coverage-stage.yml | 75 +++++++++ eng/pipelines/pr/stages/test-stages.yml | 13 ++ .../pr/steps/publish-test-results-step.yml | 63 ++++++++ eng/pipelines/pr/variables/pr-variables.yml | 9 ++ 8 files changed, 344 insertions(+), 94 deletions(-) delete mode 100644 eng/pipelines/pr/pr-pipeline.yml create mode 100644 eng/pipelines/pr/sqlclient-pr-pipeline.yml create mode 100644 eng/pipelines/pr/stages/collect-coverage-stage.yml create mode 100644 eng/pipelines/pr/steps/publish-test-results-step.yml diff --git a/eng/pipelines/pr/jobs/test-buildproj-job.yml b/eng/pipelines/pr/jobs/test-buildproj-job.yml index ec2d292ec8..c232513e1f 100644 --- a/eng/pipelines/pr/jobs/test-buildproj-job.yml +++ b/eng/pipelines/pr/jobs/test-buildproj-job.yml @@ -18,6 +18,10 @@ parameters: - name: buildSuffix type: string + # Name of the artifact to upload the test results (including code coverage) to. + - name: testResultsArtifactBaseName + type: string + # Dotnet CLI verbosity level. - name: dotnetVerbosity type: string @@ -78,6 +82,10 @@ jobs: - job: "test_${{ parameters.platformDisplayName }}_${{ parameters.testDisplayName }}" displayName: "${{ parameters.testDisplayName }}_${{ parameters.platformDisplayName }}" + variables: + - name: testResultsPath + value: "$(REPO_ROOT)/test_results/${{ parameters.platformDisplayName }}_${{ parameters.testDisplayName }}" + pool: name: ${{ parameters.poolName }} demands: @@ -107,3 +115,13 @@ jobs: -p:BuildNumber='$(Build.BuildNumber)' -p:BuildSuffix='${{ parameters.buildSuffix }}' -p:TestFramework=${{ parameters.platformDotnet }} + -p:TestResultsFolderPath=${{ variables.testResultsPath }} + + # Publish test results + - template: /eng/pipelines/pr/steps/publish-test-results-step.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + platformDisplayName: ${{ parameters.platformDisplayName }} + testDisplayName: ${{ parameters.testDisplayName }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} + testResultsPath: ${{ variables.testResultsPath }} diff --git a/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml b/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml index 9ca2c7c555..e1a5a4abd4 100644 --- a/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml +++ b/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml @@ -33,6 +33,10 @@ parameters: - name: stageNameSecrets type: string + # Name of the artifact to upload the test results (including code coverage) to. + - name: testResultsArtifactBaseName + type: string + # Platform Parameters ==================================================== # Display name of the platform. This will be formatted into the job's official name as well as @@ -128,6 +132,12 @@ jobs: - name: saPassword value: $[stageDependencies.${{ parameters.stageNameSecrets }}.secrets_job.outputs['SaPassword.Value']] + - name: testDisplayName + value: "sqlclient_manual_${{ parameters.configDisplayName}}_${{ parameters.testSet }}" + + - name: testResultsPath + value: "$(REPO_ROOT)/test_results/${{ parameters.platformDisplayName }}_$(testDisplayName)" + steps: # Install dotnet and the runtime that will run the tests (if it is not netframework) - template: /eng/pipelines/common/steps/install-dotnet.yml@self @@ -201,4 +211,13 @@ jobs: -p:BuildSuffix='${{ parameters.buildSuffix }}' -p:TestFramework=${{ parameters.platformDotnet }} -p:TestSet=${{ parameters.testSet }} + -p:TestResultsFolderPath=${{ variables.testResultsPath }} + # Publish test results + - template: /eng/pipelines/pr/steps/publish-test-results-step.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + platformDisplayName: ${{ parameters.platformDisplayName }} + testDisplayName: ${{ variables.testDisplayName }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} + testResultsPath: ${{ variables.testResultsPath }} diff --git a/eng/pipelines/pr/pr-pipeline.yml b/eng/pipelines/pr/pr-pipeline.yml deleted file mode 100644 index daf393e502..0000000000 --- a/eng/pipelines/pr/pr-pipeline.yml +++ /dev/null @@ -1,94 +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. # -################################################################################# - -name: $(DayOfYear)$(Rev:rr) - -parameters: - # General Parameters ===================================================== - - # Dotnet CLI verbosity level. - - name: dotnetVerbosity - displayName: dotnet CLI Verbosity - type: string - default: normal - values: - - quiet - - minimal - - normal - - detailed - - diagnostic - -variables: - - template: /eng/pipelines/common/variables/common-variables.yml@self - - template: /eng/pipelines/pr/variables/pr-variables.yml@self - -stages: - # Stage 1a: Build and pack all projects in the repository - - template: /eng/pipelines/pr/stages/pack-stage.yml - parameters: - buildConfiguration: Debug - buildSuffix: pr - stageName: ${{ variables.stageNamePack }} - - # Stage 1b: Generate secrets - - template: /eng/pipelines/pr/stages/generate-secrets-stage.yml - parameters: - stageName: ${{ variables.stageNameSecrets }} - - # Stage 2: Execute tests and collect code coverage - - template: /eng/pipelines/pr/stages/test-stages.yml - parameters: - buildConfiguration: Debug - buildSuffix: pr - dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - poolName: $(PoolNameDefault) - stageNamePack: ${{ variables.stageNamePack }} - stageNameSecrets: ${{ variables.stageNameSecrets }} - - platforms: - - displayName: "windows_net462" - dotnet: "net462" - image: "ADO-MMS22-SQL22" - operatingSystem: "Windows" - - displayName: "windows_net8" - dotnet: "net8.0" - image: "ADO-MMS22-SQL22" - operatingSystem: "Windows" - - displayName: "windows_net9" - dotnet: "net9.0" - image: "ADO-MMS22-SQL22" - operatingSystem: "Windows" - - displayName: "windows_net10" - dotnet: "net10.0" - image: "ADO-MMS22-SQL22" - operatingSystem: "Windows" - - - displayName: "linux_net8" - dotnet: "net8.0" - image: "ADO-UB22-SQL22" - operatingSystem: "Linux" - - displayName: "linux_net9" - dotnet: "net9.0" - image: "ADO-UB22-SQL22" - operatingSystem: "Linux" - - displayName: "linux_net10" - dotnet: "net10.0" - image: "ADO-UB22-SQL22" - operatingSystem: "Linux" - - manualTestAzureKeyVaultUrl: $(AzureKeyVaultUrl) - manualTestAzureKeyVaultTenantId: $(AzureKeyVaultTenantId) - manualTestConnectionStringNpLocalhost: $(ConnectionStringNp_LocalhostDefault_UsernamePassword) - manualTestConnectionStringTcpLocalhost: $(ConnectionStringTcp_LocalhostDefault_UsernamePassword) - manualTestConnectionStringNpAzure: $(ConnectionStringNp_Azure_ManagedIdentity) - manualTestConnectionStringTcpAzure: $(ConnectionStringTcp_Azure_ManagedIdentity) - manualTestFileStreamDirectory: "$(Pipeline.Workspace)/filestream" - manualTestLocalDbAppName: $(LocalDbAppName) - manualTestLocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - manualTestUserManagedIdentityClientId: $(UserManagedIdentityClientId) - - # Stage 3: Collect code coverage - # @TODO: diff --git a/eng/pipelines/pr/sqlclient-pr-pipeline.yml b/eng/pipelines/pr/sqlclient-pr-pipeline.yml new file mode 100644 index 0000000000..8df34ac600 --- /dev/null +++ b/eng/pipelines/pr/sqlclient-pr-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. # +################################################################################# + +# This pipeline builds and tests all products that we release. It is intended to run as validation +# for every PR. The set of tests it executes is not exhaustive, as many tests require special +# environments to provide complete coverage. Instead, this PR strikes a balance between complete +# confidence and speed of PR validation. +# +# It is triggered by pushes to PRs that target the main, dev/*, feat/*, and release/* branches in +# GitHub. The dev/* pattern includes dev/automation/* branches created by AI agents. +# +# It maps to the "sqlclient-pr" pipeline in the Public ADO project: +# https://sqlclientdrivers.visualstudio.com/public/_build?definitionId=2281 +# And the "sqlclient-pr" pipeline in the internal ADO project: +# https://dev.azure.com/SqlClientDrivers/ADO.Net/_build?definitionId=2271 + +# Set the pipeline run name to the day-of-year and the daily run counter. +name: $(DayOfYear)$(Rev:rr) + +# Trigger PR validation runs for all pushes to PRs that target the specified branches. +# https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#pr-triggers +pr: + branches: + include: + # GitHub repo branch targets that will trigger PR validation builds. + - dev/* + - feat/* + - main + - release/* + + paths: + include: + - .azuredevops/* + - .config/* + - doc/* + - eng/pipelines/common/* + - eng/pipelines/pr/* + - src/* + - tools/* + - azurepipelines-coverage.yml + - build.proj + - Directory.Packages.props + - dotnet-tools.json + - global.json + - NuGet.config + +# Do not trigger commit or schedule runs for this pipeline. +trigger: none + +parameters: + # General Parameters ===================================================== + + # Dotnet CLI verbosity level. + - name: dotnetVerbosity + displayName: dotnet CLI Verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + + - name: platforms + type: object + default: + - displayName: "windows_net462" + dotnet: "net462" + image: "ADO-MMS22-SQL22" + operatingSystem: "Windows" + - displayName: "windows_net8" + dotnet: "net8.0" + image: "ADO-MMS22-SQL22" + operatingSystem: "Windows" + - displayName: "windows_net9" + dotnet: "net9.0" + image: "ADO-MMS22-SQL22" + operatingSystem: "Windows" + - displayName: "windows_net10" + dotnet: "net10.0" + image: "ADO-MMS22-SQL22" + operatingSystem: "Windows" + + - displayName: "linux_net8" + dotnet: "net8.0" + image: "ADO-UB22-SQL22" + operatingSystem: "Linux" + - displayName: "linux_net9" + dotnet: "net9.0" + image: "ADO-UB22-SQL22" + operatingSystem: "Linux" + - displayName: "linux_net10" + dotnet: "net10.0" + image: "ADO-UB22-SQL22" + operatingSystem: "Linux" + +variables: + - template: /eng/pipelines/common/variables/common-variables.yml@self + - template: /eng/pipelines/pr/variables/pr-variables.yml@self + +stages: + # Stage 1a: Build and pack all projects in the repository + - template: /eng/pipelines/pr/stages/pack-stage.yml@self + parameters: + buildConfiguration: Debug + buildSuffix: pr + stageName: ${{ variables.stageNamePack }} + + # Stage 1b: Generate secrets + - template: /eng/pipelines/pr/stages/generate-secrets-stage.yml@self + parameters: + stageName: ${{ variables.stageNameSecrets }} + + # Stage 2: Execute tests and collect code coverage + - template: /eng/pipelines/pr/stages/test-stages.yml@self + parameters: + buildConfiguration: Debug + buildSuffix: pr + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + poolName: $(PoolNameDefault) + stageNamePack: ${{ variables.stageNamePack }} + stageNameSecrets: ${{ variables.stageNameSecrets }} + testResultsArtifactBaseName: ${{ variables.testResultsArtifactBaseName }} + + platforms: ${{ parameters.platforms }} + + manualTestAzureKeyVaultUrl: $(AzureKeyVaultUrl) + manualTestAzureKeyVaultTenantId: $(AzureKeyVaultTenantId) + manualTestConnectionStringNpLocalhost: $(ConnectionStringNp_LocalhostDefault_UsernamePassword) + manualTestConnectionStringTcpLocalhost: $(ConnectionStringTcp_LocalhostDefault_UsernamePassword) + manualTestConnectionStringNpAzure: $(ConnectionStringNp_Azure_ManagedIdentity) + manualTestConnectionStringTcpAzure: $(ConnectionStringTcp_Azure_ManagedIdentity) + manualTestFileStreamDirectory: "$(Pipeline.Workspace)/filestream" + manualTestLocalDbAppName: $(LocalDbAppName) + manualTestLocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + manualTestUserManagedIdentityClientId: $(UserManagedIdentityClientId) + + # Stage 3: Collect code coverage + - template: /eng/pipelines/pr/stages/collect-coverage-stage.yml@self + parameters: + dependsOn: + - ${{ each platform in parameters.platforms }}: + - "test_${{ platform.displayName }}" diff --git a/eng/pipelines/pr/stages/collect-coverage-stage.yml b/eng/pipelines/pr/stages/collect-coverage-stage.yml new file mode 100644 index 0000000000..2d03bb064f --- /dev/null +++ b/eng/pipelines/pr/stages/collect-coverage-stage.yml @@ -0,0 +1,75 @@ +################################################################################# +# 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: + + # Names of the test stages that must complete before collecting coverage. + - name: dependsOn + type: object + default: [] + +stages: + - stage: collect_code_coverage + displayName: "Collect code coverage" + dependsOn: ${{ parameters.dependsOn }} + + jobs: + - job: collect_code_coverage + displayName: "Collect Code Coverage" + + pool: + vmImage: ubuntu-latest + + variables: + + # Set up a temporary directory that is cleaned up after each job run. This helps avoid + # disk space issues on pooled agents that may run many jobs before being retired. + - name: workingDir + value: "$(Agent.TempDirectory)/coverage" + + steps: + + # Part 1) Merge coverage reports ================================= + + # Install .NET SDK + - template: /eng/pipelines/common/steps/install-dotnet.yml@self + + # Restore additional dotnet tools + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self + + # Download coverage reports from the test jobs. + # NOTE: The artifact name is not specified, so *all* artifacts of the build will be + # checked for the coverage reports. + - task: DownloadPipelineArtifact@2 + displayName: Download coverage reports + inputs: + itemPattern: '**/*.coverage' + targetPath: "${{ variables.workingDir }}/originals" + + # Merge original files into a single Cobertura XML file + - script: >- + dotnet tool run dotnet-coverage -- merge "${{ variables.workingDir }}/originals/**/*.coverage" + --output "${{ variables.workingDir }}/merge/cobertura.xml" + --output-format cobertura + --log-file "${{ variables.workingDir }}/merge/merge.log" + --log-level Verbose + displayName: Merge coverage files + workingDirectory: $(REPO_ROOT) + + # Part 2) Publish merged results to the pipeline results/artifacts + + # Publish the merged coverage file as a pipeline artifact + - task: PublishPipelineArtifact@1 + displayName: Publish coverage artifact + inputs: + artifact: merged_coverage + targetPath: "${{ variables.workingDir }}/merge" + + # Publish the merged coverage file as coverage results so they can be viewed in ADO UI. + - task: PublishCodeCoverageResults@2 + displayName: Publish coverage results + inputs: + summaryFileLocation: "${{ variables.workingDir }}/merge/cobertura.xml" diff --git a/eng/pipelines/pr/stages/test-stages.yml b/eng/pipelines/pr/stages/test-stages.yml index 3a1e2c2011..f6023678b7 100644 --- a/eng/pipelines/pr/stages/test-stages.yml +++ b/eng/pipelines/pr/stages/test-stages.yml @@ -45,6 +45,12 @@ parameters: - name: stageNameSecrets type: string + # Name of the artifact to upload the test results (including code coverage) to. + # Note: This artifact will be used to store results from all jobs. Ensure that the folders used + # to store the results is unique to the test run. + - name: testResultsArtifactBaseName + type: string + # Manual Test Configuration Parameters ================================== # Azure Key Vault tenant ID that will be set in the config.jsonc file for sqlclient manual tests. @@ -129,6 +135,7 @@ stages: platformDotnet: ${{ platform.dotnet }} platformImage: ${{ platform.image }} poolName: ${{ parameters.poolName }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} packageShortName: "Abstractions" testDisplayName: "abstractions" @@ -143,6 +150,7 @@ stages: platformDotnet: ${{ platform.dotnet }} platformImage: ${{ platform.image }} poolName: ${{ parameters.poolName }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} packageShortName: "Azure" testDisplayName: "azure" @@ -157,6 +165,7 @@ stages: platformDotnet: ${{ platform.dotnet }} platformImage: ${{ platform.image }} poolName: ${{ parameters.poolName }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} packageShortName: "SqlClient" testDisplayName: "sqlclient_functional" @@ -169,6 +178,7 @@ stages: buildSuffix: ${{ parameters.buildSuffix }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} stageNameSecrets: ${{ parameters.stageNameSecrets }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} platformDisplayName: ${{ platform.displayName }} platformDotnet: ${{ platform.dotnet }} @@ -195,6 +205,7 @@ stages: buildSuffix: ${{ parameters.buildSuffix }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} stageNameSecrets: ${{ parameters.stageNameSecrets }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} platformDisplayName: ${{ platform.displayName }} platformDotnet: ${{ platform.dotnet }} @@ -221,6 +232,7 @@ stages: buildSuffix: ${{ parameters.buildSuffix }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} stageNameSecrets: ${{ parameters.stageNameSecrets }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} platformDisplayName: ${{ platform.displayName }} platformDotnet: ${{ platform.dotnet }} @@ -250,6 +262,7 @@ stages: platformDotnet: ${{ platform.dotnet }} platformImage: ${{ platform.image }} poolName: ${{ parameters.poolName }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} packageShortName: "SqlClient" testDisplayName: "sqlclient_unit" diff --git a/eng/pipelines/pr/steps/publish-test-results-step.yml b/eng/pipelines/pr/steps/publish-test-results-step.yml new file mode 100644 index 0000000000..c64c3a974f --- /dev/null +++ b/eng/pipelines/pr/steps/publish-test-results-step.yml @@ -0,0 +1,63 @@ +################################################################################# +# 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. # +################################################################################# + +# Publishes the test result files from the job to the pipeline and uploads the test results +# (including code coverage results) to a pipeline artifact. + +parameters: + + # Configuration to use to build the project. + - name: buildConfiguration + type: string + values: + - Debug + - Release + + # Display name of the platform. This will be formatted into the test result name. As such, only + # alphanumeric and _ characters are permitted. + - name: platformDisplayName + type: string + + # Display name of the test target that is being executed. This will be formatted into the test + # result name. As such, only alphanumeric and _ characters are permitted. + - name: testDisplayName + type: string + + # Name of the artifact to upload the test results (including code coverage) to. + - name: testResultsArtifactBaseName + type: string + + # Absolute path to the test results folder. This folder will be published to the artifact defined + # by ${{ testResultsArtifactBaseName }}_$(System.JobId) + - name: testResultsPath + type: string + +steps: + - pwsh: New-Item -ItemType Directory -Force -Path "${{ parameters.testResultsPath }}" + displayName: Ensure test results directory exists + condition: succeededOrFailed() + + # Publish the test results (trx files) to the pipeline + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + inputs: + buildConfiguration: ${{ parameters.buildConfiguration }} + mergeTestResults: true + testResultsFiles: | + ${{ parameters.testResultsPath }}/*.trx + ${{ parameters.testResultsPath }}/**/*.coverage + testResultsFormat: VSTest + testRunTitle: "${{ parameters.platformDisplayName }}_${{ parameters.testDisplayName }}" + condition: succeededOrFailed() + + # Publish the test results as artifacts for the pipeline + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifacts' + inputs: + artifact: ${{ parameters.testResultsArtifactBaseName }}_$(System.JobId) + targetPath: ${{ parameters.testResultsPath }} + condition: succeededOrFailed() + diff --git a/eng/pipelines/pr/variables/pr-variables.yml b/eng/pipelines/pr/variables/pr-variables.yml index a9da73850e..cea536b0ba 100644 --- a/eng/pipelines/pr/variables/pr-variables.yml +++ b/eng/pipelines/pr/variables/pr-variables.yml @@ -24,8 +24,17 @@ variables: - group: sqlclient-testconfig-v1 # General Variables ====================================================== + + # Name to use for the packaging stage. This is to ensure that the packaging stage and the test + # stages (that will depend on the packaging stage passing) use the same name. - name: stageNamePack value: "pack_stage" + # Name to use for the secret generation stage. This is to ensure that the secret generation stage + # and the test stages (that will depend on the generated secret) use the same name. - name: stageNameSecrets value: "secrets_stage" + + # Base name of the test result artifacts. Job ID should be appended to this to prevent collision. + - name: testResultsArtifactBaseName + value: "test_results" From e1a7a7fd9db8723c2aa377a91a3b1ece1834a3f2 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Tue, 23 Jun 2026 06:24:00 +0100 Subject: [PATCH 156/298] Fix | Reenable SqlBulkCopy in least-privilege environments (#4306) --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 55 +++-- .../DatabaseObjects/DatabaseObject.cs | 25 ++- .../Fixtures/DatabaseObjects/DatabaseUser.cs | 67 ++++++ .../Fixtures/DatabaseObjects/ServerLogin.cs | 99 +++++++++ .../ManualTests/BulkCopy/CopyAllFromReader.cs | 8 +- .../ManualTests/BulkCopy/UnprivilegedLogin.cs | 208 ++++++++++++++++++ .../ManualTests/DataCommon/DataTestUtility.cs | 42 ++++ .../Extensions/CodeAnalysis.netfx.cs | 25 +++ .../TracingTests/XEventsTracingTest.cs | 2 +- 9 files changed, 503 insertions(+), 28 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseUser.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ServerLogin.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/Extensions/CodeAnalysis.netfx.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index af2091d7f6..af6b2524c2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -477,11 +477,12 @@ private string CreateInitialQuery() } else if (!string.IsNullOrEmpty(CatalogName)) { - CatalogName = SqlServerEscapeHelper.EscapeStringAsLiteral(SqlServerEscapeHelper.EscapeIdentifier(CatalogName)); + CatalogName = SqlServerEscapeHelper.EscapeIdentifier(CatalogName); } string objectName = ADP.BuildMultiPartName(parts); string escapedObjectName = SqlServerEscapeHelper.EscapeStringAsLiteral(objectName); + string catalogNameStringLiteral = CatalogName is null ? null : SqlServerEscapeHelper.EscapeStringAsLiteral(CatalogName); // Specify the column names explicitly. This is to ensure that we can map to hidden // columns (e.g. columns in temporal tables.) If the target table doesn't exist, // OBJECT_ID will return NULL and @Column_Names will remain non-null. The subsequent @@ -526,6 +527,11 @@ private string CreateInitialQuery() // we use STRING_AGG in that case and the COALESCE method otherwise. // // See: https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql + // + // All of this is wrapped in an test against HAS_PERMS_BY_NAME. This test verifies that + // the user possesses the necessary permissions to access sys.all_columns. If they do not + // @Column_Names will remain NULL (and be coalesced to *) and SqlBulkCopy will degrade + // gracefully, silently dropping support for hidden columns and column aliases. return $""" SELECT @@TRANCOUNT; @@ -535,6 +541,7 @@ private string CreateInitialQuery() DECLARE @Column_Name_Query_SORT NVARCHAR(MAX); DECLARE @Column_Name_Query NVARCHAR(MAX); DECLARE @Column_Names NVARCHAR(MAX) = NULL; +DECLARE @Has_Sys_All_Columns_Permissions INT = HAS_PERMS_BY_NAME('{catalogNameStringLiteral}.[sys].[all_columns]', 'OBJECT', 'SELECT'); CREATE TABLE #Column_Aliases ( @@ -554,28 +561,35 @@ IF CAST(SERVERPROPERTY('EngineEdition') AS INT) = 6 SET @Column_Name_Query_SORT = N'ORDER BY [column_id] ASC'; END -IF EXISTS (SELECT TOP 1 * FROM sys.all_columns WHERE [object_id] = OBJECT_ID('sys.all_columns') AND [name] = 'graph_type') -BEGIN - SET @Column_Name_Query_FILTER = N'WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) NOT IN (1, 3, 4, 6, 7)'; - - EXEC sp_executesql N' - INSERT INTO #Column_Aliases ([Canonical_Column_Name], [Canonical_Column_Id], [Aliased_Column_Name]) - SELECT [name], [column_id], ''$to_id'' FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 8 - UNION ALL - SELECT [name], [column_id], ''$from_id'' FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 5 - UNION ALL - SELECT [name], [column_id], ''$edge_id'' FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 2 AND [name] LIKE ''$edge[_]id[_]%'' - UNION ALL - SELECT [name], [column_id], ''$node_id'' FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 2 AND [name] LIKE ''$node[_]id[_]%''', - N'@Object_ID INT', @Object_ID = @Object_ID -END -ELSE +IF @Has_Sys_All_Columns_Permissions = 1 BEGIN - SET @Column_Name_Query_FILTER = N'WHERE [object_id] = @Object_ID'; + IF EXISTS (SELECT TOP 1 * FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = OBJECT_ID('{catalogNameStringLiteral}.[sys].[all_columns]') AND [name] = 'graph_type') + BEGIN + SET @Column_Name_Query_FILTER = N'WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) NOT IN (1, 3, 4, 6, 7)'; + + EXEC sp_executesql N' + INSERT INTO #Column_Aliases ([Canonical_Column_Name], [Canonical_Column_Id], [Aliased_Column_Name]) + SELECT [name], [column_id], ''$to_id'' FROM {catalogNameStringLiteral}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 8 + UNION ALL + SELECT [name], [column_id], ''$from_id'' FROM {catalogNameStringLiteral}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 5 + UNION ALL + SELECT [name], [column_id], ''$edge_id'' FROM {catalogNameStringLiteral}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 2 AND [name] LIKE ''$edge[_]id[_]%'' + UNION ALL + SELECT [name], [column_id], ''$node_id'' FROM {catalogNameStringLiteral}.[sys].[all_columns] WHERE [object_id] = @Object_ID AND COALESCE([graph_type], 0) = 2 AND [name] LIKE ''$node[_]id[_]%''', + N'@Object_ID INT', @Object_ID = @Object_ID + END + ELSE + BEGIN + SET @Column_Name_Query_FILTER = N'WHERE [object_id] = @Object_ID'; + END + SET @Column_Name_Query = @Column_Name_Query_SELECT + ' FROM {catalogNameStringLiteral}.[sys].[all_columns] ' + @Column_Name_Query_FILTER + ' ' + @Column_Name_Query_SORT + ';' + + EXEC sp_executesql @Column_Name_Query, N'@Object_ID INT, @Column_Names NVARCHAR(MAX) OUTPUT', @Object_ID = @Object_ID, @Column_Names = @Column_Names OUTPUT; + + DELETE FROM #Column_Aliases + WHERE [Aliased_Column_Name] IN (SELECT [name] FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID) END -SET @Column_Name_Query = @Column_Name_Query_SELECT + ' FROM {CatalogName}.[sys].[all_columns] ' + @Column_Name_Query_FILTER + ' ' + @Column_Name_Query_SORT + ';' -EXEC sp_executesql @Column_Name_Query, N'@Object_ID INT, @Column_Names NVARCHAR(MAX) OUTPUT', @Object_ID = @Object_ID, @Column_Names = @Column_Names OUTPUT; SELECT @Column_Names = COALESCE(@Column_Names, '*'); SET FMTONLY ON; @@ -586,7 +600,6 @@ UNION ALL SELECT [Canonical_Column_Name], [Aliased_Column_Name] FROM #Column_Aliases -WHERE [Aliased_Column_Name] NOT IN (SELECT [name] FROM {CatalogName}.[sys].[all_columns] WHERE [object_id] = @Object_ID) ORDER BY [Canonical_Column_Id] ASC DROP TABLE #Column_Aliases diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs index 78fa16080f..a74da79697 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs @@ -10,19 +10,28 @@ namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; /// Base class for a transient database object (such as a table, type or /// stored procedure.) /// -public abstract class DatabaseObject : IDisposable +/// +/// The type of the internal state accessible to derived types at the point of object creation +/// via the property. +/// +public abstract class DatabaseObject : IDisposable { private readonly bool _shouldDrop; protected SqlConnection Connection { get; } + protected TState State { get; } + public string Name { get; } - protected DatabaseObject(SqlConnection connection, string name, string definition, bool shouldCreate, bool shouldDrop) + public string UnescapedName => Name.Substring(1, Name.Length - 2).Replace("]]", "]"); + + protected DatabaseObject(SqlConnection connection, string name, string definition, TState state, bool shouldCreate, bool shouldDrop) { _shouldDrop = shouldDrop; Connection = connection; + State = state; Name = name; if (shouldCreate) @@ -259,3 +268,15 @@ public void Dispose() GC.SuppressFinalize(this); } } + +/// +/// Base class for a transient database object (such as a table, type or +/// stored procedure.) +/// +public abstract class DatabaseObject : DatabaseObject +{ + protected DatabaseObject(SqlConnection connection, string name, string definition, bool shouldCreate, bool shouldDrop) + : base(connection, name, definition, state: null, shouldCreate, shouldDrop) + { + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseUser.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseUser.cs new file mode 100644 index 0000000000..bc94bc78ef --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseUser.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. + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient database user, created at the start of its scope and dropped when disposed. +/// +/// +/// This class assumes that the associated server login already exists. +/// +public sealed class DatabaseUser : DatabaseObject +{ + public string DatabaseName => State; + + /// + /// Initializes a new instance of the DatabaseUser class using the specified SQL connection + /// and associated server login. + /// + /// The SQL connection used to interact with the database. + /// The name of the database where the user will be created. + /// The server login which the database user will be associated with. + public DatabaseUser(SqlConnection connection, string database, ServerLogin login) + : base(connection, login.Name, $"FOR LOGIN {login.Name}", state: database, shouldCreate: true, shouldDrop: true) + { + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE USER {Name} {definition}", Connection); + + ExecuteCommandInDatabase(createCommand); + } + + protected override void DropObject() + { + using SqlCommand dropCommand = new($"IF USER_ID('{UnescapedName}') IS NOT NULL DROP USER {Name}", Connection); + + ExecuteCommandInDatabase(dropCommand); + } + + private void ExecuteCommandInDatabase(SqlCommand command) + { + // In some cases, we want to manage a database user in a different database to the one that the connection + // currently has open. In such a case, we need to change the database context for the command execution + // and then restore it afterwards. + string? originalDatabase = DatabaseName == command.Connection.Database ? null : command.Connection.Database; + + try + { + if (originalDatabase is not null) + { + command.Connection.ChangeDatabase(DatabaseName); + } + + command.ExecuteNonQuery(); + } + finally + { + if (originalDatabase is not null) + { + command.Connection.ChangeDatabase(originalDatabase); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ServerLogin.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ServerLogin.cs new file mode 100644 index 0000000000..154899e9b3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ServerLogin.cs @@ -0,0 +1,99 @@ +// 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.Security.Cryptography; + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient server login, created at the start of its scope and dropped when disposed. +/// +public sealed class ServerLogin : DatabaseObject +{ + public string Password => State; + + /// + /// Initializes a new instance of the ServerLogin class using the specified SQL connection, login name prefix, and default database. + /// The login will be created with a randomly generated password that meets SQL Server's password complexity requirements. + /// + /// The SQL connection used to interact with the database. + /// The prefix for the login name. + /// The default database for the login. If null, not set. + public ServerLogin(SqlConnection connection, string namePrefix, string? defaultDatabase = null) + : this(connection, GenerateLongName(namePrefix), GeneratePassword(), defaultDatabase) + { + } + + private ServerLogin(SqlConnection connection, string namePrefix, string password, string? defaultDatabase) + : base(connection, namePrefix, GenerateDefinition(password, defaultDatabase), state: password, shouldCreate: true, shouldDrop: true) + { + } + + private static string GenerateDefinition(string password, string? defaultDatabase) => + $"WITH PASSWORD='{password}'" + + (string.IsNullOrEmpty(defaultDatabase) ? string.Empty : $", DEFAULT_DATABASE=[{defaultDatabase}]"); + + /// + /// Generates a password which meets the SQL Server password complexity requirements, which are: + /// + /// Minimum length of 8 characters + /// Must contain characters from three of the following four categories: + /// + /// Uppercase letters (A-Z) + /// Lowercase letters (a-z) + /// Digits (0-9) + /// Non-alphanumeric characters (e.g. !, $, #, %) + /// + /// + /// + /// A compliant password. + private static string GeneratePassword() + { + const int PasswordLength = 16; + const char UpperCaseStart = 'A'; + const char LowerCaseStart = 'a'; + const char DigitsStart = '0'; + + // First 5 characters are uppercase letters, next 5 are lowercase letters, and the last 6 are digits + Span passwordDigits = stackalloc char[PasswordLength]; + byte[] randomBytes = new byte[PasswordLength]; + using RandomNumberGenerator rndGen = RandomNumberGenerator.Create(); + + rndGen.GetBytes(randomBytes); + for(int i = 0; i < 5; i++) + { + passwordDigits[i] = (char)(UpperCaseStart + Scale(randomBytes[i], byte.MaxValue, 26)); + } + for (int i = 5; i < 10; i++) + { + passwordDigits[i] = (char)(LowerCaseStart + Scale(randomBytes[i], byte.MaxValue, 26)); + } + for (int i = 10; i < PasswordLength; i++) + { + passwordDigits[i] = (char)(DigitsStart + Scale(randomBytes[i], byte.MaxValue, 10)); + } + + return passwordDigits.ToString(); + + // Scales a byte value from the range [0, 255) to the range [0, newMaximum) + static int Scale(byte value, int existingMaximum, int newMaximum) + { + return (int)((double)value / (existingMaximum + 1) * newMaximum); + } + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE LOGIN {Name} {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + using SqlCommand dropCommand = new($"IF SUSER_ID('{UnescapedName}') IS NOT NULL DROP LOGIN {Name}", Connection); + + dropCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs index 23ff5ed245..17a613294b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs @@ -45,10 +45,10 @@ public void Test() using (DbDataReader reader = srcCmd.ExecuteReader()) { IDictionary stats; - long expectedIduCount = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 1 : 0; - long expectedSelectCount = DataTestUtility.IsAzureSynapse ? 4 : 12; - long expectedSelectRows = DataTestUtility.IsAzureSynapse ? 4 : 14; - long expectedTransactions = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 1 : 0; + long expectedIduCount = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 2 : 0; + long expectedSelectCount = DataTestUtility.IsAzureSynapse ? 4 : 13; + long expectedSelectRows = DataTestUtility.IsAzureSynapse ? 4 : 15; + long expectedTransactions = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 2 : 0; using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { bulkcopy.DestinationTableName = dstTable; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs new file mode 100644 index 0000000000..08915e30ab --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.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.Data; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SqlBulkCopyTests; + +#nullable enable + +/// +/// Some unprivileged users may not have permissions to query sys.all_columns, which is used to +/// handle column aliases. These tests verify that bulk copy operations can handle such situations +/// gracefully, falling back and ignoring column aliases. +/// +[Trait("Set", "2")] +public sealed class UnprivilegedLogin : IDisposable +{ + private readonly SqlConnection? _managementConnection; + private readonly ServerLogin? _unprivilegedLogin; + private readonly DatabaseUser? _unprivilegedMasterUser; + private readonly DatabaseUser? _unprivilegedAppUser; + + private readonly string? _unprivilegedConnectionString; + + public static bool CanRunTests => + DataTestUtility.AreConnStringsSetup() && DataTestUtility.IsNotAzureServer() + && DataTestUtility.CanCreateLogins && DataTestUtility.CanUseSqlAuthentication; + + public UnprivilegedLogin() + { + // xUnit will instantiate the class before evaluating the test condition - make sure that we don't + // attempt to make use of these capabilities if the server doesn't support them. + // This has the expected nullability implications, so AssertEnvironmentCreated needs to be called + // before any test logic. The compiler asserts these for us. + if (!CanRunTests) + { + return; + } + + // We require two connections: a "management" connection which has permissions to create logins, + // create users and modify permissions; and an "unprivileged" connection, which is used to perform + // the actual tests. The user associated with the latter connection will be denied SELECT permissions + // over master.sys.all_columns. + _managementConnection = new SqlConnection(DataTestUtility.TCPConnectionString); + _managementConnection.Open(); + + _unprivilegedLogin = new ServerLogin(_managementConnection, nameof(UnprivilegedLogin), _managementConnection.Database); + _unprivilegedAppUser = new DatabaseUser(_managementConnection, _managementConnection.Database, _unprivilegedLogin); + _unprivilegedMasterUser = new DatabaseUser(_managementConnection, "master", _unprivilegedLogin); + + using (SqlCommand permissionsModificationCommand = _managementConnection.CreateCommand()) + { + permissionsModificationCommand.CommandText = $"DENY SELECT ON [master].[sys].[all_columns] TO {_unprivilegedMasterUser.Name}"; + permissionsModificationCommand.ExecuteNonQuery(); + + permissionsModificationCommand.CommandText = $"DENY SELECT ON [{_managementConnection.Database}].[sys].[all_columns] TO {_unprivilegedAppUser.Name}"; + permissionsModificationCommand.ExecuteNonQuery(); + } + + SqlConnectionStringBuilder tcpConnectionBuilder = new(DataTestUtility.TCPConnectionString) + { + IntegratedSecurity = false, + UserID = _unprivilegedLogin.UnescapedName, + Password = _unprivilegedLogin.Password, + // Disable connection pooling - we'll be dropping this login once the tests complete, and this can only happen once all connections + // using it are closed. + Pooling = false + }; + + _unprivilegedConnectionString = tcpConnectionBuilder.ConnectionString; + } + + /// + /// This method enables nullability assertions to operate as expected. It'll always pass if CanRunTests is + /// true - the constructor will have initialized the relevant fields. + /// + [MemberNotNull(nameof(_managementConnection), + nameof(_unprivilegedLogin), + nameof(_unprivilegedMasterUser), + nameof(_unprivilegedAppUser), + nameof(_unprivilegedConnectionString))] + private void AssertEnvironmentCreated() + { + Assert.NotNull(_managementConnection); + Assert.NotNull(_unprivilegedLogin); + Assert.NotNull(_unprivilegedMasterUser); + Assert.NotNull(_unprivilegedAppUser); + Assert.NotNull(_unprivilegedConnectionString); + } + + /// + /// Verifies that a bulk copy operation succeeds when performed by a user with only SELECT and INSERT permissions, + /// without requiring access to metadata views. + /// + [ConditionalFact(nameof(CanRunTests))] + public void BulkCopyWithoutMetadataPermission_Succeeds() + { + AssertEnvironmentCreated(); + + const int BulkCopyRowCount = 5; + + using DataTable srcDataTable = new() + { + Columns = { new DataColumn("Description", typeof(string)) } + }; + using Table dstTable = new(_managementConnection, nameof(BulkCopyWithoutMetadataPermission_Succeeds), "([Description] VARCHAR(100))"); + using (SqlCommand permissionsConfigurationCommand = new($"GRANT SELECT, INSERT ON {dstTable.Name} TO {_unprivilegedAppUser.Name}", _managementConnection)) + { + permissionsConfigurationCommand.ExecuteNonQuery(); + } + + using SqlBulkCopy nodeCopy = new(_unprivilegedConnectionString); + + for (int i = 0; i < BulkCopyRowCount; i++) + { + srcDataTable.Rows.Add($"Description {i}"); + } + + nodeCopy.DestinationTableName = dstTable.Name; + nodeCopy.ColumnMappings.Add("Description", "Description"); + nodeCopy.WriteToServer(srcDataTable); + + int resultantRowCount; + using (SqlCommand rowCountQuery = new($"SELECT COUNT(*) FROM {dstTable.Name}", _managementConnection)) + { + resultantRowCount = (int)rowCountQuery.ExecuteScalar(); + } + + Assert.Equal(BulkCopyRowCount, resultantRowCount); + } + + [ConditionalFact(nameof(CanRunTests))] + public void BulkCopyWithoutMetadataPermission_FailsWhenUsingAliases() + { + AssertEnvironmentCreated(); + + using DataTable edges = new DataTable() + { + Columns = { new DataColumn("To_ID", typeof(string)), new DataColumn("From_ID", typeof(string)), new DataColumn("Description", typeof(string)) } + }; + + // Use the management connection to create the source and the destination tables, grant the + // unprivileged user permissions over them and insert some sample data into the source table. + using Table srcNodeTable = new(_managementConnection, nameof(BulkCopyWithoutMetadataPermission_FailsWhenUsingAliases), "([Name] VARCHAR(100)) AS NODE"); + using Table dstEdgeTable = new(_managementConnection, nameof(BulkCopyWithoutMetadataPermission_FailsWhenUsingAliases), "([Description] VARCHAR(100)) AS EDGE"); + using (SqlCommand permissionsConfigurationCommand = _managementConnection.CreateCommand()) + { + permissionsConfigurationCommand.CommandText = $"GRANT SELECT, INSERT ON {srcNodeTable.Name} TO {_unprivilegedAppUser.Name}"; + permissionsConfigurationCommand.ExecuteNonQuery(); + + permissionsConfigurationCommand.CommandText = $"GRANT SELECT, INSERT ON {dstEdgeTable.Name} TO {_unprivilegedAppUser.Name}"; + permissionsConfigurationCommand.ExecuteNonQuery(); + } + + string sampleNodeDataCommand = @$"INSERT INTO {srcNodeTable.Name} ([Name]) SELECT LEFT([name], 100) FROM sys.sysobjects"; + using (SqlCommand insertSampleNodes = new(sampleNodeDataCommand, _managementConnection)) + { + insertSampleNodes.ExecuteNonQuery(); + } + + using (SqlCommand nodeQuery = new($"SELECT $node_id FROM {srcNodeTable.Name}", _managementConnection)) + using (SqlDataReader reader = nodeQuery.ExecuteReader()) + { + bool firstRead = reader.Read(); + string toId; + string fromId; + + Assert.True(firstRead); + toId = reader.GetString(0); + + while (reader.Read()) + { + fromId = reader.GetString(0); + + edges.Rows.Add(toId, fromId, "Test Description"); + toId = fromId; + } + } + + // With all source data populated, try to use the unprivileged connection to perform a bulk copy + // using aliases in the column mappings. This should fail - the permissions error will be caught, + // and SqlBulkCopy should simply report that the destination column doesn't exist. + using SqlBulkCopy edgeCopy = new(_unprivilegedConnectionString); + + edgeCopy.DestinationTableName = dstEdgeTable.Name; + edgeCopy.ColumnMappings.Add("To_ID", "$to_id"); + edgeCopy.ColumnMappings.Add("From_ID", "$from_id"); + edgeCopy.ColumnMappings.Add("Description", "Description"); + + Action failingEdgeCopy = () => edgeCopy.WriteToServer(edges); + InvalidOperationException missingColumnException = Assert.Throws(failingEdgeCopy); + + Assert.Contains("'$to_id,$from_id'", missingColumnException.Message); + } + + public void Dispose() + { + _unprivilegedAppUser?.Dispose(); + _unprivilegedMasterUser?.Dispose(); + _unprivilegedLogin?.Dispose(); + _managementConnection?.Dispose(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 9bf1c0f519..bd7f6eb262 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -98,6 +98,10 @@ public static class DataTestUtility private static bool? s_isVectorSupported; private static bool? s_isVectorFloat16Supported; + // Login permissions + private static bool? s_isSysAdmin; + private static bool? s_isSecurityAdmin; + // Azure Synapse EngineEditionId == 6 // More could be read at https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql?view=sql-server-ver16#propertyname public static bool IsAzureSynapse @@ -245,6 +249,20 @@ private static bool CheckVectorFloat16Supported() } } + public static bool IsSysAdmin => + s_isSysAdmin ??= IsTCPConnStringSetup() && + IsServerRoleMember("sysadmin"); + + public static bool IsSecurityAdmin => + s_isSecurityAdmin ??= IsTCPConnStringSetup() && + IsServerRoleMember("securityadmin"); + + public static bool CanCreateLogins => + IsSysAdmin || IsSecurityAdmin; + + public static bool CanUseSqlAuthentication => + IsSysAdmin && GetAuthenticationMode() == 2; + static DataTestUtility() { Config c = Config.Load(); @@ -547,6 +565,30 @@ public static bool IsTypePresent(string typeName) return (int)command.ExecuteScalar() > 0; } + public static bool IsServerRoleMember(string roleName) + { + using SqlConnection connection = new(TCPConnectionString); + using SqlCommand command = new("SELECT IS_SRVROLEMEMBER(@role)", connection); + + connection.Open(); + command.Parameters.AddWithValue("@role", roleName); + + // IS_SRVROLEMEMBER returns 1 if the caller is a member of the specified server role, 0 if not, and DBNull.Value if the role is not valid. + return command.ExecuteScalar() is int result && result == 1; + } + + public static int GetAuthenticationMode() + { + using SqlConnection connection = new(TCPConnectionString); + + connection.Open(); + using SqlCommand command = new("EXEC xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\\Microsoft\\MSSQLServer\\MSSQLServer', N'LoginMode'", connection); + using SqlDataReader reader = command.ExecuteReader(); + + reader.Read(); + return reader.GetInt32(1); + } + public static bool IsAdmin { get diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Extensions/CodeAnalysis.netfx.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/Extensions/CodeAnalysis.netfx.cs new file mode 100644 index 0000000000..da40f53141 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Extensions/CodeAnalysis.netfx.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. + +#if NETFRAMEWORK + +namespace System.Diagnostics.CodeAnalysis; + +// These classes are provided to provide compile-time support for Microsoft.CodeAnalysis +// attributes. These attributes are native to netcore and available for netfx as a nuget +// package - but only for net472. As such, until net462 support is dropped, these placeholder +// classes will need to exist if we want to use them for static analysis. + +#nullable enable + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +internal sealed class MemberNotNullAttribute : Attribute +{ + public MemberNotNullAttribute(string member) => Members = [member]; + + public MemberNotNullAttribute(params string[] members) => Members = members; + + public string[] Members { get; } +} +#endif diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs index c1e4bad178..aef5c1bc7e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs @@ -120,7 +120,7 @@ public void XEventActivityIDConsistentWithTracing_RpcStarting() // Our stored procedure name is an escaped SQL Server object name. This will not match the object_name data // in the XEvent XML, which records it as an unescaped name. - string unescapedProcedureName = sp.Name.Substring(1, sp.Name.Length - 2).Replace("]]", "]"); + string unescapedProcedureName = sp.UnescapedName; VerifyXEventActivityIDConsistentWithTracing(unescapedProcedureName, System.Data.CommandType.StoredProcedure, "rpc_starting"); } From bea6bd8e3235f0259e72c69cd4de9fd189a8cf02 Mon Sep 17 00:00:00 2001 From: priyankatiwari08 Date: Tue, 23 Jun 2026 21:46:36 +0530 Subject: [PATCH 157/298] Add configurable idle connection timeout (ADO #39970) (#4295) * Add configurable idle connection timeout (ADO #39970) Implements spec User Stories 1, 2, 4 + FR-009 of US3 from specs/003-pool-idle-timeout/spec.md. Adds 'Connection Idle Timeout' keyword (synonym: 'Pool Idle Timeout') exposed via SqlConnectionStringBuilder.IdleTimeout. When > 0, connections that have sat idle in the pool longer than the configured number of seconds are discarded on retrieval and a fresh connection is returned. Default 0 (disabled) matches the existing convention used by LoadBalanceTimeout and ConnectionLifetime. Covers both pool designs (ChannelDbConnectionPool, WaitHandleDbConnectionPool). Deferred to follow-up: proactive timer sweep (FR-008, FR-010) which the spec assumes is built on top of the pruning feature (#37338). * Address Copilot review feedback on #4295 - Fix stale comment in ChannelDbConnectionPool.IsLiveConnection: IdleSinceUtc is initialized to CreateTime, not DateTime.MinValue. - Add WaitHandleDbConnectionPoolIdleTimeoutTest mirroring the existing channel-pool idle-timeout coverage (stamp on return, zero disables expiry, expired connection is replaced, fresh connection is reused). * Address PR #4295 inline review feedback - Skip MarkPooledIdle on return when IdleTimeout == TimeSpan.Zero so the default config has no per-return DateTime.UtcNow on the hot path. Applies to ChannelDbConnectionPool.ReturnInternalConnection and WaitHandleDbConnectionPool.PutNewObject. - Stamp IdleSinceUtc when returning a connection into the transacted pool (WaitHandleDbConnectionPool.DeactivateObject before TransactedConnectionPool.PutTransactedObject) so idle expiry on the next retrieval measures time spent parked in the transacted pool, not time since create-time / last general-pool return. - Add 2 WaitHandle pool tests covering the new behavior: IdleTimeout_TransactedPool_StampsOnReturn and IdleTimeout_Zero_DoesNotStampOnReturn. * Address PR review: default IdleTimeout=300, remove synonym, drop transacted idle handling - Default Connection Idle Timeout 0 -> 300 (5 min, matches Npgsql); 0 disables. - Remove 'Pool Idle Timeout' synonym; the canonical keyword is the only accepted form. - Make idleTimeout a required parameter on DbConnectionPoolGroupOptions; defaults now live in DbConnectionStringDefaults. - Use TimeSpan.FromSeconds for the ctor body conversion. - WaitHandle pool: drop MarkPooledIdle() on transacted-pool return and remove the idle-expiry check on transacted-pool retrieval (transacting connections must never be proactively closed). - WaitHandle pool: reorder general-pool retrieval to check idle expiry before the liveness probe; derive _cleanupWait from IdleTimeout when set. - Channel pool: reorder IsLiveConnection so the idle check runs before IsConnectionAlive(). - Tests, doc snippet, and release notes updated accordingly. * Address PR #4295 review feedback on idle timeout behavior * Remove Pool Idle Timeout synonym; keep only canonical Connection Idle Timeout keyword * Address review feedback for idle connection timeout - Colocate idleTimeout < 0 validation with the _idleTimeout assignment in DbConnectionPoolOptions - Clean up WaitHandle cleanup-wait conditional and halve the interval (two pruning cycles per eviction) - Vague-up IdleTimeout doc/resx wording per reviewer (drop 'on the next retrieval attempt' / lifecycle commitments) - Toggle UseLegacyIdleTimeoutBehavior in WaitHandle idle-timeout tests and replace reflection-based BackdateIdleSince with direct internal setter * Address Paul's review feedback: rename and doc cleanup - SqlConnectionStringBuilder.xml IdleTimeout doc: simplify , move default into , "close" -> "discard", "recycled" -> "discarded". - Rename DbConnectionInternal.IdleSinceUtc -> ReturnedTime (drop Utc suffix per Paul; align with sibling CreateTime which is also UTC but unsuffixed). - Rename DbConnectionInternal.MarkPooledIdle() -> ReturnedToPool() and reword its XML doc to record a fact ("this connection was returned to the pool") rather than assert pool policy ("idle"), per Paul's policy/mechanism separation. - Update call sites in ChannelDbConnectionPool and WaitHandleDbConnectionPool to the new names; update comments to match. - Rename test helper BackdateIdleSince -> BackdateReturnedTime in both pool test files for consistency. * Apply review feedback: drop guard, overflow-safe expiry, Yoda swap, 3-state ctor - ChannelDbConnectionPool.ReturnInternalConnection / WaitHandleDbConnectionPool.PutNewObject: drop the (!UseLegacyIdleTimeoutBehavior && IdleTimeout != Zero) guard around ReturnedToPool(); always stamp. The read path short-circuits when expiry is disabled, so the stamp is harmless and the unconditional call removes the duplicated guard and its "must always be checked together" maintenance trap. - ChannelDbConnectionPool.IsLiveConnection / WaitHandleDbConnectionPool.IsIdleExpired: rewrite expiry check from "UtcNow > ReturnedTime + idleTimeout" to "UtcNow - ReturnedTime > idleTimeout" so the comparison cannot throw when ReturnedTime is close to DateTime.MaxValue; negative TimeSpan on clock skew falls through as not-expired (fail safe). - DbConnectionPoolOptions: swap Yoda-form "if (0 != idleTimeout)" to natural "if (idleTimeout != 0)". Pre-existing loadBalanceTimeout line left untouched. - WaitHandleDbConnectionPool ctor: split the cleanup-wait initialization into three explicit branches (Legacy / New+enabled / New+disabled) so the state model is documented in the code structure. Same observable behavior as before. - Tests: remove IdleTimeout_Zero_DoesNotStampOnReturn which pinned the now- removed optimization; the user-visible "IdleTimeout=0 -> never expires" invariant is still covered by IdleTimeout_Zero_DoesNotExpire. * Re-add legacy-switch guard around ReturnedToPool stamp Skip the per-return DateTime.UtcNow when UseLegacyIdleTimeoutBehavior is on or IdleTimeout == 0, matching the short-circuit in IsLiveConnection / IsIdleExpired. Addresses Copilot review feedback. * Fix merge conflict damage in LocalAppContextSwitchesHelper The 'accept both' merge resolution dropped a SetSwitchValue( token in Dispose() and a closing brace plus /// between UseLegacyIdleTimeoutBehavior and UseOverallConnectTimeoutForPoolWait, causing CS1002/CS1513 compile errors. * Fix merge conflict damage in LocalAppContextSwitches The 'accept both' merge dropped the opening /// line on three UseOverallConnectTimeoutForPoolWait XML doc comments (const string, cached SwitchValue field, and public property), leaving them malformed. * Pass idleTimeout to DbConnectionPoolGroupOptions in budget test WaitHandleDbConnectionPoolBudgetTest was added in main while this branch was in flight; updating its DbConnectionPoolGroupOptions construction to pass idleTimeout: 0 fixes the CS7036 net462 build error. * Add TimeoutTimer arg to TryGetConnection / idleTimeout to DbConnectionPoolGroupOptions in new idle-timeout tests Main added a TimeoutTimer parameter to ChannelDbConnectionPool.TryGetConnection and WaitHandleDbConnectionPool.TryGetConnection while this branch was in flight. The new idle-timeout tests still used the old 3-arg form, causing CS7036 on net9.0. Also missed one DbConnectionPoolGroupOptions ctor call in ConcurrentCallers test. * Address PR #4295 Copilot review: document UseLegacyIdleTimeoutBehavior switch and tighten idle-timeout test coverage - Delete redundant IdleTimeout_DefaultIsZero_DisablesExpiry from ChannelDbConnectionPoolTest. The 'default' framing was misleading (the builder-layer default is 300, not 0) and the zero-as-argument path is already covered by IdleTimeout_PoolGroupOptions_ConvertsSecondsToTimeSpan and IdleTimeout_Zero_DoesNotExpire. - Add IdleTimeout_LegacySwitch_SuppressesEviction to both ChannelDbConnectionPoolTest and WaitHandleDbConnectionPoolIdleTimeoutTest. Flips UseLegacyIdleTimeoutBehavior=true via LocalAppContextSwitchesHelper, back-dates ReturnedTime well past the 1-second timeout, and asserts the connection is still reused. Closes the coverage gap on switch gating in both pool implementations. - Document the gating in the public surfaces: extend SqlConnectionStringBuilder.xml with a paragraph describing the Switch.Microsoft.Data.SqlClient.UseLegacyIdleTimeoutBehavior AppContext switch (defaults to true), and the three opt-out mechanisms (AppContext.SetSwitch, , runtimeconfig.json). Mirror the gating note in DbConnectionString_IdleTimeout in Strings.resx and the auto-generated Strings.Designer.cs accessor so the [ResDescription] surfaced in property grids matches. * Address PR 4295 review feedback: cleanup cadence, idle-eviction gate, and SetReturnedTime rename - WaitHandleDbConnectionPool ctor: collapse the three _cleanupWait branches into two (legacy-switch OR IdleTimeout==Zero -> historical 2-4 min window; else use configured timeout/2) per Paul's suggestion, and floor the new-behavior period at 1000 ms so small IdleTimeout values don't schedule a sub-second timer. - WaitHandleDbConnectionPool.CleanupCallback: gate both the destroy and the new->old age sweeps on a single `idleEvictionDisabled'' check (`!UseLegacyIdleTimeoutBehavior && IdleTimeout == Zero'') so the `OFF + 0 = disabled entirely'' case is honored. The MinPoolSize-floor re-queue below the gates still runs. - Rename DbConnectionInternal.ReturnedToPool -> SetReturnedTime and update both call sites (WaitHandle, Channel) and the related comment. The name now describes the mechanism (the connection stamps its returned time) and reads cleanly when the pool decides to skip the call because nothing will read the stamp. - Add WaitHandleDbConnectionPoolIdleTimeoutTest IdleTimeout_LegacyOff_Zero_CleanupCallbackDoesNotEvict covering the new CleanupCallback gate. Reaches the private method via reflection. * Fix ChannelDbConnectionPoolPruningTest: pass idleTimeout: 0 to DbConnectionPoolGroupOptions ctor (required after PR 4295) * Address PR 4295 review: gate WaitHandleDbConnectionPool destroy loop on IsIdleExpired With _cleanupWait = IdleTimeout/2, the legacy two-stack generational sweep in CleanupCallback could destroy a connection from _stackOld after as little as ~IdleTimeout/2 of real idle time, violating the new contract that connections are only destroyed once their idle age exceeds IdleTimeout. Add a per-connection IsIdleExpired check inside the destroy loop: when the new behaviour is enabled and the popped connection has not yet exceeded IdleTimeout, push it back onto _stackOld, release the semaphore count, and break out of the tick. The next cleanup tick reconsiders. Legacy behaviour (switch on or IdleTimeout = 0) is untouched and continues to destroy unconditionally as before. * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Address PR 4295 open reviews: revert CleanupCallback IsIdleExpired gate, clarify IdleTimeout doc, rename idleEvictionDisabled -> idleEvictionEnabled - WaitHandleDbConnectionPool.CleanupCallback: revert the per-connection IsIdleExpired short-circuit added in e9993108 (mdaigle). _stackOld is LIFO and not age-ordered after a _stackNew promotion pass, so breaking on the first non-expired top could indefinitely hide older connections underneath under sustained traffic. Pruning is back to unconditional, matching the legacy 1-to-2-tick upper bound on idle lifetime. - DbConnectionPoolOptions.IdleTimeout XML doc: state that eviction is best-effort, runs on retrieval and during periodic maintenance, and may occasionally fire earlier than the configured value (copilot). - WaitHandleDbConnectionPool: flip the local bool from idleEvictionDisabled (only ever read negated) to idleEvictionEnabled for readability (paulmedynski). * Clarify IdleTimeout doc: spell out the half-timeout early-eviction case Addresses paulmedynski's non-gating doc suggestion on PR 4295. The previous wording said connections 'may occasionally be evicted earlier than the configured value' without explaining when or by how much. Updated to make the scenario concrete: because maintenance runs on a fixed cadence shorter than the timeout, a connection that becomes idle just before a maintenance pass may be discarded after roughly half the configured timeout. Stays implementation-agnostic so the shared DbConnectionPoolGroupOptions doc applies to both pool variants. * Address Copilot review on 72d241d7: trace reason, IsIdleExpired XML doc, resx period * Distinguish 'idle-expired' vs 'found dead' in pool trace when a connection is destroyed on the SEMAPHORE_HANDLE path; behavior unchanged. * Expand IsIdleExpired XML summary to mention the UseLegacyIdleTimeoutBehavior switch as a false-path. * Drop trailing period from DbConnectionString_IdleTimeout resx so the generated designer summary no longer ends with '..'. * Address Paul review on 3680ad36: capture IsIdleExpired once, plain dash in doc, restore resx period * Add failing regression test for return-path idle eviction Demonstrates the ordering bug in ChannelDbConnectionPool.ReturnInternalConnection: IsLiveConnection runs before SetReturnedTime, so a connection whose checkout exceeded IdleTimeout (e.g. a long-running query) is wrongly evicted as idle-expired on return even though it was actively in use on the wire. This test fails on the unpatched code (Expected: 1, Actual: 0) and will pass once the stamp is hoisted ahead of the liveness check. * Stamp ReturnedTime before IsLiveConnection in ChannelDbConnectionPool.ReturnInternalConnection ReturnInternalConnection called IsLiveConnection (which now contains an idle-expiry gate that reads connection.ReturnedTime) before SetReturnedTime refreshed the stamp on the keep branch. Because ReturnedTime is only ever written in this function, a checkout longer than IdleTimeout (long-running query, etc.) made the idle gate fire on return and destroy a healthy connection that was actively in use on the wire, not idle. Hoist the SetReturnedTime block (with the same UseLegacyIdleTimeoutBehavior / IdleTimeout != Zero gating that the idle gate uses) ahead of IsLiveConnection so the stamp reflects the moment of return before the liveness check reads it. Drop the now-redundant call on the keep branch. The previous regression test (1435958c) passes after this change on net462, net8.0, net9.0, net10.0. Flagged by Copilot review on PR #4295. * Address Copilot review feedback: spelling and IdleTimeout doc - WaitHandleDbConnectionPool.CleanupCallback: behaviour -> behavior. - DbConnectionPoolOptions.IdleTimeout: reword XML doc to acknowledge that not all pool implementations perform periodic maintenance (Channel pool enforces idle timeout lazily on retrieval/return). Drop the prescriptive `half the configured timeout'' math, which was specific to the WaitHandle pool's generational sweep. --------- Co-authored-by: Priyanka Tiwari Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../SqlConnectionStringBuilder.xml | 25 ++ .../ref/Microsoft.Data.SqlClient.cs | 4 + .../DbConnectionStringDefaults.cs | 3 + .../DbConnectionStringKeywords.cs | 1 + .../Data/ProviderBase/DbConnectionInternal.cs | 25 ++ .../ConnectionPool/ChannelDbConnectionPool.cs | 28 ++ .../ConnectionPool/DbConnectionPoolOptions.cs | 28 +- .../WaitHandleDbConnectionPool.cs | 73 +++++- .../Data/SqlClient/LocalAppContextSwitches.cs | 22 ++ .../Data/SqlClient/SqlConnectionFactory.cs | 3 +- .../Data/SqlClient/SqlConnectionOptions.cs | 12 + .../SqlClient/SqlConnectionStringBuilder.cs | 31 +++ .../src/Resources/Strings.Designer.cs | 9 + .../src/Resources/Strings.resx | 3 + .../Common/LocalAppContextSwitchesHelper.cs | 15 ++ .../SqlConnectionStringBuilderTest.cs | 38 +++ .../ChannelDbConnectionPoolPruningTest.cs | 3 +- .../ChannelDbConnectionPoolTest.cs | 243 +++++++++++++++++- .../WaitHandleDbConnectionPoolBudgetTest.cs | 3 +- ...itHandleDbConnectionPoolIdleTimeoutTest.cs | 234 +++++++++++++++++ ...itHandleDbConnectionPoolTransactionTest.cs | 3 +- .../SqlClient/LocalAppContextSwitchesTest.cs | 1 + 22 files changed, 786 insertions(+), 21 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolIdleTimeoutTest.cs diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index a68c0a323b..98daa768b5 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -979,6 +979,31 @@ The following example converts an existing connection string from using SQL Serv + + + Gets or sets the maximum time, in seconds, that a connection can sit unused (idle) in the connection pool before it is discarded. The default is 300 (5 minutes). + + + The idle timeout for pooled connections, in seconds. + + + + This property corresponds to the "Connection Idle Timeout" key within the connection string. + + + In versions where the AppContext switch Switch.Microsoft.Data.SqlClient.UseLegacyIdleTimeoutBehavior is enabled (the default), the driver preserves historical pooling behavior and does not enforce this setting. Set the switch to to enable idle-timeout enforcement. + + + The driver makes a best effort to discard connections that have remained idle in the pool for longer than this value. The exact point in the connection lifecycle at which the check occurs is an implementation detail and may change over time. This protects callers from receiving connections that may have been silently closed by firewalls, load balancers, or server-side inactivity thresholds. + + + A value of zero (0) disables idle expiration; connections are kept in the pool indefinitely (subject to other expiry rules such as ). + + + Idle timeout operates independently of . Whichever threshold is exceeded first causes the connection to be discarded. + + + Gets or sets the maximum number of connections allowed in the connection pool for this specific connection string. diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs index e52cbdc8d3..96ac073744 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs @@ -1368,6 +1368,10 @@ public SqlConnectionStringBuilder(string connectionString) { } [System.ComponentModel.DisplayNameAttribute("Load Balance Timeout")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public int LoadBalanceTimeout { get { throw null; } set { } } + /// + [System.ComponentModel.DisplayNameAttribute("Connection Idle Timeout")] + [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + public int IdleTimeout { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Max Pool Size")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs index 460fa0c2cc..bf700c0b55 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs @@ -36,6 +36,9 @@ internal static class DbConnectionStringDefaults internal const bool IntegratedSecurity = false; internal const SqlConnectionIPAddressPreference IpAddressPreference = SqlConnectionIPAddressPreference.IPv4First; internal const int LoadBalanceTimeout = 0; // default of 0 means don't use + // Default configured idle timeout is 5 minutes. Connection pool behavior is gated by + // LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior for compatibility. + internal const int IdleTimeout = 300; internal const int MaxPoolSize = 100; internal const int MinPoolSize = 0; internal const bool MultipleActiveResultSets = false; 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 index 5c902de2a8..b163ec2536 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringKeywords.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringKeywords.cs @@ -31,6 +31,7 @@ internal static class DbConnectionStringKeywords internal const string IntegratedSecurity = "Integrated Security"; internal const string IpAddressPreference = "IP Address Preference"; internal const string LoadBalanceTimeout = "Load Balance Timeout"; + internal const string IdleTimeout = "Connection Idle Timeout"; internal const string MaxPoolSize = "Max Pool Size"; internal const string MinPoolSize = "Min Pool Size"; internal const string MultipleActiveResultSets = "Multiple Active Result Sets"; 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 b295f84b3d..539f79ff47 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -82,6 +82,11 @@ internal DbConnectionInternal(ConnectionState state, bool hidePassword, bool all ShouldHidePassword = hidePassword; State = state; CreateTime = DateTime.UtcNow; + // Initialize the returned-to-pool stamp to creation time so that a freshly built connection is treated + // as "just used" by the pool's idle-expiry checks until the pool's return path stamps it again on first return. + // Without this initialization, ReturnedTime would default to DateTime.MinValue, which would cause + // IsLiveConnection to immediately evict every new connection whenever IdleTimeout is configured. + ReturnedTime = CreateTime; } #region Properties @@ -91,6 +96,15 @@ internal DbConnectionInternal(ConnectionState state, bool hidePassword, bool all /// internal DateTime CreateTime { get; } + /// + /// UTC timestamp of when this connection was last returned to the pool. + /// Stamped by . Initialized to in the constructor + /// so a freshly built connection is treated as "just used" until its first return. + /// Internal setter exists to support deterministic unit tests without reflection. + /// The pool reads this value to decide whether the connection has sat idle longer than the configured idle timeout. + /// + internal DateTime ReturnedTime { get; set; } + /// /// The pool generation at the time this connection was created or added to the pool. /// Used by to detect stale connections after a pool clear. @@ -726,6 +740,17 @@ internal virtual void PrepareForReplaceConnection() // By default, there is no preparation required } + /// + /// Stamps with the current UTC time. The pool calls this from its + /// return-to-pool path only when it intends the idle-timeout machinery to act on the value; + /// the connection owns the mechanism (recording the time) while the pool owns the policy + /// (deciding when a stamp is meaningful). + /// + internal void SetReturnedTime() + { + ReturnedTime = DateTime.UtcNow; + } + internal void PrePush(DbConnection expectedOwner) { // Called by IDbConnectionPool when we're about to be put into it's pool, we take this 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 index 3218bf461b..9169e9717e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -244,6 +244,18 @@ public void ReturnInternalConnection(DbConnectionInternal connection, DbConnecti { ValidateOwnershipAndSetPoolingState(connection, owningObject); + // Stamp the return time before IsLiveConnection runs so the idle-expiry gate inside it + // measures time-in-pool, not time-since-last-return. Without this, a connection whose + // checkout exceeded IdleTimeout (e.g. a long-running query) would be wrongly evicted on + // return even though it was actively in use on the wire. The same gating conditions are + // applied here as in IsLiveConnection so we avoid the per-return DateTime.UtcNow when + // idle expiry is disabled or the legacy idle-timeout behavior is in effect. + if (!LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior && + PoolGroupOptions.IdleTimeout != TimeSpan.Zero) + { + connection.SetReturnedTime(); + } + if (!IsLiveConnection(connection)) { RemoveConnection(connection); @@ -453,6 +465,22 @@ public bool TryGetConnection( /// Returns true if the connection is live and unexpired, otherwise returns false. private bool IsLiveConnection(DbConnectionInternal connection) { + // Connection has been sitting idle longer than the configured idle timeout. + // Checked before the (potentially expensive) liveness probe so an idle-expired + // connection is discarded without an SNI round-trip. + // ReturnedTime is initialized to CreateTime so a freshly minted connection never trips this + // check on first retrieval, and is then stamped by ReturnInternalConnection on every return. + // Use subtraction rather than addition so the comparison cannot throw if ReturnedTime is + // ever close to DateTime.MaxValue. A clock skew that leaves ReturnedTime in the future + // produces a negative TimeSpan, which falls through as not-expired (fail safe). + TimeSpan idleTimeout = PoolGroupOptions.IdleTimeout; + if (!LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior && + idleTimeout != TimeSpan.Zero && + DateTime.UtcNow - connection.ReturnedTime > idleTimeout) + { + return false; + } + // Broken physical connection if (!connection.IsConnectionAlive()) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs index 5ac6f4d565..b1790948b2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs @@ -13,6 +13,7 @@ internal sealed class DbConnectionPoolGroupOptions private readonly int _maxPoolSize; private readonly int _creationTimeout; private readonly TimeSpan _loadBalanceTimeout; + private readonly TimeSpan _idleTimeout; private readonly bool _hasTransactionAffinity; private readonly bool _useLoadBalancing; @@ -22,7 +23,8 @@ public DbConnectionPoolGroupOptions( int maxPoolSize, int creationTimeout, int loadBalanceTimeout, - bool hasTransactionAffinity + bool hasTransactionAffinity, + int idleTimeout ) { _poolByIdentity = poolByIdentity; @@ -36,6 +38,16 @@ bool hasTransactionAffinity _useLoadBalancing = true; } + if (idleTimeout < 0) + { + throw new ArgumentOutOfRangeException(nameof(idleTimeout), idleTimeout, "Idle timeout cannot be negative."); + } + + if (idleTimeout != 0) + { + _idleTimeout = TimeSpan.FromSeconds(idleTimeout); + } + _hasTransactionAffinity = hasTransactionAffinity; } @@ -54,6 +66,20 @@ public TimeSpan LoadBalanceTimeout { get { return _loadBalanceTimeout; } } + /// + /// The maximum time a pooled connection can sit unused (idle) in the pool before it becomes + /// eligible for eviction. Eviction is best-effort: a connection that has been idle longer + /// than this value is discarded either on the next retrieval attempt or during a periodic + /// pool maintenance pass (in pool implementations that perform periodic maintenance), + /// whichever happens first. Implementations may check this threshold opportunistically, so + /// eviction may occur somewhat before or after the exact timeout depending on the pool + /// implementation and maintenance cadence. + /// disables idle expiration. + /// + public TimeSpan IdleTimeout + { + get { return _idleTimeout; } + } public int MaxPoolSize { get { return _maxPoolSize; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index ecf8840ebb..d2c3b57323 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -223,8 +223,25 @@ internal WaitHandleDbConnectionPool( lock (s_random) { - // Random.Next is not thread-safe - _cleanupWait = s_random.Next(12, 24) * 10 * 1000; // 2-4 minutes in 10 sec intervals, WebData 103603 + TimeSpan idleTimeout = connectionPoolGroup.PoolGroupOptions.IdleTimeout; + if (LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior || idleTimeout == TimeSpan.Zero) + { + // Historical 2-4 minute random cleanup window. Used for the legacy switch and for + // the "idle eviction disabled" state (IdleTimeout=0) where the timer still runs for + // non-idle maintenance but CleanupCallback skips the generational sweep. + _cleanupWait = s_random.Next(12, 24) * 10 * 1000; // 2-4 minutes in 10 sec intervals, WebData 103603 + } + else + { + // New idle-timeout behavior with a configured value: the WaitHandle pool takes two + // pruning cycles to evict an idle connection (new->old generation, then old->closed), + // so halve the configured timeout to approximate the requested idle lifetime. Floor + // the period at 1 second so small IdleTimeout values (e.g. 1s) don't schedule a + // sub-second timer that wakes the threadpool just to walk empty stacks. + long cleanupWaitMilliseconds = (long)idleTimeout.TotalMilliseconds / 2; + cleanupWaitMilliseconds = Math.Max(cleanupWaitMilliseconds, 1000); + _cleanupWait = cleanupWaitMilliseconds >= int.MaxValue ? int.MaxValue : (int)cleanupWaitMilliseconds; + } } _connectionFactory = connectionFactory; @@ -351,8 +368,16 @@ private void CleanupCallback(object state) // at least one period but not more than two periods. SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); + // Idle eviction (the generational destroy/age-into-old-stack sweep below) is enabled under + // the legacy switch, or when the new behavior is on with a non-zero IdleTimeout. When the + // new behavior is on and IdleTimeout is 0, eviction is disabled entirely: skip the sweep + // and only run the MinPoolSize floor maintenance. + bool idleEvictionEnabled = + LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior || + PoolGroupOptions.IdleTimeout != TimeSpan.Zero; + // Destroy free objects that put us above MinPoolSize from old stack. - while (Count > MinPoolSize) + while (idleEvictionEnabled && Count > MinPoolSize) { // While above MinPoolSize... if (_waitHandles.PoolSemaphore.WaitOne(0, false)) @@ -363,6 +388,7 @@ private void CleanupCallback(object state) if (_stackOld.TryPop(out obj)) { Debug.Assert(obj != null, "null connection is not expected"); + // If we obtained one from the old stack, destroy it. SqlClientDiagnostics.Metrics.ExitFreeConnection(); @@ -414,7 +440,7 @@ private void CleanupCallback(object state) // Push to the old-stack. For each free object, move object from // new stack to old stack. - if (_waitHandles.PoolSemaphore.WaitOne(0, false)) + if (idleEvictionEnabled && _waitHandles.PoolSemaphore.WaitOne(0, false)) { for (; ; ) { @@ -667,6 +693,10 @@ private void DeactivateObject(DbConnectionInternal obj) // DelegatedTransactionEnded event will clean up the // connection appropriately regardless of the pool state. Debug.Assert(_transactedConnectionPool != null, "Transacted connection pool was not expected to be null."); + // Transacting connections are held in their own store and are never + // proactively closed (doing so would abort the transaction, which can be + // distributed). Idle-timeout enforcement does not apply here, so we do + // not call SetReturnedTime when parking the connection in the transacted pool. _transactedConnectionPool.PutTransactedObject(transaction, obj); rootTxn = true; } @@ -1061,9 +1091,11 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj Interlocked.Decrement(ref _waitCount); obj = GetFromGeneralPool(); - if ((obj != null) && (!obj.IsConnectionAlive())) + bool isIdleExpired = obj != null && IsIdleExpired(obj); + if ((obj != null) && (isIdleExpired || !obj.IsConnectionAlive())) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", Id, obj.ObjectID); + string reason = isIdleExpired ? "idle-expired" : "found dead"; + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, {2} and removed.", Id, obj.ObjectID, reason); DestroyObject(obj); obj = null; // Setting to null in case creating a new object fails @@ -1243,6 +1275,8 @@ private DbConnectionInternal GetFromTransactedPool(out Transaction transaction) } else if (!obj.IsConnectionAlive()) { + // Transacting connections are exempt from idle-timeout eviction (closing them + // would abort the transaction, possibly distributed). Only liveness is checked here. SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", Id, obj.ObjectID); DestroyObject(obj); obj = null; @@ -1363,6 +1397,16 @@ private void PutNewObject(DbConnectionInternal obj) SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Pushing to general pool.", Id, obj.ObjectID); + // Stamp the return time so IsIdleExpired can later decide whether the connection has sat + // unused too long. Skip the stamp when idle expiry is disabled or the legacy idle-timeout + // behavior is in effect to avoid the per-return DateTime.UtcNow on the hot return path; + // IsIdleExpired short-circuits on the same conditions so the value would be unread in + // those cases. + if (!LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior && + PoolGroupOptions.IdleTimeout != TimeSpan.Zero) + { + obj.SetReturnedTime(); + } _stackNew.Push(obj); _waitHandles.PoolSemaphore.Release(1); @@ -1370,6 +1414,23 @@ private void PutNewObject(DbConnectionInternal obj) } + /// + /// Returns true when the supplied connection has been sitting idle in the pool longer than the + /// configured . Returns false when idle + /// eviction is off - that is, when the + /// switch is enabled or is zero. + /// + private bool IsIdleExpired(DbConnectionInternal obj) + { + // Use subtraction rather than addition so the comparison cannot throw if ReturnedTime is + // ever close to DateTime.MaxValue. A clock skew that leaves ReturnedTime in the future + // produces a negative TimeSpan, which falls through as not-expired (fail safe). + TimeSpan idleTimeout = PoolGroupOptions.IdleTimeout; + return !LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior && + idleTimeout != TimeSpan.Zero && + DateTime.UtcNow - obj.ReturnedTime > idleTimeout; + } + public void ReturnInternalConnection(DbConnectionInternal obj, DbConnection owningObject) { Debug.Assert(obj != null, "null obj?"); 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 df762ed7ae..d951bb5d30 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -125,6 +125,13 @@ internal static class LocalAppContextSwitches private const string UseConnectionPoolV2String = "Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + /// + /// The name of the app context switch that controls whether to preserve + /// legacy idle-timeout behavior in connection pooling. + /// + private const string UseLegacyIdleTimeoutBehaviorString = + "Switch.Microsoft.Data.SqlClient.UseLegacyIdleTimeoutBehavior"; + /// /// The name of the app context switch that controls whether pool operations /// should count against the caller's overall ConnectTimeout budget. @@ -241,6 +248,11 @@ private enum SwitchValue : byte /// private static SwitchValue s_useConnectionPoolV2 = SwitchValue.None; + /// + /// The cached value of the UseLegacyIdleTimeoutBehavior switch. + /// + private static SwitchValue s_useLegacyIdleTimeoutBehavior = SwitchValue.None; + /// /// The cached value of the UseOverallConnectTimeoutForPoolWait switch. /// @@ -576,6 +588,16 @@ public static bool UseCompatibilityAsyncBehaviour defaultValue: false, ref s_useConnectionPoolV2); + /// + /// When set to true (the default), pooling preserves historical idle-timeout behavior. + /// When set to false, configured Connection Idle Timeout is enforced by the pool. + /// + public static bool UseLegacyIdleTimeoutBehavior => + AcquireAndReturn( + UseLegacyIdleTimeoutBehaviorString, + defaultValue: true, + ref s_useLegacyIdleTimeoutBehavior); + /// /// When set to true, pool operations count against the /// caller's ConnectTimeout budget. This includes waits and async operations. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 4cdee8bdc2..1f90eac202 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -740,7 +740,8 @@ private static DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(Sql opt.MaxPoolSize, connectionTimeout, opt.LoadBalanceTimeout, - opt.Enlist); + opt.Enlist, + opt.IdleTimeout); } return poolingOptions; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs index 6eafb195c8..734ecc125f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs @@ -105,6 +105,7 @@ internal static class TRANSACTIONBINDING private readonly int _commandTimeout; private readonly int _connectTimeout; private readonly int _loadBalanceTimeout; + private readonly int _idleTimeout; private readonly int _maxPoolSize; private readonly int _minPoolSize; private readonly int _packetSize; @@ -195,6 +196,7 @@ static SqlConnectionOptions() DbConnectionStringSynonyms.IpAddressPreference); AddKeywordToMap(DbConnectionStringKeywords.LoadBalanceTimeout, DbConnectionStringSynonyms.ConnectionLifetime); + AddKeywordToMap(DbConnectionStringKeywords.IdleTimeout); AddKeywordToMap(DbConnectionStringKeywords.MultipleActiveResultSets, DbConnectionStringSynonyms.MultipleActiveResultSets); AddKeywordToMap(DbConnectionStringKeywords.MaxPoolSize); @@ -274,6 +276,7 @@ internal SqlConnectionOptions(string connectionString) _commandTimeout = ConvertValueToInt32(DbConnectionStringKeywords.CommandTimeout, DbConnectionStringDefaults.CommandTimeout); _connectTimeout = ConvertValueToInt32(DbConnectionStringKeywords.ConnectTimeout, DbConnectionStringDefaults.ConnectTimeout); _loadBalanceTimeout = ConvertValueToInt32(DbConnectionStringKeywords.LoadBalanceTimeout, DbConnectionStringDefaults.LoadBalanceTimeout); + _idleTimeout = ConvertValueToInt32(DbConnectionStringKeywords.IdleTimeout, DbConnectionStringDefaults.IdleTimeout); _maxPoolSize = ConvertValueToInt32(DbConnectionStringKeywords.MaxPoolSize, DbConnectionStringDefaults.MaxPoolSize); _minPoolSize = ConvertValueToInt32(DbConnectionStringKeywords.MinPoolSize, DbConnectionStringDefaults.MinPoolSize); _packetSize = ConvertValueToInt32(DbConnectionStringKeywords.PacketSize, DbConnectionStringDefaults.PacketSize); @@ -318,6 +321,11 @@ internal SqlConnectionOptions(string connectionString) throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.LoadBalanceTimeout); } + if (_idleTimeout < 0) + { + throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.IdleTimeout); + } + if (_connectTimeout < 0) { throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.ConnectTimeout); @@ -579,6 +587,7 @@ internal SqlConnectionOptions(SqlConnectionOptions connectionOptions, string dat _commandTimeout = connectionOptions._commandTimeout; _connectTimeout = connectionOptions._connectTimeout; _loadBalanceTimeout = connectionOptions._loadBalanceTimeout; + _idleTimeout = connectionOptions._idleTimeout; _poolBlockingPeriod = connectionOptions._poolBlockingPeriod; _maxPoolSize = connectionOptions._maxPoolSize; _minPoolSize = connectionOptions._minPoolSize; @@ -650,6 +659,9 @@ internal SqlConnectionOptions(SqlConnectionOptions connectionOptions, string dat internal int CommandTimeout => _commandTimeout; internal int ConnectTimeout => _connectTimeout; internal int LoadBalanceTimeout => _loadBalanceTimeout; + // Maximum time (in seconds) a connection can sit idle in the pool before it is discarded. + // 0 disables idle expiration. + internal int IdleTimeout => _idleTimeout; internal int MaxPoolSize => _maxPoolSize; internal int MinPoolSize => _minPoolSize; internal int PacketSize => _packetSize; 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 4e54a32c75..4038dbb295 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -50,6 +50,7 @@ private enum Keywords ServerCertificate, TrustServerCertificate, LoadBalanceTimeout, + IdleTimeout, PacketSize, TypeSystemVersion, Authentication, @@ -101,6 +102,7 @@ private enum Keywords private int _commandTimeout = DbConnectionStringDefaults.CommandTimeout; private int _connectTimeout = DbConnectionStringDefaults.ConnectTimeout; private int _loadBalanceTimeout = DbConnectionStringDefaults.LoadBalanceTimeout; + private int _idleTimeout = DbConnectionStringDefaults.IdleTimeout; private int _maxPoolSize = DbConnectionStringDefaults.MaxPoolSize; private int _minPoolSize = DbConnectionStringDefaults.MinPoolSize; private int _packetSize = DbConnectionStringDefaults.PacketSize; @@ -155,6 +157,7 @@ private static string[] CreateValidKeywords() validKeywords[(int)Keywords.InitialCatalog] = DbConnectionStringKeywords.InitialCatalog; validKeywords[(int)Keywords.IntegratedSecurity] = DbConnectionStringKeywords.IntegratedSecurity; validKeywords[(int)Keywords.LoadBalanceTimeout] = DbConnectionStringKeywords.LoadBalanceTimeout; + validKeywords[(int)Keywords.IdleTimeout] = DbConnectionStringKeywords.IdleTimeout; validKeywords[(int)Keywords.MaxPoolSize] = DbConnectionStringKeywords.MaxPoolSize; validKeywords[(int)Keywords.MinPoolSize] = DbConnectionStringKeywords.MinPoolSize; validKeywords[(int)Keywords.MultipleActiveResultSets] = DbConnectionStringKeywords.MultipleActiveResultSets; @@ -212,6 +215,7 @@ private static Dictionary CreateKeywordsDictionary() { DbConnectionStringKeywords.InitialCatalog, Keywords.InitialCatalog }, { DbConnectionStringKeywords.IntegratedSecurity, Keywords.IntegratedSecurity }, { DbConnectionStringKeywords.LoadBalanceTimeout, Keywords.LoadBalanceTimeout }, + { DbConnectionStringKeywords.IdleTimeout, Keywords.IdleTimeout }, { DbConnectionStringKeywords.MultipleActiveResultSets, Keywords.MultipleActiveResultSets }, { DbConnectionStringKeywords.MaxPoolSize, Keywords.MaxPoolSize }, { DbConnectionStringKeywords.MinPoolSize, Keywords.MinPoolSize }, @@ -349,6 +353,8 @@ private object GetAt(Keywords index) return IntegratedSecurity; case Keywords.LoadBalanceTimeout: return LoadBalanceTimeout; + case Keywords.IdleTimeout: + return IdleTimeout; case Keywords.MultipleActiveResultSets: return MultipleActiveResultSets; case Keywords.MaxPoolSize: @@ -481,6 +487,9 @@ private void Reset(Keywords index) case Keywords.LoadBalanceTimeout: _loadBalanceTimeout = DbConnectionStringDefaults.LoadBalanceTimeout; break; + case Keywords.IdleTimeout: + _idleTimeout = DbConnectionStringDefaults.IdleTimeout; + break; case Keywords.MultipleActiveResultSets: _multipleActiveResultSets = DbConnectionStringDefaults.MultipleActiveResultSets; break; @@ -979,6 +988,9 @@ public override object this[string keyword] case Keywords.LoadBalanceTimeout: LoadBalanceTimeout = ConvertToInt32(value); break; + case Keywords.IdleTimeout: + IdleTimeout = ConvertToInt32(value); + break; case Keywords.MaxPoolSize: MaxPoolSize = ConvertToInt32(value); break; @@ -1473,6 +1485,25 @@ public int LoadBalanceTimeout } } + /// + [DisplayName(DbConnectionStringKeywords.IdleTimeout)] + [ResCategory(nameof(Strings.DataCategory_Pooling))] + [ResDescription(nameof(Strings.DbConnectionString_IdleTimeout))] + [RefreshProperties(RefreshProperties.All)] + public int IdleTimeout + { + get => _idleTimeout; + set + { + if (value < 0) + { + throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.IdleTimeout); + } + SetValue(DbConnectionStringKeywords.IdleTimeout, value); + _idleTimeout = value; + } + } + /// [DisplayName(DbConnectionStringKeywords.MaxPoolSize)] [ResCategory(nameof(Strings.DataCategory_Pooling))] diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs index eed5c58a69..7064a6c19c 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs @@ -1428,6 +1428,15 @@ internal static string DbConnectionString_LoadBalanceTimeout { } } + /// + /// Looks up a localized string similar to The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration.. + /// + internal static string DbConnectionString_IdleTimeout { + get { + return ResourceManager.GetString("DbConnectionString_IdleTimeout", resourceCulture); + } + } + /// /// Looks up a localized string similar to The maximum number of connections allowed in the pool.. /// diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx index 0a65febfde..e7f438873f 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx @@ -963,6 +963,9 @@ The minimum amount of time (in seconds) for this connection to live in the pool before being destroyed. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + The maximum number of connections allowed in the pool. diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 430d6645a8..ce64aea779 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -54,6 +54,7 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly bool? _useCompatibilityAsyncBehaviourOriginal; private readonly bool? _useCompatibilityProcessSniOriginal; private readonly bool? _useConnectionPoolV2Original; + private readonly bool? _useLegacyIdleTimeoutBehaviorOriginal; private readonly bool? _useOverallConnectTimeoutForPoolWaitOriginal; #if NET && _WINDOWS private readonly bool? _useManagedNetworkingOriginal; @@ -116,6 +117,8 @@ public LocalAppContextSwitchesHelper() GetSwitchValue("s_useCompatibilityProcessSni"); _useConnectionPoolV2Original = GetSwitchValue("s_useConnectionPoolV2"); + _useLegacyIdleTimeoutBehaviorOriginal = + GetSwitchValue("s_useLegacyIdleTimeoutBehavior"); _useOverallConnectTimeoutForPoolWaitOriginal = GetSwitchValue("s_useOverallConnectTimeoutForPoolWait"); #if NET && _WINDOWS @@ -185,6 +188,9 @@ public void Dispose() SetSwitchValue( "s_useConnectionPoolV2", _useConnectionPoolV2Original); + SetSwitchValue( + "s_useLegacyIdleTimeoutBehavior", + _useLegacyIdleTimeoutBehaviorOriginal); SetSwitchValue( "s_useOverallConnectTimeoutForPoolWait", _useOverallConnectTimeoutForPoolWaitOriginal); @@ -333,6 +339,15 @@ public bool? UseConnectionPoolV2 set => SetSwitchValue("s_useConnectionPoolV2", value); } + /// + /// Get or set the UseLegacyIdleTimeoutBehavior switch value. + /// + public bool? UseLegacyIdleTimeoutBehavior + { + get => GetSwitchValue("s_useLegacyIdleTimeoutBehavior"); + set => SetSwitchValue("s_useLegacyIdleTimeoutBehavior", value); + } + /// /// Get or set the UseOverallConnectTimeoutForPoolWait switch value. /// diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index a24c330181..180fe07209 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -250,6 +250,44 @@ public void SetInvalidLoadBalanceTimeout_Throws() Assert.Contains("load balance timeout", ex.Message, StringComparison.OrdinalIgnoreCase); } + [Fact] + public void IdleTimeout_DefaultIs300() + { + // Default-constructed builder should have IdleTimeout == 300 (5 minutes), matching Npgsql. + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); + Assert.Equal(300, builder.IdleTimeout); + } + + [Fact] + public void IdleTimeout_RoundTripsThroughConnectionString() + { + // Set via property, observe in ConnectionString; parse back and observe via property. + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder + { + IdleTimeout = 45 + }; + Assert.Contains("Connection Idle Timeout=45", builder.ConnectionString, StringComparison.OrdinalIgnoreCase); + + SqlConnectionStringBuilder parsed = new SqlConnectionStringBuilder(builder.ConnectionString); + Assert.Equal(45, parsed.IdleTimeout); + } + + [Fact] + public void IdleTimeout_CanonicalKeyword_Parses() + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder("Connection Idle Timeout=120"); + Assert.Equal(120, builder.IdleTimeout); + } + + [Fact] + public void SetInvalidIdleTimeout_Throws() + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(); + + ArgumentException ex = Assert.Throws(() => builder.IdleTimeout = -1); + Assert.Contains("idle", ex.Message, StringComparison.OrdinalIgnoreCase); + } + [Fact] public void SetInvalidMaxPoolSize_Throws() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs index 74d9758cde..3f7f3b0bc5 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs @@ -35,7 +35,8 @@ private static ChannelDbConnectionPool ConstructPool( maxPoolSize: maxPoolSize, creationTimeout: 15, loadBalanceTimeout: loadBalanceTimeout, - hasTransactionAffinity: true + hasTransactionAffinity: true, + idleTimeout: 0 ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index 1fea8aede1..b79baefbd5 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -12,6 +12,7 @@ using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; +using Microsoft.Data.SqlClient.Tests.Common; using Microsoft.Extensions.Time.Testing; using Xunit; @@ -34,7 +35,8 @@ private ChannelDbConnectionPool ConstructPool(SqlConnectionFactory connectionFac maxPoolSize: 50, creationTimeout: 15, loadBalanceTimeout: 0, - hasTransactionAffinity: true + hasTransactionAffinity: true, + idleTimeout: 0 ); dbConnectionPoolGroup ??= new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), @@ -611,7 +613,8 @@ public void TestLoadBalanceTimeout() maxPoolSize: 50, creationTimeout: 15, loadBalanceTimeout: 500, - hasTransactionAffinity: true + hasTransactionAffinity: true, + idleTimeout: 0 ); var pool = ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); Assert.Equal(poolGroupOptions.LoadBalanceTimeout, pool.LoadBalanceTimeout); @@ -629,7 +632,8 @@ public void TestPoolGroup() maxPoolSize: 50, creationTimeout: 15, loadBalanceTimeout: 500, - hasTransactionAffinity: true)); + hasTransactionAffinity: true, + idleTimeout: 0)); var pool = ConstructPool(SuccessfulConnectionFactory, dbConnectionPoolGroup: dbConnectionPoolGroup); Assert.Equal(dbConnectionPoolGroup, pool.PoolGroup); } @@ -643,7 +647,8 @@ public void TestPoolGroupOptions() maxPoolSize: 50, creationTimeout: 15, loadBalanceTimeout: 500, - hasTransactionAffinity: true); + hasTransactionAffinity: true, + idleTimeout: 0); var pool = ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); Assert.Equal(poolGroupOptions, pool.PoolGroupOptions); } @@ -679,7 +684,8 @@ public void TestUseLoadBalancing() maxPoolSize: 50, creationTimeout: 15, loadBalanceTimeout: 500, - hasTransactionAffinity: true); + hasTransactionAffinity: true, + idleTimeout: 0); var pool = ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); Assert.Equal(poolGroupOptions.UseLoadBalancing, pool.UseLoadBalancing); } @@ -938,6 +944,218 @@ out DbConnectionInternal? newConnection #endregion + #region Idle Timeout Tests + + // Helper: build a pool whose IdleTimeout is the given number of seconds. + private ChannelDbConnectionPool ConstructPoolWithIdleTimeout(int idleTimeoutSeconds) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 50, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: idleTimeoutSeconds); + return ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); + } + + [Fact] + public void IdleTimeout_PoolGroupOptions_ConvertsSecondsToTimeSpan() + { + // 30 seconds in -> TimeSpan(0, 0, 30) out. + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 50, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 30); + + Assert.Equal(TimeSpan.FromSeconds(30), poolGroupOptions.IdleTimeout); + } + + [Fact] + public void IdleTimeout_StampedOnReturn() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // Arrange - long idle timeout so the return path stamps (not evicts). + var pool = ConstructPoolWithIdleTimeout(idleTimeoutSeconds: 3600); + SqlConnection owningConnection = new(); + pool.TryGetConnection(owningConnection, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? connection); + Assert.NotNull(connection); + + // Backdate by a small amount that's still well inside the idle window so the return path + // doesn't decide to evict instead of stamp. + BackdateReturnedTime(connection, TimeSpan.FromSeconds(5)); + DateTime stampedBack = connection.ReturnedTime; + + // Act + DateTime before = DateTime.UtcNow; + pool.ReturnInternalConnection(connection, owningConnection); + DateTime after = DateTime.UtcNow; + + // Assert: stamp falls within the return window and is strictly newer than the backdated value. + Assert.InRange(connection.ReturnedTime, before, after); + Assert.True(connection.ReturnedTime > stampedBack); + } + + [Fact] + public void IdleTimeout_Zero_DoesNotExpire() + { + // Arrange - pool with idle expiry disabled + var pool = ConstructPoolWithIdleTimeout(idleTimeoutSeconds: 0); + SqlConnection owner = new(); + pool.TryGetConnection(owner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? first); + Assert.NotNull(first); + + // Return + back-date ReturnedTime to simulate a long sit. + pool.ReturnInternalConnection(first, owner); + BackdateReturnedTime(first, TimeSpan.FromHours(1)); + + // Act + SqlConnection owner2 = new(); + pool.TryGetConnection(owner2, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? second); + + // Assert - same instance, idle expiry disabled + Assert.Same(first, second); + Assert.Equal(1, pool.Count); + } + + [Fact] + public void IdleTimeout_Set_ExpiresOldConnection() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // Arrange - pool with 1-second idle timeout + var pool = ConstructPoolWithIdleTimeout(idleTimeoutSeconds: 1); + SqlConnection owner = new(); + pool.TryGetConnection(owner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? first); + Assert.NotNull(first); + + // Return + back-date ReturnedTime beyond the timeout. + pool.ReturnInternalConnection(first, owner); + BackdateReturnedTime(first, TimeSpan.FromSeconds(5)); + + // Act - request another connection + SqlConnection owner2 = new(); + pool.TryGetConnection(owner2, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? second); + + // Assert - the expired one is discarded; a new one is minted. + Assert.NotNull(second); + Assert.NotSame(first, second); + Assert.Equal(1, pool.Count); + } + + [Fact] + public void IdleTimeout_Set_KeepsFreshConnection() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // Arrange - 60-second idle timeout, connection just returned + var pool = ConstructPoolWithIdleTimeout(idleTimeoutSeconds: 60); + SqlConnection owner = new(); + pool.TryGetConnection(owner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? first); + Assert.NotNull(first); + pool.ReturnInternalConnection(first, owner); + + // Act - immediately request another connection + SqlConnection owner2 = new(); + pool.TryGetConnection(owner2, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? second); + + // Assert - same instance reused, well within idle window + Assert.Same(first, second); + } + + [Fact] + public void IdleTimeout_LegacySwitch_SuppressesEviction() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = true; + + // Arrange - 1-second idle timeout, but legacy switch suppresses the new eviction path. + var pool = ConstructPoolWithIdleTimeout(idleTimeoutSeconds: 1); + SqlConnection owner = new(); + pool.TryGetConnection(owner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? first); + Assert.NotNull(first); + + // Return + back-date well past the configured timeout. + pool.ReturnInternalConnection(first, owner); + BackdateReturnedTime(first, TimeSpan.FromMinutes(5)); + + // Act - request another connection. + SqlConnection owner2 = new(); + pool.TryGetConnection(owner2, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? second); + + // Assert - with the legacy switch on, the stale connection is still reused. + Assert.Same(first, second); + } + + [Fact] + public void IdleTimeout_LongCheckout_ReturnedConnectionIsPreserved() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // 1-second idle timeout: tight enough that a 5-second "checkout" backdate exceeds it. + var pool = ConstructPoolWithIdleTimeout(idleTimeoutSeconds: 1); + SqlConnection owner = new(); + pool.TryGetConnection(owner, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? connection); + Assert.NotNull(connection); + + // Simulate a long-running query: ReturnedTime (initialised to CreateTime when minted) is + // backdated past the idle window while the connection is still checked out and busy on + // the wire. + BackdateReturnedTime(connection, TimeSpan.FromSeconds(5)); + + // Act - return the actively-used connection. + pool.ReturnInternalConnection(connection, owner); + + // Assert - the connection must be re-stamped and preserved, not evicted as idle-expired. + // If IsLiveConnection runs before SetReturnedTime in ReturnInternalConnection, it sees + // the stale stamp and wrongly destroys a healthy in-use connection. + Assert.Equal(1, pool.Count); + + SqlConnection owner2 = new(); + pool.TryGetConnection(owner2, taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? reused); + Assert.Same(connection, reused); + } + + // Forcibly rewinds a connection's ReturnedTime by the given amount so tests don't have to sleep. + private static void BackdateReturnedTime(DbConnectionInternal connection, TimeSpan delta) + { + connection.ReturnedTime = DateTime.UtcNow - delta; + } + + #endregion + #region Test classes internal class SuccessfulSqlConnectionFactory : SqlConnectionFactory { @@ -1013,7 +1231,8 @@ public void Constructor_WithZeroMaxPoolSize_ThrowsArgumentOutOfRangeException() maxPoolSize: 0, // This should cause an exception creationTimeout: 15, loadBalanceTimeout: 0, - hasTransactionAffinity: true + hasTransactionAffinity: true, + idleTimeout: 0 ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), @@ -1044,7 +1263,8 @@ public void Constructor_WithLargeMaxPoolSize() maxPoolSize: 10000, creationTimeout: 15, loadBalanceTimeout: 0, - hasTransactionAffinity: true + hasTransactionAffinity: true, + idleTimeout: 0 ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), @@ -1085,7 +1305,8 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() maxPoolSize: 1, creationTimeout: 15, loadBalanceTimeout: 0, - hasTransactionAffinity: true + hasTransactionAffinity: true, + idleTimeout: 0 ); var dbConnectionPoolGroup1 = new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), @@ -1111,7 +1332,8 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() maxPoolSize: 2, creationTimeout: 15, loadBalanceTimeout: 0, - hasTransactionAffinity: true + hasTransactionAffinity: true, + idleTimeout: 0 ); var dbConnectionPoolGroup2 = new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), @@ -1154,7 +1376,8 @@ public async Task ConcurrentCallers_ShouldTimeoutIndependently() maxPoolSize: 1, creationTimeout: 15, loadBalanceTimeout: 0, - hasTransactionAffinity: true + hasTransactionAffinity: true, + idleTimeout: 0 ); var pool = ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBudgetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBudgetTest.cs index 6c00f8e38e..3688c8d772 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBudgetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBudgetTest.cs @@ -47,7 +47,8 @@ private WaitHandleDbConnectionPool CreatePool( maxPoolSize: maxPoolSize, creationTimeout: creationTimeoutMs, loadBalanceTimeout: 0, - hasTransactionAffinity: true); + hasTransactionAffinity: true, + idleTimeout: 0); var dbConnectionPoolGroup = new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolIdleTimeoutTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolIdleTimeoutTest.cs new file mode 100644 index 0000000000..4cbc8db636 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolIdleTimeoutTest.cs @@ -0,0 +1,234 @@ +// 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.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Microsoft.Data.SqlClient.Tests.Common; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool; + +/// +/// Deterministic tests for WaitHandleDbConnectionPool idle-timeout enforcement. +/// Mirrors the corresponding tests in so that the +/// retrieval-side idle-expiry behavior is covered for both pool implementations. +/// +public class WaitHandleDbConnectionPoolIdleTimeoutTest : IDisposable +{ + private const int DefaultMaxPoolSize = 50; + private const int DefaultMinPoolSize = 0; + private const int DefaultCreationTimeoutInMilliseconds = 15000; + + private WaitHandleDbConnectionPool _pool = null!; + + public void Dispose() + { + _pool?.Shutdown(); + _pool?.Clear(); + } + + private WaitHandleDbConnectionPool CreatePool(int idleTimeoutSeconds) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: DefaultMinPoolSize, + maxPoolSize: DefaultMaxPoolSize, + creationTimeout: DefaultCreationTimeoutInMilliseconds, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: idleTimeoutSeconds); + + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + poolGroupOptions); + + var pool = new WaitHandleDbConnectionPool( + new WaitHandleDbConnectionPoolTransactionTest.MockSqlConnectionFactory(), + dbConnectionPoolGroup, + DbConnectionPoolIdentity.NoIdentity, + new DbConnectionPoolProviderInfo()); + + pool.Startup(); + return pool; + } + + private DbConnectionInternal GetConnection(SqlConnection owner) + { + _pool.TryGetConnection( + owner, + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? connection); + Assert.NotNull(connection); + return connection!; + } + + // Forcibly rewinds a connection's ReturnedTime by the given amount so tests don't have to sleep. + private static void BackdateReturnedTime(DbConnectionInternal connection, TimeSpan delta) + { + connection.ReturnedTime = DateTime.UtcNow - delta; + } + + [Fact] + public void IdleTimeout_StampedOnReturn() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // Arrange - long idle timeout so the return path stamps (not evicts). + _pool = CreatePool(idleTimeoutSeconds: 3600); + SqlConnection owner = new(); + DbConnectionInternal connection = GetConnection(owner); + + // Backdate by a small amount that's still well inside the idle window so the return path + // doesn't decide to evict instead of stamp. + BackdateReturnedTime(connection, TimeSpan.FromSeconds(5)); + DateTime stampedBack = connection.ReturnedTime; + + // Act + DateTime before = DateTime.UtcNow; + _pool.ReturnInternalConnection(connection, owner); + DateTime after = DateTime.UtcNow; + + // Assert: stamp falls within the return window and is strictly newer than the backdated value. + Assert.InRange(connection.ReturnedTime, before, after); + Assert.True(connection.ReturnedTime > stampedBack); + } + + [Fact] + public void IdleTimeout_Zero_DoesNotExpire() + { + // Arrange - pool with idle expiry disabled + _pool = CreatePool(idleTimeoutSeconds: 0); + SqlConnection owner = new(); + DbConnectionInternal first = GetConnection(owner); + + // Return + back-date ReturnedTime to simulate a long sit. + _pool.ReturnInternalConnection(first, owner); + BackdateReturnedTime(first, TimeSpan.FromHours(1)); + + // Act + SqlConnection owner2 = new(); + DbConnectionInternal second = GetConnection(owner2); + + // Assert - same instance, idle expiry disabled + Assert.Same(first, second); + Assert.Equal(1, _pool.Count); + } + + [Fact] + public void IdleTimeout_Set_ExpiresOldConnection() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // Arrange - pool with 1-second idle timeout + _pool = CreatePool(idleTimeoutSeconds: 1); + SqlConnection owner = new(); + DbConnectionInternal first = GetConnection(owner); + + // Return + back-date ReturnedTime beyond the timeout. + _pool.ReturnInternalConnection(first, owner); + BackdateReturnedTime(first, TimeSpan.FromSeconds(5)); + + // Act - request another connection + SqlConnection owner2 = new(); + DbConnectionInternal second = GetConnection(owner2); + + // Assert - the expired one is discarded; a new one is minted. + Assert.NotSame(first, second); + } + + [Fact] + public void IdleTimeout_Set_KeepsFreshConnection() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // Arrange - 60-second idle timeout, connection just returned + _pool = CreatePool(idleTimeoutSeconds: 60); + SqlConnection owner = new(); + DbConnectionInternal first = GetConnection(owner); + _pool.ReturnInternalConnection(first, owner); + + // Act - immediately request another connection + SqlConnection owner2 = new(); + DbConnectionInternal second = GetConnection(owner2); + + // Assert - same instance reused, well within idle window + Assert.Same(first, second); + } + + [Fact] + public void IdleTimeout_LegacySwitch_SuppressesEviction() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = true; + + // Arrange - 1-second idle timeout, but legacy switch suppresses the new eviction path. + _pool = CreatePool(idleTimeoutSeconds: 1); + SqlConnection owner = new(); + DbConnectionInternal first = GetConnection(owner); + + // Return + back-date well past the configured timeout. + _pool.ReturnInternalConnection(first, owner); + BackdateReturnedTime(first, TimeSpan.FromMinutes(5)); + + // Act - request another connection. + SqlConnection owner2 = new(); + DbConnectionInternal second = GetConnection(owner2); + + // Assert - with the legacy switch on, the stale connection is still reused. + Assert.Same(first, second); + } + + [Fact] + public void IdleTimeout_LegacyOff_Zero_CleanupCallbackDoesNotEvict() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // Arrange - new idle-timeout behavior enabled with IdleTimeout=0 means "disabled entirely": + // the generational destroy/age-into-old-stack sweep in CleanupCallback must be a no-op so + // connections above MinPoolSize are not pruned on the cleanup tick. + _pool = CreatePool(idleTimeoutSeconds: 0); + + // Vend, then return, several connections so they sit in _stackNew. + SqlConnection o1 = new(); + SqlConnection o2 = new(); + SqlConnection o3 = new(); + DbConnectionInternal c1 = GetConnection(o1); + DbConnectionInternal c2 = GetConnection(o2); + DbConnectionInternal c3 = GetConnection(o3); + _pool.ReturnInternalConnection(c1, o1); + _pool.ReturnInternalConnection(c2, o2); + _pool.ReturnInternalConnection(c3, o3); + + int countBefore = _pool.Count; + int idleBefore = _pool.IdleCount; + Assert.Equal(3, countBefore); + Assert.Equal(3, idleBefore); + + // Act - two cleanup cycles. Pre-fix this would move new->old then destroy old above + // MinPoolSize (default 0), dropping Count and IdleCount to 0. + InvokeCleanupCallback(_pool); + InvokeCleanupCallback(_pool); + + // Assert - cleanup loops short-circuited; all three connections still pooled. + Assert.Equal(countBefore, _pool.Count); + Assert.Equal(idleBefore, _pool.IdleCount); + } + + private static void InvokeCleanupCallback(WaitHandleDbConnectionPool pool) + { + MethodInfo cleanup = typeof(WaitHandleDbConnectionPool).GetMethod( + "CleanupCallback", + BindingFlags.Instance | BindingFlags.NonPublic)!; + cleanup.Invoke(pool, new object?[] { null }); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs index 555de74b29..88af13922b 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs @@ -55,7 +55,8 @@ private WaitHandleDbConnectionPool CreatePool( maxPoolSize: maxPoolSize, creationTimeout: DefaultCreationTimeoutInMilliseconds, loadBalanceTimeout: 0, - hasTransactionAffinity: hasTransactionAffinity + hasTransactionAffinity: hasTransactionAffinity, + idleTimeout: 0 ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index c5c6f7ec73..67837e10f2 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -56,6 +56,7 @@ public void TestDefaultAppContextSwitchValues() Assert.True(LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour); Assert.True(LocalAppContextSwitches.UseCompatibilityProcessSni); Assert.True(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); + Assert.True(LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior); Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); Assert.False(LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait); Assert.False(LocalAppContextSwitches.TruncateScaledDecimal); From ff239c97ca1752007fd48b1dfb8ab8959aa90e05 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:04:35 -0700 Subject: [PATCH 158/298] [Scheduled Run] Localized resource files from OneLocBuild (#4398) Co-authored-by: SqlClient DevOps --- src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx | 6 ++++++ .../src/Resources/Strings.pt-BR.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx | 6 ++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx | 6 ++++++ .../src/Resources/Strings.zh-Hans.resx | 6 ++++++ .../src/Resources/Strings.zh-Hant.resx | 6 ++++++ 13 files changed, 78 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx index 65914fdf4c..dc677ada6a 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx @@ -606,6 +606,9 @@ Pro {0} nelze najít zprostředkovatele ověřování. Nainstalujte balíček NuGet Microsoft.Data.SqlClient.Extensions.Azure (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), abyste mohli používat metody ověřování Active Directory (Entra ID). + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Parametr {0} nesmí obsahovat hodnotu Null ani nesmí být prázdný. @@ -960,6 +963,9 @@ Minimální doba (v sekundách), po jakou má být toto připojení uchováváno ve fondu, než bude zrušeno. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Maximální počet připojení povolených ve fondu. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx index fcbac77cb7..820d9f4568 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx @@ -606,6 +606,9 @@ Für „{0}“ wurde kein Authentifizierungsanbieter gefunden. Installieren Sie das NuGet-Paket „Microsoft.Data.SqlClient.Extensions.Azure“ (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), um Active Directory (Entra ID)-Authentifizierungsmethoden zu verwenden. + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Der Parameter "{0}" darf nicht NULL oder leer sein. @@ -960,6 +963,9 @@ Die Mindestdauer (in Sekunden), die diese Verbindung im Pool verbleibt, bevor sie gelöscht wird. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Die maximal zulässige Anzahl von Verbindungen im Pool. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx index e62177e353..2789788846 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx @@ -606,6 +606,9 @@ No se encuentra un proveedor de autenticación para ''{0}''. Instale el paquete NuGet "Microsoft.Data.SqlClient.Extensions.Azure" (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) para usar métodos de autenticación Active Directory (Id. de Entra). + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + El parámetro "{0}" no puede ser nulo ni estar vacío. @@ -960,6 +963,9 @@ Periodo mínimo de tiempo (en segundos) que residirá esta conexión en el grupo antes de ser destruida. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Número máximo de conexiones permitidas en el grupo. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx index f175dbf816..67815a7f9f 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx @@ -606,6 +606,9 @@ Impossible de trouver un fournisseur d'authentification pour « {0} ». Installez le package NuGet « Microsoft.Data.SqlClient.Extensions.Azure » (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) pour utiliser les méthodes d'authentification Active Directory Domain Services (Entra ID). + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Le paramètre « {0} » ne peut pas être Null ou vide. @@ -960,6 +963,9 @@ Durée minimale de conservation (en secondes) de cette connexion avant sa destruction. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Nombre maximal de connexions autorisé dans le pool. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx index 129310c5eb..16ef46bd2c 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx @@ -606,6 +606,9 @@ Non è possibile trovare un provider di autenticazione per '{0}'. Installare il pacchetto NuGet 'Microsoft.Data.SqlClient.Extensions.Azure' (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) per usare i metodi di autenticazione di Active Directory (Entra ID). + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Il parametro '{0}' non può essere Null o vuoto. @@ -960,6 +963,9 @@ Tempo minimo di attesa (in secondi) perché la connessione sia attiva nel pool prima di essere eliminata. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Numero massimo di connessioni consentite nel pool. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx index f8b67f7cba..6fc0d5b867 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx @@ -606,6 +606,9 @@ '{0}' の認証プロバイダーが見つかりません。Active Directory (Entra ID) 認証方法を使用するには、'Microsoft.Data.SqlClient.Extensions.Azure' NuGet パッケージ (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) をインストールしてください。 + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + パラメーター '{0}' を null や空にすることはできません。 @@ -960,6 +963,9 @@ この接続が破棄されるまでにプールに存在できる最低限の時間 (秒) です。 + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + プールに保持できる最大接続数です。 diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx index a3a6bc51e4..fc186e4188 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx @@ -606,6 +606,9 @@ '{0}'에 대한 인증 공급자를 찾을 수 없습니다. Active Directory(Entra ID) 인증 방법을 사용하려면 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 패키지(https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure)를 설치하세요. + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + '{0}' 매개 변수는 null이거나 비워 둘 수 없습니다. @@ -960,6 +963,9 @@ 이 연결이 제거되기 전에 풀에서 활성화되어 있는 최소 시간(초)입니다. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + 풀에서 허용되는 최대 연결 수입니다. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx index eeb6d69665..ff7594a995 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx @@ -606,6 +606,9 @@ Nie można odnaleźć dostawcy uwierzytelniania dla elementu „{0}”. Zainstaluj pakiet NuGet „Microsoft.Data.SqlClient.Extensions.Azure” (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), aby korzystać z metod uwierzytelniania usługi Active Directory (Entra ID). + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Parametr „{0}” nie może mieć wartości null ani być pusty. @@ -960,6 +963,9 @@ Minimalny czas (w sekundach) przechowywania tego połączenia w puli przed jego usunięciem. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Maksymalna liczba połączeń dozwolonych w puli. 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 5873d61783..aceb8af554 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx @@ -606,6 +606,9 @@ Não é possível localizar um provedor de autenticação para '{0}'. Instale o pacote NuGet 'Microsoft.Data.SqlClient.Extensions.Azure' (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) para usar métodos de autenticação do Active Directory (Entra ID). + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + O parâmetro '{0}' não pode ser nulo ou vazio. @@ -960,6 +963,9 @@ O tempo mínimo (em segundos) para que essa conexão exista no pool antes de ser destruída. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + O número máximo de conexões permitidas no pool. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx index 9d1fcbc6f1..e6bd7e21ed 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx @@ -606,6 +606,9 @@ Не удается найти поставщик проверки подлинности для "{0}". Установите пакет NuGet "Microsoft.Data.SqlClient.Extensions.Azure" (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) для использования методов проверки подлинности Active Directory (Entra ID). + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Параметр «{0}» не может иметь значение NULL или быть пустым. @@ -960,6 +963,9 @@ Минимальное время (в секундах), которое это подключение будет оставаться в пуле до уничтожения. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Максимально допустимое число подключений в пуле. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx index 65a0046cfb..bee4c2db2d 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx @@ -606,6 +606,9 @@ '{0}' için kimlik doğrulaması sağlayıcısı bulunamıyor. Active Directory (Entra ID) kimlik doğrulaması yöntemlerini kullanmak için ‘Microsoft.Data.SqlClient.Extensions.Azure’ NuGet paketini (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) yükleyin. + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + '{0}' parametresi null veya boş olamaz. @@ -960,6 +963,9 @@ Bu bağlantının, yok edilmeden önce havuzda en az ne süreyle (saniye olarak) etkin kalacağı. + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Havuzda izin verilen en çok bağlantı sayısı. 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 8f881105c9..5fbca4eeb0 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx @@ -606,6 +606,9 @@ 找不到 ‘{0}’ 的身份验证提供程序。安装 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 包(https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure)以使用 Active Directory (Entra ID)身份验证方法。 + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + 参数“{0}”不能为 null 或为空。 @@ -960,6 +963,9 @@ 此连接在池中存活直至被销毁的最短时间(以秒为单位)。 + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + 池中所允许连接的最大数目。 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 2081722fb7..aea00e873a 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx @@ -606,6 +606,9 @@ 找不到 '{0}' 的驗證提供者。請安裝 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 套件 (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure),以使用 Active Directory (Entra ID) 認證方法。 + + Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + 參數 '{0}' 不能是 Null 或空白。 @@ -960,6 +963,9 @@ 集區中的連接在被終結前的存在時間下限 (單位為秒)。 + + The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + 集區中允許的連接上限。 From 83412c7c5a57ff6eda801df1e59df5668cbe5fbd Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Tue, 23 Jun 2026 18:25:07 -0500 Subject: [PATCH 159/298] Async Generic Helpers (#4334) * Extract AsyncHelper from SqlUtil.cs into the utilities namespace # Conflicts: # src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj # src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs # src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs # src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj # src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.netfx.cs # src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs # src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs # src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs # src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs # src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs # src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs # src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs # src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs * Implement AsyncHelpers with generic state, add unit tests * Change existing async helper calls in SqlBulkCopy.cs * Change existing async helper calls in TdsParser.cs * Change existing async helper calls in TdsParser.cs * Change existing async helper calls in SqlCommand * Remove old tests * Comments from copilot round 1 * Address feedback from wraith - including constraining state types to class types. * Fix tests for last change --- .../Connection/SqlConnectionInternal.cs | 10 +- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 62 +- .../Data/SqlClient/SqlCommand.Encryption.cs | 24 +- .../Data/SqlClient/SqlCommand.NonQuery.cs | 87 +- .../Data/SqlClient/SqlCommand.Reader.cs | 39 +- .../Data/SqlClient/SqlCommand.Xml.cs | 73 +- .../Microsoft/Data/SqlClient/SqlConnection.cs | 4 +- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 244 --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 20 +- .../Data/SqlClient/TdsParserStateObject.cs | 48 +- .../Data/SqlClient/Utilities/AsyncHelper.cs | 871 ++++++++ .../tests/Directory.Packages.props | 1 + .../tests/FunctionalTests/SqlHelperTest.cs | 62 - .../Microsoft.Data.SqlClient.UnitTests.csproj | 2 + .../SqlClient/Utilities/AsyncHelperTest.cs | 1814 +++++++++++++++++ .../UnitTests/Utilities/MockExtensions.cs | 120 ++ 16 files changed, 2995 insertions(+), 486 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/AsyncHelper.cs delete mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlHelperTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Utilities/AsyncHelperTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Utilities/MockExtensions.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index d87333c05b..7c3e177520 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Security; using System.Text; @@ -14,12 +13,15 @@ using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; -using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.ConnectionPool; -using IsolationLevel = System.Data.IsolationLevel; using Microsoft.Data.SqlClient.Internal; +using Microsoft.Data.SqlClient.Utilities; +using IsolationLevel = System.Data.IsolationLevel; + +#if NETFRAMEWORK +using Microsoft.Data.Common.ConnectionString; +#endif namespace Microsoft.Data.SqlClient.Connection { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index af6b2524c2..55142268e9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -17,6 +17,7 @@ using Microsoft.Data.Common; using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.Internal; +using Microsoft.Data.SqlClient.Utilities; namespace Microsoft.Data.SqlClient { @@ -789,7 +790,7 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i // Remove it from our unmatched set. unmatchedColumns.Remove(localColumn.MappedDestinationColumn); - + // Check for column types that we refuse to bulk load, even // though we found a match. // @@ -2868,18 +2869,17 @@ private Task CopyBatchesAsyncContinued(BulkCopySimpleResultSet internalResults, task, source, state: this, - onSuccess: (object state) => + onSuccess: state => { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - Task continuedTask = sqlBulkCopy.CopyBatchesAsyncContinuedOnSuccess(internalResults, updateBulkCommandText, cts, source); + Task continuedTask = state.CopyBatchesAsyncContinuedOnSuccess(internalResults, updateBulkCommandText, cts, source); if (continuedTask == null) { // Continuation finished sync, recall into CopyBatchesAsync to continue - sqlBulkCopy.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); + state.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); } }, - onFailure: static (Exception _, object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: false), - onCancellation: static (object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: true)); + onFailure: static (state, _) => state.CopyBatchesAsyncContinuedOnError(cleanupParser: false), + onCancellation: static state => state.CopyBatchesAsyncContinuedOnError(cleanupParser: true)); return source.Task; } @@ -2930,24 +2930,23 @@ private Task CopyBatchesAsyncContinuedOnSuccess(BulkCopySimpleResultSet internal writeTask, source, state: this, - onSuccess: (object state) => + onSuccess: state => { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; try { - sqlBulkCopy.RunParser(); - sqlBulkCopy.CommitTransaction(); + state.RunParser(); + state.CommitTransaction(); } catch (Exception) { - sqlBulkCopy.CopyBatchesAsyncContinuedOnError(cleanupParser: false); + state.CopyBatchesAsyncContinuedOnError(cleanupParser: false); throw; } // Always call back into CopyBatchesAsync - sqlBulkCopy.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); + state.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); }, - onFailure: static (Exception _, object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: false)); + onFailure: static (state, _ ) => state.CopyBatchesAsyncContinuedOnError(cleanupParser: false)); return source.Task; } } @@ -3202,21 +3201,20 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio // No need to cancel timer since SqlBulkCopy creates specific task source for reconnection. AsyncHelper.SetTimeoutExceptionWithState( - completion: cancellableReconnectTS, - timeout: BulkCopyTimeout, + taskCompletionSource: cancellableReconnectTS, + timeoutInSeconds: BulkCopyTimeout, state: _destinationTableName, - onFailure: static state => - SQL.BulkLoadInvalidDestinationTable((string)state, SQL.CR_ReconnectTimeout()), + onTimeout: static state => SQL.BulkLoadInvalidDestinationTable(state, SQL.CR_ReconnectTimeout()), cancellationToken: CancellationToken.None ); AsyncHelper.ContinueTaskWithState( - task: cancellableReconnectTS.Task, - completion: source, + taskToContinue:cancellableReconnectTS.Task, + taskCompletionSource: source, state: regReconnectCancel, - onSuccess: (object state) => + onSuccess: state => { - ((StrongBox)state).Value.Dispose(); + state.Value.Dispose(); if (_parserLock != null) { _parserLock.Release(); @@ -3226,10 +3224,22 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio _parserLock.Wait(canReleaseFromAnyThread: true); WriteToServerInternalRestAsync(cts, source); }, - onFailure: static (_, state) => ((StrongBox)state).Value.Dispose(), - onCancellation: static state => ((StrongBox)state).Value.Dispose(), - exceptionConverter: ex => SQL.BulkLoadInvalidDestinationTable(_destinationTableName, ex) - ); + onFailure: (state, exception) => + { + state.Value.Dispose(); + + // Convert exception and set it on the source + // Note: This is safe because the helper will only try to set the + // exception and b/c it is already set will pass without setting + // to the original exception. + Exception convertedException = SQL.BulkLoadInvalidDestinationTable( + _destinationTableName, + exception); + source.TrySetException(convertedException); + }, + onCancellation: static regReconnectCancel2 => + regReconnectCancel2.Value.Dispose()); + return; } else diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs index fa5cfb5ab2..3b0fc6e93f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.Data.Common; using Microsoft.Data.SqlClient.Connection; +using Microsoft.Data.SqlClient.Utilities; namespace Microsoft.Data.SqlClient { @@ -252,23 +253,22 @@ private SqlDataReader GetParameterEncryptionDataReader( bool isRetry) { returnTask = AsyncHelper.CreateContinuationTaskWithState( - task: fetchInputParameterEncryptionInfoTask, + taskToContinue: fetchInputParameterEncryptionInfoTask, state: this, - onSuccess: state => + onSuccess: sqlCommand => { - SqlCommand command = (SqlCommand)state; bool processFinallyBlockAsync = true; bool decrementAsyncCountInFinallyBlockAsync = true; try { // Check for any exceptions on network write, before reading. - command.CheckThrowSNIException(); + sqlCommand.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. - SqlConnectionInternal internalConnectionTds = command._activeConnection.GetOpenTdsConnection(); + SqlConnectionInternal internalConnectionTds = sqlCommand._activeConnection.GetOpenTdsConnection(); if (internalConnectionTds is not null) { internalConnectionTds.DecrementAsyncCount(); @@ -277,13 +277,13 @@ private SqlDataReader GetParameterEncryptionDataReader( // Complete executereader. // @TODO: If we can remove this reference, this could be a static lambda - describeParameterEncryptionDataReader = command.CompleteAsyncExecuteReader( + describeParameterEncryptionDataReader = sqlCommand.CompleteAsyncExecuteReader( isInternal: false, forDescribeParameterEncryption: true); - Debug.Assert(command._stateObj is null, "non-null state object in PrepareForTransparentEncryption."); + Debug.Assert(sqlCommand._stateObj is null, "non-null state object in PrepareForTransparentEncryption."); // Read the results of describe parameter encryption. - command.ReadDescribeEncryptionParameterResults( + sqlCommand.ReadDescribeEncryptionParameterResults( describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, isRetry); @@ -303,7 +303,7 @@ private SqlDataReader GetParameterEncryptionDataReader( } finally { - command.PrepareTransparentEncryptionFinallyBlock( + sqlCommand.PrepareTransparentEncryptionFinallyBlock( closeDataReader: processFinallyBlockAsync, decrementAsyncCount: decrementAsyncCountInFinallyBlockAsync, clearDataStructures: processFinallyBlockAsync, @@ -312,11 +312,9 @@ private SqlDataReader GetParameterEncryptionDataReader( describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); } }, - onFailure: static (exception, state) => + onFailure: static (sqlCommand, exception) => { - SqlCommand command = (SqlCommand)state; - command.CachedAsyncState?.ResetAsyncState(); - + sqlCommand.CachedAsyncState?.ResetAsyncState(); if (exception is not null) { throw exception; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs index 141416537f..ac9ccbbc6b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs @@ -12,6 +12,7 @@ using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.Internal; +using Microsoft.Data.SqlClient.Utilities; #if NETFRAMEWORK using System.Security.Permissions; @@ -82,11 +83,11 @@ public override int ExecuteNonQuery() #if NETFRAMEWORK SqlConnection.ExecutePermission.Demand(); #endif - + // Reset _pendingCancel upon entry into any Execute - used to synchronize state // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - + using var diagnosticScope = s_diagnosticListener.CreateCommandScope(this, _transaction); using var eventScope = SqlClientEventScope.Create($"SqlCommand.ExecuteNonQuery | API | Object Id {ObjectID}"); @@ -150,9 +151,9 @@ public override Task ExecuteNonQueryAsync(CancellationToken cancellationTok IsProviderRetriable ? InternalExecuteNonQueryWithRetryAsync(cancellationToken) : InternalExecuteNonQueryAsync(cancellationToken); - + #endregion - + #region Private Methods // @TODO: This can be inlined into InternalExecuteNonQueryAsync before restructuring into async pathway @@ -164,7 +165,7 @@ private IAsyncResult BeginExecuteNonQueryAsync(AsyncCallback callback, object st $"Activity Id {ActivityCorrelator.Current}, " + $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); - + return BeginExecuteNonQueryInternal( CommandBehavior.Default, callback, @@ -222,13 +223,13 @@ private IAsyncResult BeginExecuteNonQueryInternal( if (execNonQuery is not null) { AsyncHelper.ContinueTaskWithState( - task: execNonQuery, - completion: localCompletion, - state: Tuple.Create(this, localCompletion), - onSuccess: static state => + taskToContinue: execNonQuery, + taskCompletionSource: localCompletion, + state1: this, + state2: localCompletion, + onSuccess: static (sqlCommand, localCompletion) => { - var parameters = (Tuple>)state; - parameters.Item1.BeginExecuteNonQueryInternalReadStage(parameters.Item2); + sqlCommand.BeginExecuteNonQueryInternalReadStage(localCompletion); }); } else @@ -247,7 +248,7 @@ private IAsyncResult BeginExecuteNonQueryInternal( // When we use query caching for parameter encryption we need to retry on specific errors. // In these cases finalize the call internally and trigger a retry when needed. - // @TODO: store this method call in a variable, it's faaaaar too big to be used in an if statement + // @TODO: store this method call in a variable, it's faaaaar too big to be used in an if statement if ( !TriggerInternalEndAndRetryIfNecessary( behavior, @@ -270,7 +271,7 @@ private IAsyncResult BeginExecuteNonQueryInternal( { globalCompletion = localCompletion; } - + // Add callback after work is done to avoid overlapping Begin/End methods if (callback is not null) { @@ -317,31 +318,31 @@ private void CleanupAfterExecuteNonQueryAsync(Task task, TaskCompletionSour if (task.IsFaulted) { Exception e = task.Exception?.InnerException; - + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - + source.SetException(e); } else if (task.IsCanceled) { s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - + source.SetCanceled(); } else { // Task successful s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - + source.SetResult(task.Result); } } - + // @TODO: This can be inlined into InternalExecuteNonQueryAsync before restructuring into async pathway private int EndExecuteNonQueryAsync(IAsyncResult asyncResult) { Debug.Assert(!_internalEndExecuteInitiated || _stateObj == null); - + SqlClientEventSource.Log.TryCorrelationTraceEvent( "SqlCommand.EndExecuteNonQueryAsync | Info | Correlation | " + $"Object Id {ObjectID}, " + @@ -357,7 +358,7 @@ private int EndExecuteNonQueryAsync(IAsyncResult asyncResult) ReliablePutStateObject(); throw asyncException.InnerException; } - + ThrowIfReconnectionHasBeenCanceled(); // lock on _stateObj prevents races with close/cancel. // If we have already initiated the End call internally, we have already done that, so @@ -369,7 +370,7 @@ private int EndExecuteNonQueryAsync(IAsyncResult asyncResult) return EndExecuteNonQueryInternal(asyncResult); } } - + return EndExecuteNonQueryInternal(asyncResult); } @@ -413,7 +414,7 @@ private int EndExecuteNonQueryInternal(IAsyncResult asyncResult) WriteEndExecuteEvent(success, sqlExceptionNumber, isSynchronous: false); } } - + // @TODO: Return int? private object InternalEndExecuteNonQuery( IAsyncResult asyncResult, @@ -426,17 +427,17 @@ private object InternalEndExecuteNonQuery( $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"MARS={_activeConnection?.Parser.MARSOn}, " + $"AsyncCommandInProgress={_activeConnection?.AsyncCommandInProgress}"); - + VerifyEndExecuteState((Task)asyncResult, endMethod); WaitForAsyncResults(asyncResult, isInternal); - + // 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) { VerifyEndExecuteState((Task)asyncResult, endMethod, fullCheckForColumnEncryption: true); } - + bool processFinallyBlock = true; try { @@ -508,13 +509,13 @@ private object InternalEndExecuteNonQuery( PutStateObject(); } } - + Debug.Assert(_stateObj == null, "non-null state object in EndExecuteNonQuery"); - + return _rowsAffected; // @TODO: CER Exception Handling was removed here (see GH#3581) } - + // @TODO: Restructure to make this a sync-only method private Task InternalExecuteNonQuery( TaskCompletionSource completion, @@ -536,7 +537,7 @@ private Task InternalExecuteNonQuery( SqlStatistics statistics = Statistics; _rowsAffected = -1; - + // @TODO: Break into smaller methods ("full" and "simple") // This function may throw for an invalid connection @@ -630,16 +631,16 @@ private Task InternalExecuteNonQueryAsync(CancellationToken cancellationTok #if NETFRAMEWORK SqlConnection.ExecutePermission.Demand(); #endif - + SqlClientEventSource.Log.TryCorrelationTraceEvent( "SqlCommand.InternalExecuteNonQueryAsync | API | Correlation | " + $"Object Id {ObjectID}, " + $"Activity Id {ActivityCorrelator.Current}, " + $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); - + Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); - + // 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 // you need it for a more important piece of data that justifies the tuple allocation later @@ -698,14 +699,14 @@ private Task InternalExecuteNonQueryAsync(CancellationToken cancellationTok catch (Exception e) { s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - + source.SetException(e); context.Dispose(); } return returnedTask; } - + private Task InternalExecuteNonQueryWithRetry( // @TODO: Task is ignored bool sendToPipe, int timeout, @@ -725,7 +726,7 @@ private Task InternalExecuteNonQueryWithRetry( // @TODO: Task is ignored asyncWrite, isRetry, methodName)); - + usedCache = innerUsedCache; return result; } @@ -735,7 +736,7 @@ private Task InternalExecuteNonQueryWithRetryAsync(CancellationToken cancel sender: this, function: () => InternalExecuteNonQueryAsync(cancellationToken), cancellationToken); - + // @TODO: Sort args, drop TDS from name // @TODO: Restructure to make this the common method for sync and async methods (not InternalExecuteNonQuery) private Task RunExecuteNonQueryTds(string methodName, bool isAsync, int timeout, bool asyncWrite) @@ -755,7 +756,7 @@ private Task RunExecuteNonQueryTds(string methodName, bool isAsync, int timeout, TaskCompletionSource completion = new TaskCompletionSource(); _activeConnection.RegisterWaitingForReconnect(completion.Task); _reconnectionCompletionSource = completion; - + // Basically, this RunExecuteNonQueryTds onto the end of the reconnection RunExecuteNonQueryTdsSetupReconnnectContinuation( methodName, @@ -842,12 +843,12 @@ private Task RunExecuteNonQueryTds(string methodName, bool isAsync, int timeout, return null; } - + /// /// Since we use CompareExchange, we cannot make the reconnect success continuation static. /// Thus, we cannot use the "WithState" continuation helper. If this was part of /// RunExecuteNonQueryTds, we would be allocating the lambda each time. So, we make this a - /// separate method. + /// separate method. /// // @TODO: Sort args, fix name private void RunExecuteNonQueryTdsSetupReconnnectContinuation( @@ -865,7 +866,7 @@ private void RunExecuteNonQueryTdsSetupReconnnectContinuation( timeout, static () => SQL.CR_ReconnectTimeout(), timeoutCts.Token); - + AsyncHelper.ContinueTask( reconnectTask, completion, @@ -875,7 +876,7 @@ private void RunExecuteNonQueryTdsSetupReconnnectContinuation( { return; } - + Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion); timeoutCts.Cancel(); @@ -899,14 +900,14 @@ private void RunExecuteNonQueryTdsSetupReconnnectContinuation( } }); } - + #endregion internal sealed class ExecuteNonQueryAsyncCallContext : AAsyncCallContext { public SqlCommand Command => _owner; - + public Guid OperationId { get; set; } public TaskCompletionSource TaskCompletionSource => _source; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs index a98d82118b..bfdbd40fd5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs @@ -13,10 +13,10 @@ using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.Internal; +using Microsoft.Data.SqlClient.Utilities; #if NETFRAMEWORK using System.Security.Permissions; -using Microsoft.Data.SqlClient.Utilities; #endif namespace Microsoft.Data.SqlClient @@ -1599,18 +1599,18 @@ private Task RunExecuteReaderTdsSetupContinuation( { // @TODO: Why use the state version if we can't make this a static helper? return AsyncHelper.CreateContinuationTaskWithState( - task: writeTask, + taskToContinue: writeTask, state: _activeConnection, - onSuccess: state => + onSuccess: sqlConnection => { // This will throw if the connection is closed. // @TODO: So... can we have something that specifically does that? - ((SqlConnection)state).GetOpenTdsConnection(); + sqlConnection.GetOpenTdsConnection(); CachedAsyncState.SetAsyncReaderState(ds, runBehavior, optionSettings); }, - onFailure: static (exception, state) => + onFailure: static (sqlConnection, _) => { - ((SqlConnection)state).GetOpenTdsConnection().DecrementAsyncCount(); + sqlConnection.GetOpenTdsConnection().DecrementAsyncCount(); }); } @@ -1632,7 +1632,7 @@ private void RunExecuteReaderTdsSetupReconnectContinuation( AsyncHelper.SetTimeoutException( completion, timeout, - onFailure: static () => SQL.CR_ReconnectTimeout(), + onTimeout: static () => SQL.CR_ReconnectTimeout(), timeoutCts.Token); // @TODO: With an object to pass around we can use the state-based version @@ -1703,14 +1703,13 @@ private SqlDataReader RunExecuteReaderTdsWithTransparentParameterEncryption( // @TODO: This is a prime candidate for proper async-await execution TaskCompletionSource completion = new TaskCompletionSource(); AsyncHelper.ContinueTaskWithState( - task: describeParameterEncryptionTask, - completion: completion, + taskToContinue: describeParameterEncryptionTask, + taskCompletionSource: completion, state: this, - onSuccess: state => + onSuccess: sqlCommand => { - SqlCommand command = (SqlCommand)state; - command.GenerateEnclavePackage(); - command.RunExecuteReaderTds( + sqlCommand.GenerateEnclavePackage(); + sqlCommand.RunExecuteReaderTds( cmdBehavior, runBehavior, returnStream, @@ -1729,23 +1728,23 @@ private SqlDataReader RunExecuteReaderTdsWithTransparentParameterEncryption( else { AsyncHelper.ContinueTaskWithState( - task: subTask, - completion: completion, + taskToContinue: subTask, + taskCompletionSource: completion, state: completion, - onSuccess: static state => ((TaskCompletionSource)state).SetResult(null)); + onSuccess: static state => state.SetResult(null)); } }, - onFailure: static (exception, state) => + onFailure: static (sqlCommand, exception) => { - ((SqlCommand)state).CachedAsyncState?.ResetAsyncState(); + sqlCommand.CachedAsyncState?.ResetAsyncState(); if (exception is not null) { throw exception; } }, - onCancellation: static state => + onCancellation: static sqlCommand => { - ((SqlCommand)state).CachedAsyncState?.ResetAsyncState(); + sqlCommand.CachedAsyncState?.ResetAsyncState(); }); task = completion.Task; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs index 2be6166e8b..447627375e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs @@ -13,6 +13,7 @@ using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.Server; using Microsoft.Data.SqlClient.Internal; +using Microsoft.Data.SqlClient.Utilities; #if NETFRAMEWORK using System.Security.Permissions; @@ -43,7 +44,7 @@ public IAsyncResult BeginExecuteXmlReader(AsyncCallback callback, object stateOb #if NETFRAMEWORK SqlConnection.ExecutePermission.Demand(); #endif - + SqlClientEventSource.Log.TryCorrelationTraceEvent( "SqlCommand.BeginExecuteXmlReader | API | Correlation | " + $"Object Id {ObjectID}, " + @@ -77,18 +78,18 @@ public XmlReader EndExecuteXmlReader(IAsyncResult asyncResult) $"Command Text '{CommandText}'"); } } - + /// public XmlReader ExecuteXmlReader() { #if NETFRAMEWORK SqlConnection.ExecutePermission.Demand(); #endif - + // Reset _pendingCancel upon entry into any Execute - used to synchronize state // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - + using var diagnosticScope = s_diagnosticListener.CreateCommandScope(this, _transaction); using var eventScope = SqlClientEventScope.Create($"SqlCommand.ExecuteXmlReader | API | Object Id {ObjectID}"); @@ -142,11 +143,11 @@ public Task ExecuteXmlReaderAsync(CancellationToken cancellationToken IsProviderRetriable ? InternalExecuteXmlReaderWithRetryAsync(cancellationToken) : InternalExecuteXmlReaderAsync(cancellationToken); - + #endregion - + #region Private Methods - + private static XmlReader CompleteXmlReader(SqlDataReader dataReader, bool isAsync) { XmlReader xmlReader = null; @@ -189,7 +190,7 @@ private IAsyncResult BeginExecuteXmlReaderAsync(AsyncCallback callback, object s $"Activity Id {ActivityCorrelator.Current}, " + $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); - + return BeginExecuteXmlReaderInternal( CommandBehavior.SequentialAccess, callback, @@ -215,7 +216,7 @@ private IAsyncResult BeginExecuteXmlReaderInternal( // Reset _pendingCancel upon entry into any Execute - used to synchronize state // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - + // Special case - done outside of try/catches to prevent putting a stateObj back // into pool when we should not. ValidateAsyncCommand(); @@ -247,7 +248,7 @@ private IAsyncResult BeginExecuteXmlReaderInternal( asyncWrite, isRetry); - // @TODO: NonQuery pathway has the continueTaskWithState block inside this try. One or the other seems wrong + // @TODO: NonQuery pathway has the continueTaskWithState block inside this try. One or the other seems wrong } catch (Exception e) when (ADP.IsCatchableOrSecurityExceptionType(e)) { @@ -261,13 +262,13 @@ private IAsyncResult BeginExecuteXmlReaderInternal( if (writeTask is not null) { AsyncHelper.ContinueTaskWithState( - task: writeTask, - completion: localCompletion, - state: Tuple.Create(this, localCompletion), - onSuccess: static state => + taskToContinue: writeTask, + taskCompletionSource: localCompletion, + state1: this, + state2: localCompletion, + onSuccess: static (sqlCommand, localCompletion) => { - var parameters = (Tuple>)state; - parameters.Item1.BeginExecuteXmlReaderInternalReadStage(parameters.Item2); + sqlCommand.BeginExecuteXmlReaderInternalReadStage(localCompletion); }); } else @@ -352,21 +353,21 @@ private void CleanupAfterExecuteXmlReaderAsync( if (task.IsFaulted) { Exception e = task.Exception?.InnerException; - + s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - + source.SetException(e); } else if (task.IsCanceled) { s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - + source.SetCanceled(); } else { s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - + source.SetResult(task.Result); } } @@ -374,7 +375,7 @@ private void CleanupAfterExecuteXmlReaderAsync( private XmlReader EndExecuteXmlReaderAsync(IAsyncResult asyncResult) { Debug.Assert(!_internalEndExecuteInitiated || _stateObj is null); - + SqlClientEventSource.Log.TryCorrelationTraceEvent( "SqlCommand.EndExecuteXmlReaderAsync | API | Correlation | " + $"Object Id {ObjectID}, " + @@ -389,9 +390,9 @@ private XmlReader EndExecuteXmlReaderAsync(IAsyncResult asyncResult) ReliablePutStateObject(); throw asyncException.InnerException; } - + ThrowIfReconnectionHasBeenCanceled(); - + // Locking _stateObj prevents races with close/cancel. // If we have already initiated the End call internally, we have already done that, so // no point doing it again. @@ -405,7 +406,7 @@ private XmlReader EndExecuteXmlReaderAsync(IAsyncResult asyncResult) return EndExecuteXmlReaderInternal(asyncResult); } - + private XmlReader EndExecuteXmlReaderInternal(IAsyncResult asyncResult) { bool success = false; @@ -443,27 +444,27 @@ private XmlReader EndExecuteXmlReaderInternal(IAsyncResult asyncResult) WriteEndExecuteEvent(success, sqlExceptionNumber, isSynchronous: false); } } - + private Task InternalExecuteXmlReaderAsync(CancellationToken cancellationToken) { #if NETFRAMEWORK SqlConnection.ExecutePermission.Demand(); #endif - + SqlClientEventSource.Log.TryCorrelationTraceEvent( "SqlCommand.InternalExecuteXmlReaderAsync | API | Correlation | " + $"Object Id {ObjectID}, " + $"Activity Id {ActivityCorrelator.Current}, " + $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); - + Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); - + // 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 // you need it for a more important piece of data that justifies the tuple allocation later TaskCompletionSource source = new TaskCompletionSource(_activeConnection); - + CancellationTokenRegistration registration = new CancellationTokenRegistration(); if (cancellationToken.CanBeCanceled) { @@ -476,7 +477,7 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella registration = cancellationToken.Register(callback: s_cancelIgnoreFailure, state: this); } - // @TODO: This can be cleaned up to lines if InnerConnection is always SqlInternalConnection + // @TODO: This can be cleaned up to lines if InnerConnection is always SqlInternalConnection ExecuteXmlReaderAsyncCallContext context = null; if (_activeConnection?.InnerConnection is SqlConnectionInternal sqlInternalConnection) { @@ -516,34 +517,34 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella TaskCompletionSource source = context.TaskCompletionSource; context.Dispose(); - + command.CleanupAfterExecuteXmlReaderAsync(task, source, operationId); }, scheduler: TaskScheduler.Default); } - catch (Exception e) + catch (Exception e) { s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - + source.SetException(e); } return returnedTask; } - + private Task InternalExecuteXmlReaderWithRetryAsync(CancellationToken cancellationToken) => RetryLogicProvider.ExecuteAsync( sender: this, () => InternalExecuteXmlReaderAsync(cancellationToken), cancellationToken); - + #endregion internal sealed class ExecuteXmlReaderAsyncCallContext : AAsyncCallContext { public SqlCommand Command => _owner; - + public Guid OperationId { get; set; } public TaskCompletionSource TaskCompletionSource => _source; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 6a4d7b8972..b27b7081c6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -23,8 +23,10 @@ using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Data.SqlClient.Diagnostics; -using Microsoft.SqlServer.Server; using Microsoft.Data.SqlClient.Internal; +using Microsoft.Data.SqlClient.Utilities; +using Microsoft.SqlServer.Server; + #if NETFRAMEWORK using System.Runtime.CompilerServices; using System.Security.Permissions; 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 049734993e..678697d221 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -46,250 +46,6 @@ internal static ArgumentOutOfRangeException InvalidMinAndMaxPair(string minParam => new ArgumentOutOfRangeException(minParamName, StringsHelper.GetString(Strings.SqlRetryLogic_InvalidMinMaxPair, minValue, maxValue, minParamName, maxParamName)); } - internal static class AsyncHelper - { - internal static Task CreateContinuationTask( - Task task, - Action onSuccess, - Action onFailure = null) - { - if (task == null) - { - onSuccess(); - return null; - } - else - { - TaskCompletionSource completion = new TaskCompletionSource(); - ContinueTaskWithState( - task, - completion, - state: Tuple.Create(onSuccess, onFailure, completion), - onSuccess: static (object state) => - { - var parameters = (Tuple, TaskCompletionSource>)state; - Action success = parameters.Item1; - TaskCompletionSource taskCompletionSource = parameters.Item3; - success(); - taskCompletionSource.SetResult(null); - }, - onFailure: static (Exception exception, object state) => - { - var parameters = (Tuple, TaskCompletionSource>)state; - Action failure = parameters.Item2; - failure?.Invoke(exception); - } - ); - return completion.Task; - } - } - - internal static Task CreateContinuationTaskWithState(Task task, object state, Action onSuccess, Action onFailure = null) - { - if (task == null) - { - onSuccess(state); - return null; - } - else - { - var completion = new TaskCompletionSource(); - ContinueTaskWithState(task, completion, state, - onSuccess: (object continueState) => - { - onSuccess(continueState); - completion.SetResult(null); - }, - onFailure: onFailure - ); - return completion.Task; - } - } - - internal static Task CreateContinuationTask( - Task task, - Action onSuccess, - T1 arg1, - T2 arg2, - Action onFailure = null) - { - return CreateContinuationTask(task, () => onSuccess(arg1, arg2), onFailure); - } - - internal static void ContinueTask(Task task, - TaskCompletionSource completion, - Action onSuccess, - Action onFailure = null, - Action onCancellation = null, - Func exceptionConverter = null) - { - task.ContinueWith( - tsk => - { - if (tsk.Exception != null) - { - Exception exc = tsk.Exception.InnerException; - if (exceptionConverter != null) - { - exc = exceptionConverter(exc); - } - try - { - onFailure?.Invoke(exc); - } - finally - { - completion.TrySetException(exc); - } - } - else if (tsk.IsCanceled) - { - try - { - onCancellation?.Invoke(); - } - finally - { - completion.TrySetCanceled(); - } - } - else - { - try - { - onSuccess(); - } - // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception e) - { - completion.SetException(e); - } - } - }, TaskScheduler.Default - ); - } - - // the same logic as ContinueTask but with an added state parameter to allow the caller to avoid the use of a closure - // the parameter allocation cannot be avoided here and using closure names is clearer than Tuple numbered properties - internal static void ContinueTaskWithState(Task task, - TaskCompletionSource completion, - object state, - Action onSuccess, - Action onFailure = null, - Action onCancellation = null, - Func exceptionConverter = null) - { - task.ContinueWith( - (Task tsk, object state2) => - { - if (tsk.Exception != null) - { - Exception exc = tsk.Exception.InnerException; - if (exceptionConverter != null) - { - exc = exceptionConverter(exc); - } - - try - { - onFailure?.Invoke(exc, state2); - } - finally - { - completion.TrySetException(exc); - } - } - else if (tsk.IsCanceled) - { - try - { - onCancellation?.Invoke(state2); - } - finally - { - completion.TrySetCanceled(); - } - } - else - { - try - { - onSuccess(state2); - } - // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception e) - { - completion.SetException(e); - } - } - }, - state: state, - scheduler: TaskScheduler.Default - ); - } - - internal static void WaitForCompletion(Task task, int timeout, Action onTimeout = null, bool rethrowExceptions = true) - { - try - { - task.Wait(timeout > 0 ? (1000 * timeout) : Timeout.Infinite); - } - catch (AggregateException ae) - { - if (rethrowExceptions) - { - Debug.Assert(ae.InnerExceptions.Count == 1, "There is more than one exception in AggregateException"); - ExceptionDispatchInfo.Capture(ae.InnerException).Throw(); - } - } - if (!task.IsCompleted) - { - task.ContinueWith(static t => { var ignored = t.Exception; }); //Ensure the task does not leave an unobserved exception - onTimeout?.Invoke(); - } - } - - internal static void SetTimeoutException(TaskCompletionSource completion, int timeout, Func onFailure, CancellationToken ctoken) - { - if (timeout > 0) - { - Task.Delay(timeout * 1000, ctoken).ContinueWith( - (Task task) => - { - if (!task.IsCanceled && !completion.Task.IsCompleted) - { - completion.TrySetException(onFailure()); - } - } - ); - } - } - - internal static void SetTimeoutExceptionWithState( - TaskCompletionSource completion, - int timeout, - object state, - Func onFailure, - CancellationToken cancellationToken) - { - if (timeout <= 0) - { - return; - } - - Task.Delay(timeout * 1000, cancellationToken).ContinueWith( - (task, innerState) => - { - if (!task.IsCanceled && !completion.Task.IsCompleted) - { - completion.TrySetException(onFailure(innerState)); - } - }, - state: state, - cancellationToken: CancellationToken.None); - } - } - internal static class SQL { // The class SQL defines the exceptions that are specific to the SQL Adapter. 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 79c725c074..c3f96b077a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -25,15 +25,13 @@ using Microsoft.Data.SqlClient.DataClassification; using Microsoft.Data.SqlClient.LocalDb; using Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.Internal; using Microsoft.Data.SqlClient.Utilities; using Microsoft.SqlServer.Server; -using Microsoft.Data.SqlClient.Internal; #if NETFRAMEWORK using System.Runtime.CompilerServices; -#if _WINDOWS using Interop.Windows.Sni; -#endif using Microsoft.Data.SqlTypes; #endif @@ -12327,11 +12325,11 @@ private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaTy } else { - return AsyncHelper.CreateContinuationTask( + return AsyncHelper.CreateContinuationTaskWithState( unterminatedWriteTask, - onSuccess: WriteInt, - arg1: 0, - arg2: stateObj); + state1: this, + state2: stateObj, + onSuccess: static (parser, state) => parser.WriteInt(0, state)); } } else @@ -13288,11 +13286,11 @@ private Task WriteEncryptionMetadata(Task terminatedWriteTask, SqlColumnEncrypti else { // Otherwise, create a continuation task to write the encryption metadata after the previous write completes. - return AsyncHelper.CreateContinuationTask( + return AsyncHelper.CreateContinuationTaskWithState( terminatedWriteTask, - onSuccess: WriteEncryptionMetadata, - arg1: columnEncryptionParameterInfo, - arg2: stateObj); + state1: columnEncryptionParameterInfo, + state2: stateObj, + onSuccess: WriteEncryptionMetadata); } } 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 45ed29c2ce..921fb3f30b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -17,6 +17,7 @@ using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ManagedSni; using Microsoft.Data.SqlClient.Internal; +using Microsoft.Data.SqlClient.Utilities; #if NETFRAMEWORK using System.Runtime.ConstrainedExecution; @@ -24,10 +25,6 @@ namespace Microsoft.Data.SqlClient { -#if NETFRAMEWORK - using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers; -#endif - sealed internal class LastIOTimer { internal long _value; @@ -189,8 +186,8 @@ private enum SnapshotStatus 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. + // Below 2 properties are used to enforce timeout delays in code to + // reproduce issues related to threadpool starvation and timeout delay. // It should always be set to false by default, and only be enabled during testing. internal bool _enforceTimeoutDelay = false; internal int _enforcedTimeoutDelayInMilliSeconds = 5000; @@ -1272,13 +1269,12 @@ internal Task ExecuteFlush() else { return AsyncHelper.CreateContinuationTaskWithState( - task: writePacketTask, + taskToContinue: writePacketTask, state: this, - onSuccess: static (object state) => + onSuccess: static state => { - TdsParserStateObject stateObject = (TdsParserStateObject)state; - stateObject.HasPendingData = true; - stateObject._messageStatus = 0; + state.HasPendingData = true; + state._messageStatus = 0; } ); } @@ -2178,7 +2174,7 @@ internal TdsOperationStatus TryReadStringWithEncoding(int length, System.Text.En buf = TryTakeSnapshotStorage() as byte[]; Debug.Assert(buf != null || !isContinuing, "if continuing stored buffer must be present to contain previous data to continue from"); Debug.Assert(buf == null || buf.Length == length, "stored buffer length must be null or must have been created with the correct length"); - + if (buf != null) { startOffset = GetSnapshotTotalSize(); @@ -2191,7 +2187,7 @@ internal TdsOperationStatus TryReadStringWithEncoding(int length, System.Text.En } TdsOperationStatus result = TryReadByteArray(buf, length, out _, startOffset, canContinue); - + if (result != TdsOperationStatus.Done) { if (result == TdsOperationStatus.NeedMoreData) @@ -3485,7 +3481,7 @@ internal TdsOperationStatus TryReadNetworkPacket() while (_inBytesRead == 0) { // a partial packet must have taken the packet data so we - // need to read more data to complete the packet, but we + // need to read more data to complete the packet, but we // can't return NeedMoreData in sync mode so we have to // spin fetching more data here until we have something // that the caller can read @@ -3772,7 +3768,7 @@ private void OnTimeoutAsync(object state) TimeoutState timeoutState = (TimeoutState)state; if (timeoutState.IdentityValue == _timeoutIdentityValue) { - // the return value is not useful here because no choice is going to be made using it + // the return value is not useful here because no choice is going to be made using it // we only want to make this call to set the state knowing that it will be seen later OnTimeoutCore(TimeoutState.Running, TimeoutState.ExpiredAsync); } @@ -3935,7 +3931,7 @@ internal void ReadSni(TaskCompletionSource completion) { Debug.Assert(completion != null, "Async on but null asyncResult passed"); - // if the state is currently stopped then change it to running and allocate a new identity value from + // if the state is currently stopped then change it to running and allocate a new identity value from // the identity source. The identity value is used to correlate timer callback events to the currently // running timeout and prevents a late timer callback affecting a result it does not relate to int previousTimeoutState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Running, TimeoutState.Stopped); @@ -4372,7 +4368,7 @@ private void WriteBytesSetupContinuation(byte[] array, int len, TaskCompletionSo /// packet parsing. /// /// - internal string DumpBuffer() + internal string DumpBuffer() { StringBuilder buffer = new StringBuilder(128); buffer.AppendLine("dumping buffer"); @@ -4381,7 +4377,7 @@ internal string DumpBuffer() int cc = 0; // character counter int i; buffer.AppendLine("used buffer:"); - for (i=0; i< _inBytesUsed; i++) + for (i=0; i< _inBytesUsed; i++) { if (cc==16) { buffer.AppendLine(); @@ -4390,16 +4386,16 @@ internal string DumpBuffer() buffer.AppendFormat("{0,-2:X2} ", _inBuff[i]); cc++; } - if (cc>0) + if (cc>0) { buffer.AppendLine(); } cc = 0; buffer.AppendLine("unused buffer:"); - for (i=_inBytesUsed; i<_inBytesRead; i++) + for (i=_inBytesUsed; i<_inBytesRead; i++) { - if (cc==16) + if (cc==16) { buffer.AppendLine(); cc = 0; @@ -4407,13 +4403,13 @@ internal string DumpBuffer() buffer.AppendFormat("{0,-2:X2} ", _inBuff[i]); cc++; } - if (cc>0) + if (cc>0) { buffer.AppendLine(); } return buffer.ToString(); } - + internal void SetSnapshot() { StateSnapshot snapshot = _snapshot; @@ -4444,7 +4440,7 @@ internal void ResetSnapshot() } /// - /// Returns true if the state object is in the state of continuing from a previously stored snapshot packet + /// Returns true if the state object is in the state of continuing from a previously stored snapshot packet /// meaning that consumers should resume from the point where they last needed more data instead of beginning /// to process packets in the snapshot from the beginning again /// @@ -4539,7 +4535,7 @@ internal int GetSnapshotPacketID() /// /// sets a value on the snapshot to allow the ContinueEnabled property to return true.
/// this function should be called only by functions that explicitly support the snapshot status - /// status + /// status ///
internal void RequestContinue(bool value) { @@ -4862,7 +4858,7 @@ partial void SetDebugDataHashImpl() { Hash = null; } - + } partial void CheckDebugDataHashImpl() diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/AsyncHelper.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/AsyncHelper.cs new file mode 100644 index 0000000000..9ce1bf336f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/AsyncHelper.cs @@ -0,0 +1,871 @@ +// 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.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient.Internal; + +#nullable enable + +namespace Microsoft.Data.SqlClient.Utilities +{ + /// + /// Provides helpers for interacting with asynchronous tasks. + /// + /// + /// These helpers mainly provide continuation and timeout functionality. They utilize + /// at their core, and as such are fairly antiquated + /// implementations. If possible these methods should be utilized less and async/await native + /// constructs should be used. + /// + internal static class AsyncHelper + { + /// + /// Continues a task and signals failure of the continuation via the provided + /// . + /// + /// + /// If the completes in the following states, these + /// actions will be taken: + /// * With exception + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * Will try to set exception on + /// * Cancelled + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * Will try to set cancelled on + /// * Successfully + /// * is called + /// * IF an exception is thrown during execution of , the + /// helper will try to set an exception on the . + /// * is *not* completed with result on success. + /// This is to allow the task completion source to be continued even more after this + /// current continuation. + /// + /// Task to continue with provided callbacks + /// + /// Completion source used to track completion of the continuation, see remarks for details + /// + /// Callback to execute on successful completion of the task + /// Callback to execute on failure of the task (optional) + /// Callback to execute on cancellation of the task (optional) + internal static void ContinueTask( + Task taskToContinue, + TaskCompletionSource taskCompletionSource, + Action onSuccess, + Action? onFailure = null, + Action? onCancellation = null) + { + ContinuationState continuationState = new ContinuationState( + OnCancellation: onCancellation, + OnFailure: onFailure, + OnSuccess: onSuccess, + TaskCompletionSource: taskCompletionSource); + + Task continuationTask = taskToContinue.ContinueWith( + static (tsk, continuationState2) => + { + ContinuationState typedState = (ContinuationState)continuationState2!; + + if (tsk.Exception != null) + { + Exception innerException = tsk.Exception.InnerException ?? tsk.Exception; + try + { + typedState.OnFailure?.Invoke(innerException); + } + finally + { + typedState.TaskCompletionSource.TrySetException(innerException); + } + } + else if (tsk.IsCanceled) + { + try + { + typedState.OnCancellation?.Invoke(); + } + finally + { + typedState.TaskCompletionSource.TrySetCanceled(); + } + } + else + { + try + { + typedState.OnSuccess(); + } + // @TODO: CER Exception Handling was removed here (see GH#3581) + catch (Exception e) + { + typedState.TaskCompletionSource.TrySetException(e); + } + } + }, + state: continuationState, + scheduler: TaskScheduler.Default); + + // Explicitly follow up by observing any exception thrown during continuation + ObserveContinuationException(continuationTask); + } + + /// + /// Continues a task and signals failure of the continuation via the provided + /// . This overload provides a single state object + /// to the callbacks. + /// + /// + /// When possible, use static lambdas for callbacks. + /// + /// If the completes in the following states, these + /// actions will be taken: + /// * With exception + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * Will try to set exception on + /// * Cancelled + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * Will try to set cancelled on + /// * Successfully + /// * is called + /// * IF an exception is thrown during execution of , the + /// helper will try to set an exception on the . + /// * is *not* completed with result on success. + /// This is to allow the task completion source to be continued even more after this + /// current continuation. + /// + /// + /// Type of the state object to provide to the callbacks, constrained to class types to + /// prevent accidental modification of pass-by-value types. + /// + /// Task to continue with provided callbacks + /// + /// Completion source used to track completion of the continuation, see remarks for details + /// + /// State object to provide to callbacks + /// Callback to execute on successful completion of the task + /// Callback to execute on failure of the task (optional) + /// Callback to execute on cancellation of the task (optional) + internal static void ContinueTaskWithState( + Task taskToContinue, + TaskCompletionSource taskCompletionSource, + TState state, + Action onSuccess, + Action? onFailure = null, + Action? onCancellation = null) + where TState : class + { + ContinuationState continuationState = new( + OnCancellation: onCancellation, + OnFailure: onFailure, + OnSuccess: onSuccess, + State: state, + TaskCompletionSource: taskCompletionSource); + + Task continuationTask = taskToContinue.ContinueWith( + static (task, continuationState2) => + { + ContinuationState typedState2 = (ContinuationState)continuationState2!; + + if (task.Exception is not null) + { + Exception innerException = task.Exception.InnerException ?? task.Exception; + try + { + typedState2.OnFailure?.Invoke(typedState2.State, innerException); + } + finally + { + typedState2.TaskCompletionSource.TrySetException(innerException); + } + } + else if (task.IsCanceled) + { + try + { + typedState2.OnCancellation?.Invoke(typedState2.State); + } + finally + { + typedState2.TaskCompletionSource.TrySetCanceled(); + } + } + else + { + try + { + typedState2.OnSuccess(typedState2.State); + // @TODO: The one unpleasant thing with this code is that the TCS is not set completed and left to the caller to do or not do (which is more unpleasant) + } + catch (Exception e) + { + typedState2.TaskCompletionSource.TrySetException(e); + } + } + }, + state: continuationState, + scheduler: TaskScheduler.Default); + + // Explicitly follow up by observing any exception thrown during continuation + ObserveContinuationException(continuationTask); + } + + /// + /// Continues a task and signals failure of the continuation via the provided + /// . This overload provides two state objects to + /// the callbacks. + /// + /// + /// When possible, use static lambdas for callbacks. + /// + /// If the completes in the following states, these + /// actions will be taken: + /// * With exception + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * Will try to set exception on + /// * Cancelled + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * Will try to set cancelled on + /// * Successfully + /// * is called + /// * IF an exception is thrown during execution of , the + /// helper will try to set an exception on the . + /// * is *not* completed with result on success. + /// This is to allow the task completion source to be continued even more after this + /// current continuation. + /// + /// Task to continue with provided callbacks + /// + /// Completion source used to track completion of the continuation, see remarks for details + /// + /// + /// Type of the first state object to provide to the callbacks, constrained to class types + /// to prevent accidental modification of pass-by-value types. + /// + /// + /// Type of the second state object to provide to the callbacks, constrained to class types + /// to prevent accidental modification of pass-by-value types. + /// + /// First state object to provide to callbacks + /// Second state object to provide to callbacks + /// Callback to execute on successful completion of the task + /// Callback to execute on failure of the task (optional) + /// Callback to execute on cancellation of the task (optional) + internal static void ContinueTaskWithState( + Task taskToContinue, + TaskCompletionSource taskCompletionSource, + TState1 state1, + TState2 state2, + Action onSuccess, + Action? onFailure = null, + Action? onCancellation = null) + where TState1 : class + where TState2 : class + { + ContinuationState continuationState = new( + OnCancellation: onCancellation, + OnFailure: onFailure, + OnSuccess: onSuccess, + State1: state1, + State2: state2, + TaskCompletionSource: taskCompletionSource); + + Task continuationTask = taskToContinue.ContinueWith( + static (task, continuationState2) => + { + ContinuationState typedState2 = + (ContinuationState)continuationState2!; + + if (task.Exception is not null) + { + Exception innerException = task.Exception.InnerException ?? task.Exception; + try + { + typedState2.OnFailure?.Invoke(typedState2.State1, typedState2.State2, innerException); + } + finally + { + typedState2.TaskCompletionSource.TrySetException(innerException); + } + } + else if (task.IsCanceled) + { + try + { + typedState2.OnCancellation?.Invoke(typedState2.State1, typedState2.State2); + } + finally + { + typedState2.TaskCompletionSource.TrySetCanceled(); + } + } + else + { + try + { + typedState2.OnSuccess(typedState2.State1, typedState2.State2); + } + catch (Exception e) + { + typedState2.TaskCompletionSource.TrySetException(e); + } + } + }, + state: continuationState, + scheduler: TaskScheduler.Default); + + // Explicitly follow up by observing any exception thrown during continuation + ObserveContinuationException(continuationTask); + } + + /// + /// Continues a task and returns the continuation task. + /// + /// + /// When possible, use static lambdas for callbacks. + /// + /// If the completes in the following states, these + /// actions will be taken: + /// * With exception + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * The task will be completed with an exception. + /// * Cancelled + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * The task will be completed as cancelled. + /// * Successfully + /// * is called + /// * IF an exception is thrown during execution of , the + /// task will be completed with the exception. + /// * The task will be completed as successful. + /// + /// + /// Task to continue with provided callbacks, if null, null will be returned. + /// + /// Callback to execute on successful completion of the task + /// Callback to execute on failure of the task (optional) + /// Callback to execute on cancellation of the task (optional) + internal static Task? CreateContinuationTask( + Task? taskToContinue, + Action onSuccess, + Action? onFailure = null, + Action? onCancellation = null) + { + if (taskToContinue is null) + { + // This is a remnant of ye olde async/sync code that return null tasks when + // executing synchronously. It's still desirable that the onSuccess executes + // regardless of whether the preceding action was synchronous or asynchronous. + onSuccess(); + return null; + } + + // @TODO: Can totally use a non-generic TaskCompletionSource + TaskCompletionSource taskCompletionSource = new(); + ContinuationState continuationState = new( + OnCancellation: onCancellation, + OnFailure: onFailure, + OnSuccess: onSuccess, + TaskCompletionSource: taskCompletionSource); + + Task continuationTask = taskToContinue.ContinueWith( + static (task, continuationState2) => + { + ContinuationState typedState = (ContinuationState)continuationState2!; + if (task.Exception is not null) + { + Exception innerException = task.Exception.InnerException ?? task.Exception; + try + { + typedState.OnFailure?.Invoke(innerException); + } + finally + { + typedState.TaskCompletionSource.TrySetException(innerException); + } + } + else if (task.IsCanceled) + { + try + { + typedState.OnCancellation?.Invoke(); + } + finally + { + typedState.TaskCompletionSource.TrySetCanceled(); + } + } + else + { + try + { + typedState.OnSuccess(); + typedState.TaskCompletionSource.TrySetResult(null); + } + catch (Exception e) + { + typedState.TaskCompletionSource.TrySetException(e); + } + } + }, + state: continuationState, + scheduler: TaskScheduler.Default); + + // Explicitly follow up by observing any exception thrown during continuation + ObserveContinuationException(continuationTask); + + return taskCompletionSource.Task; + } + + /// + /// Continues a task and returns the continuation task. This overload allows a state object + /// to be passed into the callbacks. + /// + /// + /// When possible, use static lambdas for callbacks. + /// + /// If the completes in the following states, these + /// actions will be taken: + /// * With exception + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * The task will be completed with an exception. + /// * Cancelled + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * The task will be completed as cancelled. + /// * Successfully + /// * is called + /// * IF an exception is thrown during execution of , the + /// task will be completed with the exception. + /// * The task will be completed as successful. + /// + /// + /// Type of the state object to pass to callbacks, constrained to class types to prevent + /// accidental modification of pass-by-value types. + /// + /// + /// Task to continue with provided callbacks, if null, null will be returned. + /// + /// State object to pass to the callbacks + /// Callback to execute on successful completion of the task + /// Callback to execute on failure of the task (optional) + /// Callback to execute on cancellation of the task (optional) + internal static Task? CreateContinuationTaskWithState( + Task? taskToContinue, + TState state, + Action onSuccess, + Action? onFailure = null, + Action? onCancellation = null) + where TState : class + { + if (taskToContinue is null) + { + // This is a remnant of ye olde async/sync code that return null tasks when + // executing synchronously. It's still desirable that the onSuccess executes + // regardless of whether the preceding action was synchronous or asynchronous. + onSuccess(state); + return null; + } + + // @TODO: Can totally use a non-generic TaskCompletionSource + TaskCompletionSource taskCompletionSource = new(); + ContinuationState continuationState = new( + OnCancellation: onCancellation, + OnFailure: onFailure, + OnSuccess: onSuccess, + State: state, + TaskCompletionSource: taskCompletionSource); + + Task continuationTask = taskToContinue.ContinueWith( + static (task, continuationState2) => + { + ContinuationState typedState2 = (ContinuationState)continuationState2!; + + if (task.Exception is not null) + { + Exception innerException = task.Exception.InnerException ?? task.Exception; + try + { + typedState2.OnFailure?.Invoke(typedState2.State, innerException); + } + finally + { + typedState2.TaskCompletionSource.TrySetException(innerException); + } + } + else if (task.IsCanceled) + { + try + { + typedState2.OnCancellation?.Invoke(typedState2.State); + } + finally + { + typedState2.TaskCompletionSource.TrySetCanceled(); + } + } + else + { + try + { + typedState2.OnSuccess(typedState2.State); + typedState2.TaskCompletionSource.TrySetResult(null); + } + catch (Exception e) + { + typedState2.TaskCompletionSource.TrySetException(e); + } + } + + }, + state: continuationState, + scheduler: TaskScheduler.Default); + + // Explicitly follow up by observing any exception thrown during continuation + ObserveContinuationException(continuationTask); + + return taskCompletionSource.Task; + } + + /// + /// Continues a task and returns the continuation task. This overload allows two state + /// objects to be passed into the callbacks. + /// + /// + /// When possible, use static lambdas for callbacks. + /// + /// If the completes in the following states, these + /// actions will be taken: + /// * With exception + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * The task will be completed with an exception. + /// * Cancelled + /// * is called (if provided) + /// * Any exception thrown by will be logged and + /// swallowed as unobserved. + /// * The task will be completed as cancelled. + /// * Successfully + /// * is called + /// * IF an exception is thrown during execution of , the + /// task will be completed with the exception. + /// * The task will be completed as successful. + /// + /// + /// Type of the first state object to pass to callbacks, constrained to class types to + /// prevent accidental modification of pass-by-value types. + /// + /// + /// Type of the second state object to pass to callbacks, constrained to class types to + /// prevent accidental modification of pass-by-value types. + /// + /// Task to continue with provided callbacks, if null, null will be returned. + /// + /// First state object to pass to the callbacks + /// Second state object to pass to the callbacks + /// Callback to execute on successful completion of the task + /// Callback to execute on failure of the task (optional) + /// Callback to execute on cancellation of the task (optional) + internal static Task? CreateContinuationTaskWithState( + Task? taskToContinue, + TState1 state1, + TState2 state2, + Action onSuccess, + Action? onFailure = null, + Action? onCancellation = null) + where TState1 : class + where TState2 : class + { + if (taskToContinue is null) + { + // This is a remnant of ye olde async/sync code that return null tasks when + // executing synchronously. It's still desirable that the onSuccess executes + // regardless of whether the preceding action was synchronous or asynchronous. + onSuccess(state1, state2); + return null; + } + + // @TODO: Can totally use a non-generic TaskCompletionSource + TaskCompletionSource taskCompletionSource = new(); + ContinuationState continuationState = new( + OnCancellation: onCancellation, + OnFailure: onFailure, + OnSuccess: onSuccess, + State1: state1, + State2: state2, + TaskCompletionSource: taskCompletionSource); + + Task continuationTask = taskToContinue.ContinueWith( + static (task, continuationState2) => + { + ContinuationState typedState2 = + (ContinuationState)continuationState2!; + + if (task.Exception is not null) + { + Exception innerException = task.Exception.InnerException ?? task.Exception; + try + { + typedState2.OnFailure?.Invoke(typedState2.State1, typedState2.State2, innerException); + } + finally + { + typedState2.TaskCompletionSource.TrySetException(innerException); + } + } + else if (task.IsCanceled) + { + try + { + typedState2.OnCancellation?.Invoke(typedState2.State1, typedState2.State2); + } + finally + { + typedState2.TaskCompletionSource.TrySetCanceled(); + } + } + else + { + try + { + typedState2.OnSuccess(typedState2.State1, typedState2.State2); + typedState2.TaskCompletionSource.TrySetResult(null); + } + catch (Exception e) + { + typedState2.TaskCompletionSource.TrySetException(e); + } + } + + }, + state: continuationState, + scheduler: TaskScheduler.Default); + + // Explicitly follow up by observing any exception thrown during continuation + ObserveContinuationException(continuationTask); + + return taskCompletionSource.Task; + } + + /// + /// Executes a timeout task in parallel with the provided + /// . If the timeout completes before the task + /// completion source, the provided is executed and the + /// exception returned is set as the exception that completes the task completion source. + /// + /// Task to execute with a timeout + /// + /// Number of seconds to wait until timing out the task. If set to <=0, the method will + /// immediately return without calling or setting any state on + /// the . + /// + /// + /// Callback to execute when the task does not complete within the allotted time. The + /// exception returned by the callback is set on the . + /// + /// Cancellation token to prematurely cancel timeout + internal static void SetTimeoutException( + TaskCompletionSource taskCompletionSource, + int timeoutInSeconds, + Func onTimeout, + CancellationToken cancellationToken) + { + if (timeoutInSeconds <= 0) + { + return; + } + + Task.Delay(TimeSpan.FromSeconds(timeoutInSeconds), cancellationToken) + .ContinueWith( + task => + { + // If the timeout ran to completion AND the task to complete did not complete + // then the timeout expired first, run the timeout handler + if (!task.IsCanceled && !taskCompletionSource.Task.IsCompleted) + { + taskCompletionSource.TrySetException(onTimeout()); + } + }, + cancellationToken: CancellationToken.None, + continuationOptions: TaskContinuationOptions.ExecuteSynchronously, + scheduler: TaskScheduler.Default); + } + + /// + /// Executes a timeout task in parallel with the provided + /// . If the timeout completes before the task + /// completion source, the provided is executed and the + /// exception returned is set as the exception that completes the task completion source. + /// This overload provides a state object to the timeout callback. + /// + /// + /// Type of the state object to pass to callbacks, constrained to class types to prevent + /// accidental modification of pass-by-value types. + /// + /// Task to execute with a timeout + /// + /// Number of seconds to wait until timing out the task. If set to <=0, the method will + /// immediately return without calling or setting any state on + /// the . + /// + /// State object to pass to the callback + /// + /// Callback to execute when the task does not complete within the allotted time. The + /// exception returned by the callback is set on the . + /// + /// Cancellation token to prematurely cancel timeout + internal static void SetTimeoutExceptionWithState( + TaskCompletionSource taskCompletionSource, + int timeoutInSeconds, + TState state, + Func onTimeout, + CancellationToken cancellationToken) + where TState : class + { + if (timeoutInSeconds <= 0) + { + return; + } + + Task.Delay(TimeSpan.FromSeconds(timeoutInSeconds), cancellationToken) + .ContinueWith( + (task, state2) => + { + // If the timeout ran to completion AND the task to complete did not + // complete, then the timeout expired first, run the timeout handler. + if (!task.IsCanceled && !taskCompletionSource.Task.IsCompleted) + { + taskCompletionSource.TrySetException(onTimeout((TState)state2!)); + } + }, + state: state, + cancellationToken: CancellationToken.None, + continuationOptions: TaskContinuationOptions.ExecuteSynchronously, + scheduler: TaskScheduler.Default); + } + + /// + /// Waits for a maximum of seconds for completion of + /// the provided . If the task is not completed before the timeout + /// elapses, a continuation is added to the task that observes any throw exceptions. The + /// callback is called after attaching the exception + /// observing exception. + /// + /// Task to execute with a timeout + /// + /// Number of seconds to wait until timing out the task. If <= 0, the timeout will be + /// infinite. + /// + /// + /// Callback to execute when the task does not complete within the allotted time. + /// + /// + /// If true, the inner exception of any raised + /// during execution, including timeout of the task, will be rethrown. + /// + internal static void WaitForCompletion( + Task task, + int timeoutInSeconds, + Action? onTimeout = null, + bool rethrowExceptions = true) + { + try + { + TimeSpan timeout = timeoutInSeconds > 0 + ? TimeSpan.FromSeconds(timeoutInSeconds) + : Timeout.InfiniteTimeSpan; + task.Wait(timeout); + } + catch (AggregateException ae) + { + if (rethrowExceptions) + { + Debug.Assert(ae.InnerException is not null, "Inner exception is null"); + Debug.Assert(ae.InnerExceptions.Count == 1, "There is more than one exception in AggregateException"); + ExceptionDispatchInfo.Capture(ae.InnerException).Throw(); + } + } + + if (!task.IsCompleted) + { + ObserveContinuationException(task); + onTimeout?.Invoke(); + } + } + + /// + /// This method is intended to be used within the above helpers to ensure that any + /// exceptions thrown during callbacks do not go unobserved. If these exceptions were + /// to go unobserved, they will trigger events to be raised by the default task scheduler. + /// Neither situation is ideal: + /// * If an application assigns a listener to this event, it will generate events that + /// should be reported to us. But, because it happens outside the stack that caused the + /// exception, most of the context of the exception is lost. Furthermore, the event is + /// triggered when the GC runs, so the event happens asynchronous to the action that + /// caused it. + /// * Adding this forced observation of the exception prevents applications from receiving + /// the event, effectively swallowing it. + /// * However, if we log the exception when we observe it, we can still log that the + /// unobserved exception happened without causing undue disruption to the application + /// or leaking resources and causing overhead by raising the event. + /// + private static void ObserveContinuationException(Task continuationTask) + { + continuationTask.ContinueWith( + static task => + { + SqlClientEventSource.Log.TryTraceEvent($"Unobserved task exception: {task.Exception}"); + _ = task.Exception; + }, + CancellationToken.None, + TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + + private record ContinuationState( + Action? OnCancellation, + Action? OnFailure, + Action OnSuccess, + TaskCompletionSource TaskCompletionSource); + + private record ContinuationState( + Action? OnCancellation, + Action? OnFailure, + Action OnSuccess, + TState State, + TaskCompletionSource TaskCompletionSource) + where TState : class; + + private record ContinuationState( + Action? OnCancellation, + Action? OnFailure, + Action OnSuccess, + TState1 State1, + TState2 State2, + TaskCompletionSource TaskCompletionSource) + where TState1 : class + where TState2 : class; + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props index 7925952d11..4f3dbacb21 100644 --- a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props @@ -6,6 +6,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlHelperTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlHelperTest.cs deleted file mode 100644 index 44286b8c0e..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlHelperTest.cs +++ /dev/null @@ -1,62 +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 System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.Data.SqlClient.Tests -{ - public class SqlHelperTest - { - private void TimeOutATask() - { - var sqlClientAssembly = Assembly.GetAssembly(typeof(SqlCommand)); - //We're using reflection to avoid exposing the internals - MethodInfo waitForCompletion = sqlClientAssembly.GetType("Microsoft.Data.SqlClient.AsyncHelper") - ?.GetMethod("WaitForCompletion", BindingFlags.Static | BindingFlags.NonPublic); - - Assert.False(waitForCompletion == null, "Running a test on SqlUtil.WaitForCompletion but could not find this method"); - TaskCompletionSource tcs = new TaskCompletionSource(); - waitForCompletion.Invoke(null, new object[] { tcs.Task, 1, null, true }); //Will time out as task uncompleted - tcs.SetException(new TimeoutException("Dummy timeout exception")); //Our task now completes with an error - } - - private Exception UnwrapException(Exception e) - { - return e?.InnerException != null ? UnwrapException(e.InnerException) : e; - } - - [Fact] - public void WaitForCompletion_DoesNotCreateUnobservedException() - { - var unobservedExceptionHappenedEvent = new AutoResetEvent(false); - Exception unhandledException = null; - void handleUnobservedException(object o, UnobservedTaskExceptionEventArgs a) - { unhandledException = a.Exception; unobservedExceptionHappenedEvent.Set(); } - - TaskScheduler.UnobservedTaskException += handleUnobservedException; - - try - { - TimeOutATask(); //Create the task in another function so the task has no reference remaining - GC.Collect(); //Force collection of unobserved task - GC.WaitForPendingFinalizers(); - - bool unobservedExceptionHappend = unobservedExceptionHappenedEvent.WaitOne(1); - if (unobservedExceptionHappend) //Save doing string interpolation in the happy case - { - var e = UnwrapException(unhandledException); - Assert.Fail($"Did not expect an unobserved exception, but found a {e?.GetType()} with message \"{e?.Message}\""); - } - } - finally - { - TaskScheduler.UnobservedTaskException -= handleUnobservedException; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index e09e49c967..12ea9c52d3 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -98,6 +98,7 @@ --> + @@ -115,6 +116,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Utilities/AsyncHelperTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Utilities/AsyncHelperTest.cs new file mode 100644 index 0000000000..8f1529f8ef --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Utilities/AsyncHelperTest.cs @@ -0,0 +1,1814 @@ +// 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.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient.UnitTests.Utilities; +using Microsoft.Data.SqlClient.Utilities; +using Moq; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.Microsoft.Data.SqlClient.Utilities +{ + public class AsyncHelperTest + { + // This timeout is set fairly high. The tests are expected to complete quickly, but are + // dependent on congestion of the thread pool. If the thread pool is congested, like on a + // full CI run, short timeouts may elapse even if the code under test would behave as + // expected. As such, we set a long timeout to ride out reasonable congestion on the + // thread pool, but still trigger a failure if the code under test hangs. + // @TODO: If suite-level timeouts are added, these timeouts can likely be removed. + private static readonly TimeSpan RunTimeout = TimeSpan.FromSeconds(30); + + #region ContinueTask + + /// + /// Verifies that the asynchronous continuation process is correctly handled when the task + /// completes successfully. Ensures that the success handler is invoked exactly once, and + /// neither failure nor cancellation handlers are invoked. + /// + [Fact] + public async Task ContinueTask_TaskCompletes() + { + // Arrange + // - Task to continue that completed successfully + Task taskToContinue = Task.CompletedTask; + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + Mock> mockOnFailure = new(); + Mock mockOnCancellation = new(); + + // Note: We have to set up mockOnSuccess to set a result on the task completion source, + // since the AsyncHelper will not do it, and without that, we cannot reliably + // know when the continuation completed. We will use SetResult b/c it will throw + // if it has already been set. + Mock mockOnSuccess = new(); + mockOnSuccess.Setup(action => action()) + .Callback(() => taskCompletionSource.SetResult(0)); + + // Act + AsyncHelper.ContinueTask( + taskToContinue: taskToContinue, + taskCompletionSource: taskCompletionSource, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + mockOnSuccess.Verify(action => action(), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + + /// + /// Validates that the continuation process correctly handles cases when the success handler + /// throws an exception. Ensures the task is marked as faulted and the failure and + /// cancellation handlers are not invoked. + /// + [Fact] + public async Task ContinueTask_TaskCompletesHandlerThrows() + { + // Arrange + // - Task to continue that completed successfully + Task taskToContinue = Task.CompletedTask; + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + + // - mockOnSuccess handler throws + Mock mockOnSuccess = new(); + mockOnSuccess.SetupThrows(); + + Mock> mockOnFailure = new(); + Mock mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTask( + taskToContinue, + taskCompletionSource, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + mockOnSuccess.Verify(action => action(), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + + /// + /// Verifies that the asynchronous continuation process is correctly handled when the task + /// is cancelled. Ensures the cancellation handler is invoked exactly once, while neither + /// the success nor failure handlers are invoked. Validates that the resulting task is + /// marked as cancelled. + /// + /// + /// Indicates whether the cancellation handler should throw an exception during its + /// execution to validate that cancellation is properly reported, regardless of handler + /// failures. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ContinueTask_TaskCancels(bool handlerShouldThrow) + { + // Arrange + // - Task to continue that is cancelled + Task taskToContinue = GetCancelledTask(); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + + Mock mockOnCancellation = new(); + if (handlerShouldThrow) + { + mockOnCancellation.SetupThrows(); + } + + Mock mockOnSuccess = new(); + Mock> mockOnFailure = new(); + + // Act + AsyncHelper.ContinueTask( + taskToContinue, + taskCompletionSource, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have been cancelled, regardless of mockOnCancellation throwing + Assert.Equal(TaskStatus.Canceled, taskCompletionSource.Task.Status); + + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.Verify(action => action(), Times.Once); + } + + /// + /// Verifies the behavior of the asynchronous continuation process when the task to continue + /// is cancelled, and no specific cancellation handler is provided. Ensures that the task is + /// marked as cancelled without invoking the success or failure handlers. + /// + [Fact] + public async Task ContinueTask_TaskCancelsNoHandler() + { + // Arrange + // - Task to continue that is cancelled + Task taskToContinue = GetCancelledTask(); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + Mock mockOnSuccess = new(); + Mock> mockOnFailure = new(); + + // Act + AsyncHelper.ContinueTask( + taskToContinue, + taskCompletionSource, + mockOnSuccess.Object, + mockOnFailure.Object, + onCancellation: null); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have been cancelled + Assert.Equal(TaskStatus.Canceled, taskCompletionSource.Task.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + } + + /// + /// Tests the behavior of the asynchronous continuation process when the initial task is + /// faulted. Verifies that the fault handler is invoked exactly once and neither success nor + /// cancellation handlers are called. Ensures that the continuation task transitions to a + /// faulted state. + /// + /// + /// Indicates whether the fault handler itself is expected to throw an exception during + /// execution. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ContinueTask_TaskFaults(bool handlerShouldThrow) + { + // Arrange + // - Task to continue that is faulted + Task taskToContinue = Task.FromException(new Exception()); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + + Mock> mockOnFailure = new(); + if (handlerShouldThrow) + { + mockOnFailure.SetupThrows(); + } + + Mock mockOnSuccess = new(); + Mock mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTask( + taskToContinue, + taskCompletionSource, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have faulted, regardless of mockOnFailure throwing + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.Verify(action => action(It.IsAny()), Times.Once); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies that when a task to continue faults and no fault handler is provided, the + /// proper behavior is executed. Ensures that the TaskCompletionSource transitions to a + /// faulted state and neither success nor cancellation handlers are invoked. Validates that + /// the absence of a fault handler does not disrupt the flow or produce unexpected side + /// effects. + /// + [Fact] + public async Task ContinueTask_TaskFaultsNoHandler() + { + // Arrange + // - Task to continue that is faulted + Task taskToContinue = Task.FromException(new Exception()); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + Mock mockOnSuccess = new(); + Mock mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTask( + taskToContinue, + taskCompletionSource, + mockOnSuccess.Object, + onFailure: null, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have faulted + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + + mockOnSuccess.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies that an unobserved exception is not created when continuing a cancelled task + /// using the AsyncHelper. Ensures proper handling of task continuation in scenarios where + /// cancellation occurs and the provided cancellation handler is utilized correctly. + /// + [Fact] + public async Task ContinueTask_DoesNotCreateUnobservedException() + { + await VerifyDoesNotCreateUnobservedException(async e => + { + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + + AsyncHelper.ContinueTask( + GetCancelledTask(), + taskCompletionSource, + onSuccess: static () => { }, + onCancellation: () => throw e); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + }); + } + + #endregion + + #region ContinueTaskWithState + + /// + /// Verifies that a continuation process is correctly executed when the associated task + /// completes successfully. Ensures that the success handler is invoked once, while failure + /// and cancellation handlers are not invoked. Also verifies synchronization and proper + /// state propagation between the task and continuation handlers. + /// + [Fact] + public async Task ContinueTaskWithState_1Generic_TaskCompletes() + { + // Arrange + // - Task to continue that completed successfully + Task taskToContinue = Task.CompletedTask; + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // Note: We have to set up mockOnSuccess to set a result on the task completion source, + // since the AsyncHelper will not do it, and without that, we cannot reliably + // know when the continuation completed. We will use SetResult b/c it will throw + // if it has already been set. + Mock> mockOnSuccess = new(); + mockOnSuccess.Setup(action => action(state1)) + .Callback(_ => taskCompletionSource.SetResult(0)); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue: taskToContinue, + taskCompletionSource: taskCompletionSource, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + mockOnSuccess.Verify(action => action(state1), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Tests the behavior of the asynchronous continuation method when the task completes + /// successfully but the success handler throws an exception. Verifies that the + /// TaskCompletionSource is properly faulted, the success handler is invoked exactly once + /// with the provided state, and neither the failure nor cancellation handlers are invoked. + /// + [Fact] + public async Task ContinueTaskWithState_1Generic_TaskCompletesHandlerThrows() + { + // Arrange + // - Task to continue that completed successfully + Task taskToContinue = Task.CompletedTask; + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + + // - mockOnSuccess handler throws + Mock> mockOnSuccess = new(); + mockOnSuccess.Setup(action => action(It.IsAny())).Throws(); + + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have faulted + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + + mockOnSuccess.Verify(action => action(state1), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies the behavior of the asynchronous continuation process when a task is cancelled + /// and includes a state object. Ensures that the appropriate cancellation handler is + /// invoked exactly once, while success and failure handlers are not. Also validates that + /// the task completion source is correctly transitioned to the cancelled state, regardless + /// of whether the cancellation handler throws an exception or not. + /// + /// + /// A boolean value indicating whether the cancellation handler is expected to throw an + /// exception. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ContinueTaskWithState_1Generic_TaskCancels(bool handlerShouldThrow) + { + // Arrange + // - Task to continue that was cancelled + Task taskToContinue = GetCancelledTask(); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + + Mock> mockOnCancellation = new(); + if (handlerShouldThrow) + { + mockOnCancellation.SetupThrows(); + } + + Mock> mockOnSuccess = new(); + Mock> mockOnFailure = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have been cancelled, regardless of mockOnCancellation throwing + Assert.Equal(TaskStatus.Canceled, taskCompletionSource.Task.Status); + + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.Verify(action => action(state1), Times.Once); + } + + /// + /// Ensures that when a task with an attached state is cancelled, no success, failure, or + /// cancellation handlers are invoked. Verifies that the associated TaskCompletionSource is + /// properly cancelled to reflect the task's state. This test method checks behavior + /// specifically when no cancellation handler is provided. + /// + [Fact] + public async Task ContinueTaskWithState_1Generic_TaskCancelsNoHandler() + { + // Arrange + // - Task to continue that was cancelled + Task taskToContinue = GetCancelledTask(); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnFailure = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + onCancellation: null); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have been cancelled + Assert.Equal(TaskStatus.Canceled, taskCompletionSource.Task.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + } + + /// + /// Verifies that when a task with a generic state parameter faults, the fault handler is + /// invoked correctly. Validates that the state is passed to the handler and that other + /// handlers, such as success and cancellation handlers, are not triggered. Additionally, + /// ensures the final task transitions to a faulted state as expected. + /// + /// + /// A boolean flag indicating whether the fault handler should throw an exception when + /// invoked. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ContinueTaskWithState_1Generic_TaskFaults(bool handlerShouldThrow) + { + // Arrange + // - Task to continue that faulted + Task taskToContinue = Task.FromException(new Exception()); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + + Mock> mockOnFailure = new(); + if (handlerShouldThrow) + { + mockOnFailure.SetupThrows(); + } + + Mock> mockOnSuccess = new(); + Mock> mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have faulted, regardless of mockOnFailure throwing + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.Verify(action => action(state1, It.IsAny()), Times.Once); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Ensures a faulted task is handled properly when no failure handler is provided. + /// Validates that the task's fault state is correctly passed to the continuation process, + /// and no unintended success or cancellation handlers are invoked. This test verifies that + /// the task completion source reflects the faulted status as expected, promoting reliable + /// behavior in fault scenarios. + /// + [Fact] + public async Task ContinueTaskWithState_1Generic_TaskFaultsNoHandler() + { + // Arrange + // - Task to continue that faulted + Task taskToContinue = Task.FromException(new Exception()); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + mockOnSuccess.Object, + onFailure: null, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have faulted + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Confirms that the generic task continuation mechanism, with state, does not result in + /// any unobserved exceptions. Verifies that the continuation invocation handles all + /// outcomes, including cancellation, without allowing exceptions to remain unobserved or + /// propagate outside the test scope. + /// + [Fact] + public async Task ContinueTaskWithState_1Generic_DoesNotCreateUnobservedException() + { + await VerifyDoesNotCreateUnobservedException(async e => + { + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + + AsyncHelper.ContinueTaskWithState( + GetCancelledTask(), + taskCompletionSource, + new object(), + onSuccess: static _ => { }, + onCancellation: _ => throw e); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + }); + } + + #endregion + + #region ContinueTaskWithState + + /// + /// Validates that the continuation of a task, which completed successfully, is handled + /// appropriately when state information and generic types are involved. Ensures that the + /// success delegate is triggered exactly once while both failure and cancellation handlers + /// are never invoked. + /// + [Fact] + public async Task ContinueTaskWithState_2Generics_TaskCompletes() + { + // Arrange + // - Task to continue that completed successfully + Task taskToContinue = Task.CompletedTask; + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // Note: We have to set up mockOnSuccess to set a result on the task completion source, + // since the AsyncHelper will not do it, and without that, we cannot reliably + // know when the continuation completed. We will use SetResult b/c it will throw + // if it has already been set. + Mock> mockOnSuccess = new(); + mockOnSuccess.Setup(action => action(state1, state2)) + .Callback((_, _) => taskCompletionSource.SetResult(0)); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue: taskToContinue, + taskCompletionSource: taskCompletionSource, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + mockOnSuccess.Verify(action => action(state1, state2), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies the behavior of the `ContinueTaskWithState` method when a task completes + /// successfully and the success handler throws an exception. Ensures the faulted status of + /// the task completion source and that the success handler is invoked exactly once with the + /// correct state objects. Also confirms that neither the failure handler nor the + /// cancellation handler are triggered. + /// + [Fact] + public async Task ContinueTaskWithState_2Generics_TaskCompletesHandlerThrows() + { + // Arrange + // - Task to continue that completed successfully + Task taskToContinue = Task.CompletedTask; + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + object state2 = new object(); + + // - mockOnSuccess handler throws + Mock> mockOnSuccess = new(); + mockOnSuccess.Setup(o => o(It.IsAny(), It.IsAny())).Throws(); + + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have faulted + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + + // - mockOnSuccess was called with state obj + mockOnSuccess.Verify(action => action(state1, state2), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies the behavior of the ContinueTaskWithState method when the task to continue is + /// canceled. Ensures that the cancellation handler is invoked exactly once with the + /// specified state parameters, while both success and failure handlers are not called. + /// Confirms that the task completion source reflects the canceled state properly, + /// regardless of whether the cancellation handler throws an exception. + /// + /// + /// Indicates whether the cancellation handler should throw an exception during execution. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ContinueTaskWithState_2Generics_TaskCancels(bool handlerShouldThrow) + { + // Arrange + // - Task to continue that was cancelled + Task taskToContinue = GetCancelledTask(); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnCancellation = new(); + if (handlerShouldThrow) + { + mockOnCancellation.SetupThrows(); + } + + Mock> mockOnSuccess = new(); + Mock> mockOnFailure = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have been cancelled, regardless of mockOnCancellation throwing + Assert.Equal(TaskStatus.Canceled, taskCompletionSource.Task.Status); + + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.Verify(action => action(state1, state2), Times.Once); + } + + /// + /// Verifies that when a task to continue with two state objects is cancelled and no + /// cancellation handler is provided, the associated TaskCompletionSource transitions to a + /// cancelled state. Ensures that neither the success handler nor the failure handler is + /// invoked. + /// + [Fact] + public async Task ContinueTaskWithState_2Generics_TaskCancelsNoHandler() + { + // Arrange + // - Task to continue that was cancelled + Task taskToContinue = GetCancelledTask(); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnFailure = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + onCancellation: null); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have been cancelled + Assert.Equal(TaskStatus.Canceled, taskCompletionSource.Task.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + } + + /// + /// Validates that when a faulted task continues in the presence of two state objects, the + /// failure handler is invoked exactly once for the provided state objects and exception. + /// Ensures no other handlers, such as the success or cancellation handlers, are called, and + /// the task completion source transitions to a faulted state as expected. + /// + /// + /// A boolean indicating whether the failure handler should throw an exception when invoked. + /// If true, the failure handler will throw; otherwise, it will complete normally. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ContinueTaskWithState_2Generics_TaskFaults(bool handlerShouldThrow) + { + // Arrange + // - Task to continue that faulted + Task taskToContinue = Task.FromException(new Exception()); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnFailure = new(); + if (handlerShouldThrow) + { + mockOnFailure.SetupThrows(); + } + + Mock> mockOnSuccess = new(); + Mock> mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have faulted, regardless of mockOnFailure throwing + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + + mockOnSuccess.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + mockOnFailure.Verify(action => action(state1, state2, It.IsAny()), Times.Once); + } + + /// + /// Verifies that when a task to continue faults and no failure handler is provided, the + /// resulting task faults as expected. Ensures that the success and cancellation handlers + /// are never invoked, and the task continuation completes with a faulted status. + /// + [Fact] + public async Task ContinueTaskWithState_2Generics_TaskFaultsNoHandler() + { + // Arrange + // - Task to continue that faulted + Task taskToContinue = Task.FromException(new Exception()); + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnCancellation = new(); + + // Act + AsyncHelper.ContinueTaskWithState( + taskToContinue, + taskCompletionSource, + state1, + state2, + mockOnSuccess.Object, + onFailure: null, + mockOnCancellation.Object); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + + // Assert + // - taskCompletionSource should have faulted + Assert.Equal(TaskStatus.Faulted, taskCompletionSource.Task.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Ensures that a task with state and two generic parameters, when cancelled or failed, + /// does not create an unobserved exception. Verifies proper handling of unobserved task + /// exceptions through registration of cancellation or failure handlers in the continuation + /// process. + /// + [Fact] + public async Task ContinueTaskWithState_2Generics_DoesNotCreateUnobservedException() + { + await VerifyDoesNotCreateUnobservedException(async e => + { + TaskCompletionSource taskCompletionSource = GetTaskCompletionSource(); + + AsyncHelper.ContinueTaskWithState( + GetCancelledTask(), + taskCompletionSource, + new object(), + new object(), + onSuccess: static (_, _) => { }, + onCancellation: (_, _) => throw e); + await RunWithTimeout(taskCompletionSource.Task, RunTimeout); + }); + } + + #endregion + + #region CreateContinuationTask + + /// + /// Tests the behavior of the CreateContinuationTask method when the provided task to + /// continue is null. Ensures that the method returns null and invokes the onSuccess action + /// exactly once. Verifies that neither the onFailure nor the onCancellation actions are + /// called. + /// + [Fact] + public void CreateContinuationTask_NullTask() + { + // Arrange + Mock mockOnSuccess = new(); + Mock> mockOnFailure = new(); + Mock mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTask( + taskToContinue: null, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + + // Assert + Assert.Null(continuationTask); + + mockOnSuccess.Verify(action => action(), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Validates that a continuation task is successfully created and completed when the + /// initial task finishes execution without any exceptions. Ensures the success handler is + /// invoked exactly once, while neither the failure handler nor the cancellation handler are + /// triggered during the process. + /// + [Fact] + public async Task CreateContinuationTask_TaskCompletes() + { + // Arrange + // - Task to continue completed successfully + Task taskToContinue = Task.CompletedTask; + Mock mockOnSuccess = new(); + Mock> mockOnFailure = new(); + Mock mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTask( + taskToContinue, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.RanToCompletion, continuationTask.Status); + + mockOnSuccess.Verify(action => action(), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Ensures that a continuation task invokes the appropriate handlers when the success + /// handler throws after the primary task completes successfully. Verifies that the + /// continuation task transitions to a faulted state and only the success handler is invoked + /// once, while the failure and cancellation handlers are never called. + /// + [Fact] + public async Task CreateContinuationTask_TaskCompletesHandlerThrows() + { + // Arrange + // - Task to continue completed successfully + Task taskToContinue = Task.CompletedTask; + Mock> mockOnFailure = new(); + Mock mockOnCancellation = new(); + + // - mockOnSuccess handler throws + Mock mockOnSuccess = new(); + mockOnSuccess.SetupThrows(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTask( + taskToContinue, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.Verify(action => action(), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Tests that a continuation task correctly handles a cancellation of the original task. + /// Verifies that the cancellation handler is invoked exactly once while ensuring that + /// neither the success handler nor the failure handler is called. If the cancellation + /// handler is configured to throw, this behavior is also validated. + /// + /// + /// Indicates whether the cancellation handler is expected to throw an exception during + /// execution. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateContinuationTask_TaskCancels(bool handlerShouldThrow) + { + // Arrange + // - Task to continue was cancelled + Task taskToContinue = GetCancelledTask(); + Mock> mockOnFailure = new(); + Mock mockOnSuccess = new(); + + Mock mockOnCancellation = new(); + if (handlerShouldThrow) + { + mockOnCancellation.SetupThrows(); + } + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTask( + taskToContinue, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Canceled, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.Verify(action => action(), Times.Once); + } + + /// + /// Verifies that a continuation task created with a null cancellation handler properly + /// handles task cancellation. Ensures that no success or failure handlers are invoked and + /// the resulting task is marked as canceled. + /// + [Fact] + public async Task CreateContinuationTask_TaskCancelsNoHandler() + { + // Arrange + // - Task to continue was cancelled + Task taskToContinue = GetCancelledTask(); + Mock> mockOnFailure = new(); + Mock mockOnSuccess = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTask( + taskToContinue, + mockOnSuccess.Object, + mockOnFailure.Object, + onCancellation: null); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Canceled, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + } + + /// + /// Ensures that a continuation task is correctly created and handled when the original task + /// faults. Validates that the failure handler is invoked exactly once, while success and + /// cancellation handlers are never invoked. + /// + /// + /// Indicates whether the failure handler should throw an exception when invoked, testing + /// edge cases in handling throwing failures. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateContinuationTask_TaskFaults(bool handlerShouldThrow) + { + // Arrange + // - Task to continue faulted + Task taskToContinue = Task.FromException(new Exception()); + Mock mockOnSuccess = new(); + Mock mockOnCancellation = new(); + + Mock> mockOnFailure = new(); + if (handlerShouldThrow) + { + mockOnFailure.SetupThrows(); + } + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTask( + taskToContinue, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.Verify(action => action(It.IsAny()), Times.Once); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies that when a task faults and no failure or cancellation handlers are provided, + /// the continuation task is created in a faulted state. Ensures that success and + /// cancellation handlers are never invoked in this scenario. + /// + [Fact] + public async Task CreateContinuationTask_TaskFaultsNoHandler() + { + // Arrange + // - Task to continue completed successfully + Task taskToContinue = Task.FromException(new Exception()); + Mock mockOnSuccess = new(); + Mock mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTask( + taskToContinue, + mockOnSuccess.Object, + onFailure: null, + onCancellation: null); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Ensures that creating a continuation task does not result in an unobserved exception + /// being thrown, even when the preceding task is canceled. Verifies that the cancellation + /// handler, when provided, properly handles this scenario, avoiding unobserved exceptions + /// in all cases. + /// + [Fact] + public async Task CreateContinuationTask_DoesNotCreateUnobservedException() + { + await VerifyDoesNotCreateUnobservedException(async e => + { + Task? continuationTask = AsyncHelper.CreateContinuationTask( + GetCancelledTask(), + onSuccess: static () => { }, + onCancellation: () => throw e); + await RunWithTimeout(continuationTask, RunTimeout); + }); + } + + #endregion + + #region CreateContinuationTaskWithState + + /// + /// Validates the behavior of the CreateContinuationTaskWithState method when provided with + /// a null task. Ensures that the continuation task is not created, the success handler is + /// invoked exactly once with the given state, and neither the failure nor the cancellation + /// handlers are invoked. + /// + [Fact] + public void CreateContinuationTaskWithState_1Generic_NullTask() + { + // Arrange + object state1 = new object(); + Mock> mockOnSuccess = new(); + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue: null, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + + // Assert + Assert.Null(continuationTask); + + mockOnSuccess.Verify(action => action(state1), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies that the continuation task is correctly created and executed when the initial + /// task completes successfully. Ensures that the success handler is triggered exactly once, + /// while failure and cancellation handlers are not invoked, confirming their appropriate + /// exclusion in successful completion scenarios. + /// + [Fact] + public async Task CreateContinuationTaskWithState_1Generic_TaskCompletes() + { + // Arrange + // - Task to continue completed successfully + Task taskToContinue = Task.CompletedTask; + object state1 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.RanToCompletion, continuationTask.Status); + mockOnSuccess.Verify(action => action(state1), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies that a continuation task is correctly created with state when the preceding + /// task completes successfully but the success handler throws an exception. Ensures the + /// continuation task transitions to a faulted state, triggering the exception. Confirms + /// that the success handler is invoked exactly once, while neither the failure handler nor + /// the cancellation handler are invoked. + /// + [Fact] + public async Task CreateContinuationTaskWithState_1Generic_TaskCompletesHandlerThrows() + { + // Arrange + // - Task to continue completed successfully + Task taskToContinue = Task.CompletedTask; + object state1 = new object(); + + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // - mockOnSuccess handler throws + Mock> mockOnSuccess = new(); + mockOnSuccess.SetupThrows(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.Verify(action => action(state1), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Handles the creation of a continuation task with state when the task to continue is + /// canceled. Ensures the cancellation handler is invoked successfully, while success and + /// failure handlers remain uninvoked. Validates task status and the correct execution of + /// the cancellation handler. + /// + /// + /// Determines whether the cancellation handler should throw an exception during execution. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateContinuationTaskWithState_1Generic_TaskCancels(bool handlerShouldThrow) + { + // Arrange + // - Task to continue was cancelled + Task taskToContinue = GetCancelledTask(); + object state1 = new object(); + + Mock> mockOnFailure = new(); + Mock> mockOnSuccess = new(); + + Mock> mockOnCancellation = new(); + if (handlerShouldThrow) + { + mockOnCancellation.SetupThrows(); + } + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Canceled, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.Verify(action => action(state1), Times.Once); + } + + /// + /// Validates that when a task is cancelled and no cancellation handler is provided, the + /// continuation task transitions to the cancelled state. Ensures that no success or failure + /// handlers are invoked in this scenario. + /// + [Fact] + public async Task CreateContinuationTaskWithState_1Generic_TaskCancelsNoHandler() + { + // Arrange + // - Task to continue was cancelled + Task taskToContinue = GetCancelledTask(); + object state1 = new object(); + + Mock> mockOnFailure = new(); + Mock> mockOnSuccess = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + onCancellation: null); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Canceled, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + } + + /// + /// Tests the behavior of the CreateContinuationTaskWithState method when the task to + /// continue has faulted. Ensures that the failure handler is invoked exactly once with the + /// correct state and exception, while other handlers (success and cancellation) are not + /// invoked. Validates that the resulting continuation task ends with a faulted status. + /// + /// + /// Indicates if the failure handler should throw an exception during execution. This is + /// used for testing resilience to handler exceptions. True to simulate a throwing handler; + /// otherwise, false. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateContinuationTaskWithState_1Generic_TaskFaults(bool handlerShouldThrow) + { + // Arrange + // - Task to continue faulted + Task taskToContinue = Task.FromException(new Exception()); + object state1 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnCancellation = new(); + + Mock> mockOnFailure = new(); + if (handlerShouldThrow) + { + mockOnFailure.SetupThrows(); + } + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.Verify(action => action(state1, It.IsAny()), Times.Once); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Validates that a continuation task is correctly created when the preceding task faults + /// and no failure handler is provided. Ensures that the continuation task reflects the + /// faulted status of the original task, and that neither the success nor cancellation + /// handlers are invoked in this scenario. + /// + [Fact] + public async Task CreateContinuationTaskWithState_1Generic_TaskFaultsNoHandler() + { + // Arrange + // - Task to continue faulted + Task taskToContinue = Task.FromException(new Exception()); + object state1 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + mockOnSuccess.Object, + onFailure: null, + onCancellation: null); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Confirms that calling CreateContinuationTaskWithState with a generic state does not + /// result in an unobserved exception, even when the original task is canceled. Verifies + /// that the cancellation handler, if provided, correctly executes and that other handlers + /// are not triggered unexpectedly. + /// + [Fact] + public async Task CreateContinuationTaskWithState_1Generic_DoesNotCreateUnobservedException() + { + await VerifyDoesNotCreateUnobservedException(async e => + { + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + GetCancelledTask(), + new object(), + onSuccess: static _ => { }, + onCancellation: _ => throw e); + await RunWithTimeout(continuationTask, RunTimeout); + }); + } + + #endregion + + #region CreateContinuationTaskWithState + + /// + /// Tests the behavior of when the input task is null. Validates that the method correctly returns + /// null and that the success handler is invoked with the provided state objects. Ensures + /// neither the failure handler nor the cancellation handler is invoked. + /// + [Fact] + public void CreateContinuationTaskWithState_2Generics_NullTask() + { + // Arrange + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue: null, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + + // Assert + Assert.Null(continuationTask); + + mockOnSuccess.Verify(action => action(state1, state2), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Tests that the asynchronous continuation task correctly executes when the original task + /// completes successfully. Verifies that the success handler is triggered exactly once with + /// the provided state parameters, while the failure and cancellation handlers are not + /// invoked. + /// + [Fact] + public async Task CreateContinuationTaskWithState_2Generics_TaskCompletes() + { + // Arrange + // - Task to continue completed successfully + Task taskToContinue = Task.CompletedTask; + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.RanToCompletion, continuationTask.Status); + mockOnSuccess.Verify(action => action(state1, state2), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Validates that a continuation task created with two states invokes the success handler + /// once when the original task completes successfully, even if the success handler throws + /// an exception. Ensures that no failure or cancellation handlers are invoked and that the + /// continuation task ends in a faulted state as a result of the thrown exception. + /// + [Fact] + public async Task CreateContinuationTaskWithState_2Generics_TaskCompletesHandlerThrows() + { + // Arrange + // - Task to continue completed successfully + Task taskToContinue = Task.CompletedTask; + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnFailure = new(); + Mock> mockOnCancellation = new(); + + // - mockOnSuccess handler throws + Mock> mockOnSuccess = new(); + mockOnSuccess.SetupThrows(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.Verify(action => action(state1, state2), Times.Once); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Verifies that the continuation task is correctly created and executed when the initial + /// task is canceled. Ensures that the cancellation handler is invoked exactly once, while + /// success and failure handlers are never called. + /// + /// + /// A boolean parameter indicating whether the cancellation handler should throw an + /// exception when invoked. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateContinuationTaskWithState_2Generics_TaskCancels(bool handlerShouldThrow) + { + // Arrange + // - Task to continue was cancelled + Task taskToContinue = GetCancelledTask(); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnFailure = new(); + Mock> mockOnSuccess = new(); + + Mock> mockOnCancellation = new(); + if (handlerShouldThrow) + { + mockOnCancellation.SetupThrows(); + } + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Canceled, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + mockOnCancellation.Verify(action => action(state1, state2), Times.Once); + } + + /// + /// Validates that a continuation task created with two generic state parameters cancels + /// without invoking any success, failure, or cancellation handlers when the task being + /// continued was already cancelled. Ensures that the continuation task is in a cancelled + /// state and that none of the handlers are executed. + /// + [Fact] + public async Task CreateContinuationTaskWithState_2Generics_TaskCancelsNoHandler() + { + // Arrange + // - Task to continue was cancelled + Task taskToContinue = GetCancelledTask(); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnFailure = new(); + Mock> mockOnSuccess = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + onCancellation: null); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Canceled, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.VerifyNeverCalled(); + } + + /// + /// Verifies that a continuation task created with two generic state parameters correctly + /// handles the scenario where the antecedent task faults. Ensures that the failure handler + /// is invoked exactly once with the provided state and exception, while neither the success + /// nor cancellation handlers are invoked. + /// + /// + /// A flag indicating whether the failure handler should throw an exception when invoked. + /// This parameter is used to test the behavior of the continuation task in the presence of + /// handler failures. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateContinuationTaskWithState_2Generics_TaskFaults(bool handlerShouldThrow) + { + // Arrange + // - Task to continue faulted + Task taskToContinue = Task.FromException(new Exception()); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnCancellation = new(); + + Mock> mockOnFailure = new(); + if (handlerShouldThrow) + { + mockOnFailure.SetupThrows(); + } + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + state2, + mockOnSuccess.Object, + mockOnFailure.Object, + mockOnCancellation.Object); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnFailure.Verify(action => action(state1, state2, It.IsAny()), Times.Once); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Validates that a continuation task created for a faulted initial task does not invoke + /// the success or cancellation handlers. Ensures the resulting task properly transitions to + /// the Faulted state. The test confirms that the absence of a failure handler does not + /// create unintended behavior or exceptions. + /// + [Fact] + public async Task CreateContinuationTaskWithState_2Generics_TaskFaultsNoHandler() + { + // Arrange + // - Task to continue faulted + Task taskToContinue = Task.FromException(new Exception()); + object state1 = new object(); + object state2 = new object(); + + Mock> mockOnSuccess = new(); + Mock> mockOnCancellation = new(); + + // Act + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + taskToContinue, + state1, + state2, + mockOnSuccess.Object, + onFailure: null, + onCancellation: null); + await RunWithTimeout(continuationTask, RunTimeout); + + // Assert + Assert.Equal(TaskStatus.Faulted, continuationTask.Status); + mockOnSuccess.VerifyNeverCalled(); + mockOnCancellation.VerifyNeverCalled(); + } + + /// + /// Validates that creating a continuation task with two generic parameters does not result + /// in an unobserved exception. Ensures that the task handles cancellation scenarios without + /// leaking exceptions into the unobserved exception handler. + /// + [Fact] + public async Task CreateContinuationTaskWithState_2Generics_DoesNotCreateUnobservedException() + { + await VerifyDoesNotCreateUnobservedException(async e => + { + Task? continuationTask = AsyncHelper.CreateContinuationTaskWithState( + GetCancelledTask(), + new object(), + new object(), + onSuccess: static (_, _) => { }, + onCancellation: (_, _) => throw e); + await RunWithTimeout(continuationTask, RunTimeout); + }); + } + + #endregion + + #region WaitForCompletion + + /// + /// Verifies that the WaitForCompletion method does not create unobserved exceptions when a + /// faulted task is garbage collected. Ensures that exceptions occurring after a task times + /// out are properly handled and do not propagate as unobserved task exceptions. + /// + [Fact] + public async Task WaitForCompletion_DoesNotCreateUnobservedException() + { + await VerifyDoesNotCreateUnobservedException(e => + { + // - Run task that will always time out + TaskCompletionSource? tcs = new(); + AsyncHelper.WaitForCompletion( + tcs.Task, + timeoutInSeconds: 1, + onTimeout: null, + rethrowExceptions: true); + + // - Task has timed out, simulate faulting task completion source + tcs.SetException(e); + + // - Drop the last strong reference to tcs so the faulting task can be finalized + tcs = null; + + return Task.CompletedTask; + }); + } + + #endregion + + private static Task GetCancelledTask() + { + using CancellationTokenSource cts = new(); + cts.Cancel(); + + return Task.FromCanceled(cts.Token); + } + + private static TaskCompletionSource GetTaskCompletionSource() + => new(TaskCreationOptions.RunContinuationsAsynchronously); + + private static async Task VerifyDoesNotCreateUnobservedException(Func methodUnderTest) + { + // Arrange + // - Set up an unobserved exception handler that only captures the exception that will + // be thrown by the continuation callback. + // NOTE: This only captures the exception thrown by the callback because unit tests run + // in parallel. Thus, other tests may cause unobserved exceptions that would cause + // false failures of this test. + Guid exceptionId = Guid.NewGuid(); + Exception? unhandledException = null; + EventHandler handleUnobservedException = + (_, args) => + { + // Filter inner exceptions on type and expected ID + IEnumerable observableInnerExceptions = + args.Exception.InnerExceptions.OfType(); + if (observableInnerExceptions.Any(oe => oe.Identifier == exceptionId)) + { + unhandledException = args.Exception; + args.SetObserved(); + } + }; + + // - Register the unobserved exception handler + // @TODO: Can we do this with a custom scheduler to avoid changing global state? + TaskScheduler.UnobservedTaskException += handleUnobservedException; + + try + { + // Act + // - Run the test method + await methodUnderTest(new ObservableException(exceptionId)); + + // - Force GC collection to get any unobserved exception to raise an exception. + // However, if the method under test is behaving correctly, this *should not* + // raise any exception. + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + // Assert + // - The unobserved exception handler should not have captured any exception. + Assert.Null(unhandledException); + } + finally + { + // Cleanup + TaskScheduler.UnobservedTaskException -= handleUnobservedException; + } + } + + private static async Task RunWithTimeout([NotNull] Task? taskToRun, TimeSpan timeout) + { + if (taskToRun is null) + { + Assert.Fail("Expected non-null task for timeout"); + } + + Task winner = await Task.WhenAny(taskToRun, Task.Delay(timeout)); + if (winner != taskToRun) + { + Assert.Fail("Timeout elapsed."); + } + + // Force observation of any exception + _ = taskToRun.Exception; + } + + private class ObservableException : Exception + { + public ObservableException(Guid id) + { + Identifier = id; + } + + public Guid Identifier { get; set; } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Utilities/MockExtensions.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Utilities/MockExtensions.cs new file mode 100644 index 0000000000..d7b7e1818f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Utilities/MockExtensions.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 Moq; + +namespace Microsoft.Data.SqlClient.UnitTests.Utilities +{ + /// + /// Provides extension methods for working with mocked instances of Action delegates in unit tests. + /// + public static class MockExtensions + { + /// + /// Configures the specified mock to throw an exception of the specified type + /// when its associated action is invoked. + /// + /// The type of exception to throw. + /// The mock object to configure. + public static void SetupThrows(this Mock mock) + where TException : Exception, new() + { + mock.Setup(action => action()) + .Throws(); + } + + /// + /// Configures the specified mock of an Action delegate with one parameter to throw + /// an exception of the specified type when it is invoked with any argument. + /// + /// The type of the parameter of the Action delegate. + /// The type of exception to throw. + /// The mock object of the Action delegate to configure. + public static void SetupThrows(this Mock> mock) + where TException : Exception, new() + { + mock.Setup(action => action(It.IsAny())) + .Throws(); + } + + /// + /// Configures the specified mock to throw an exception of the specified type + /// when the mocked Action with two parameters is invoked. + /// + /// The type of the first parameter of the mocked Action. + /// The type of the second parameter of the mocked Action. + /// The type of exception to throw. + /// The mock object to configure. + public static void SetupThrows(this Mock> mock) + where TException : Exception, new() + { + mock.Setup(action => action(It.IsAny(), It.IsAny())) + .Throws(); + } + + /// + /// Configures the specified mock to throw an exception of the specified type + /// when its associated action is invoked with three parameters of the specified types. + /// + /// The type of the first parameter of the action. + /// The type of the second parameter of the action. + /// The type of the third parameter of the action. + /// The type of exception to throw. + /// The mock object to configure. + public static void SetupThrows(this Mock> mock) + where TException : Exception, new() + { + mock.Setup(action => action(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(); + } + + /// + /// Verifies that the mocked action has never been invoked. + /// + /// The mock object for which to verify that the action was never called. + public static void VerifyNeverCalled(this Mock mock) => + mock.Verify(action => action(), Times.Never); + + /// + /// Verifies that the specified mock action was never invoked. + /// + /// The type of the parameter expected by the mocked action. + /// The mocked action to verify. + public static void VerifyNeverCalled(this Mock> mock) + { + mock.Verify( + action => action(It.IsAny()), + Times.Never); + } + + /// + /// Verifies that the specified mock action was never called during the test execution. + /// + /// The type of the first parameter of the action. + /// The type of the second parameter of the action. + /// The mock object representing the action to verify. + public static void VerifyNeverCalled(this Mock> mock) + { + mock.Verify( + action => action(It.IsAny(), It.IsAny()), + Times.Never); + } + + /// + /// Verifies that the specified mock of an was never called + /// with any arguments during the test execution. + /// + /// The type of the first parameter of the action. + /// The type of the second parameter of the action. + /// The type of the third parameter of the action. + /// The mock object to verify. + public static void VerifyNeverCalled(this Mock> mock) + { + mock.Verify( + action => action(It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never); + } + } +} From 145b28570a643596ce79a5369d540fbe2d96dc77 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 25 Jun 2026 04:35:14 +1000 Subject: [PATCH 160/298] missing cleanup of CancellationTokenSource (#4009) Co-authored-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> --- .../ManagedSni/SsrpClient.netcore.cs | 2 +- .../Data/SqlClient/SqlCommand.NonQuery.cs | 13 ++ .../Data/SqlClient/SqlCommand.Reader.cs | 13 ++ .../Microsoft/Data/SqlClient/SqlConnection.cs | 18 ++- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 14 +- .../Data/SqlClient/SqlSequentialStream.cs | 20 ++- .../Data/SqlClient/SqlSequentialTextReader.cs | 20 ++- .../CancellationTokenSourceDisposalTest.cs | 132 ++++++++++++++++++ 8 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/CancellationTokenSourceDisposalTest.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs index 4544d1169f..0633dc2cce 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs @@ -299,7 +299,7 @@ private static SsrpResult SendUDPRequest(IPAddress[] ipAddresses, int port, byte if (allIPsInParallel) // Used for MultiSubnetFailover { List> tasks = new(ipAddresses.Length); - CancellationTokenSource cts = new CancellationTokenSource(); + using CancellationTokenSource cts = new CancellationTokenSource(); for (int i = 0; i < ipAddresses.Length; i++) { IPEndPoint endPoint = new IPEndPoint(ipAddresses[i], port); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs index ac9ccbbc6b..311c26b4e5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs @@ -874,11 +874,14 @@ private void RunExecuteNonQueryTdsSetupReconnnectContinuation( { if (completion.Task.IsCompleted) { + timeoutCts.Cancel(); + timeoutCts.Dispose(); return; } Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion); timeoutCts.Cancel(); + timeoutCts.Dispose(); Task subTask = RunExecuteNonQueryTds( methodName, @@ -898,6 +901,16 @@ private void RunExecuteNonQueryTdsSetupReconnnectContinuation( state: completion, onSuccess: static state => ((TaskCompletionSource)state).SetResult(null)); } + }, + onFailure: _ => + { + timeoutCts.Cancel(); + timeoutCts.Dispose(); + }, + onCancellation: () => + { + timeoutCts.Cancel(); + timeoutCts.Dispose(); }); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs index bfdbd40fd5..1aca08c232 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs @@ -1643,11 +1643,14 @@ private void RunExecuteReaderTdsSetupReconnectContinuation( { if (completion.Task.IsCompleted) { + timeoutCts.Cancel(); + timeoutCts.Dispose(); return; } Interlocked.CompareExchange(ref _reconnectionCompletionSource, null, completion); timeoutCts.Cancel(); + timeoutCts.Dispose(); RunExecuteReaderTds( cmdBehavior, @@ -1672,6 +1675,16 @@ private void RunExecuteReaderTdsSetupReconnectContinuation( state: completion, onSuccess: static state => ((TaskCompletionSource)state).SetResult(null)); } + }, + onFailure: _ => + { + timeoutCts.Cancel(); + timeoutCts.Dispose(); + }, + onCancellation: () => + { + timeoutCts.Cancel(); + timeoutCts.Dispose(); }); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index b27b7081c6..7b236afc2b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -1406,10 +1406,18 @@ public override void Close() Task reconnectTask = _currentReconnectionTask; if (reconnectTask != null && !reconnectTask.IsCompleted) { - CancellationTokenSource cts = _reconnectionCancellationSource; - if (cts != null) + // Only request cancellation here. ReconnectAsync still owns the + // CancellationTokenSource and disposes it in its finally block once it + // has stopped using the token; disposing it here while the reconnect + // task is still running could surface ObjectDisposedException. + try + { + _reconnectionCancellationSource?.Cancel(); + } + catch (ObjectDisposedException) { - cts.Cancel(); + // ReconnectAsync's finally block disposed the source concurrently; + // the reconnect is already completing, so there is nothing to cancel. } AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false); // we do not need to deal with possible exceptions in reconnection if (State != ConnectionState.Open) @@ -1651,6 +1659,7 @@ internal void RegisterWaitingForReconnect(Task waitingTask) private async Task ReconnectAsync(int timeout) { + CancellationTokenSource cts = new CancellationTokenSource(); try { long commandTimeoutExpiration = 0; @@ -1658,7 +1667,6 @@ private async Task ReconnectAsync(int timeout) { commandTimeoutExpiration = ADP.TimerCurrent() + ADP.TimerFromSeconds(timeout); } - CancellationTokenSource cts = new CancellationTokenSource(); _reconnectionCancellationSource = cts; CancellationToken ctoken = cts.Token; int retryCount = _connectRetryCount; // take a snapshot: could be changed by modifying the connection string @@ -1719,6 +1727,8 @@ private async Task ReconnectAsync(int timeout) } finally { + _reconnectionCancellationSource = null; + cts.Dispose(); _recoverySessionData = null; _suppressStateChangeForReconnection = false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index ca44267eb6..6b7b53688e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -881,7 +881,12 @@ public override void Close() TdsParserStateObject stateObj = _stateObj; // Request that the current task is stopped - _cancelAsyncOnCloseTokenSource.Cancel(); + var cancelTokenSource = Interlocked.Exchange(ref _cancelAsyncOnCloseTokenSource, null); + if (cancelTokenSource != null) + { + cancelTokenSource.Cancel(); + cancelTokenSource.Dispose(); + } var currentTask = _currentTask; if ((currentTask != null) && (!currentTask.IsCompleted)) { @@ -1121,7 +1126,12 @@ virtual internal void CloseReaderFromConnection() var stateObj = _stateObj; _isClosed = true; // Request that the current task is stopped - _cancelAsyncOnCloseTokenSource.Cancel(); + var cancelTokenSource = Interlocked.Exchange(ref _cancelAsyncOnCloseTokenSource, null); + if (cancelTokenSource != null) + { + cancelTokenSource.Cancel(); + cancelTokenSource.Dispose(); + } if (stateObj != null) { var networkPacketTaskSource = stateObj._networkPacketTaskSource; 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 52b965325d..bb3b8f9bf0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs @@ -235,6 +235,15 @@ public override void Write(byte[] buffer, int offset, int count) /// internal void SetClosed() { + // Idempotent: once closed, _reader is null and _disposalTokenSource may already + // have been disposed by Dispose(). Calling Cancel() again would throw + // ObjectDisposedException, so bail out. SqlDataReader can invoke SetClosed() + // after the consumer has already disposed the stream. + if (_reader == null) + { + return; + } + _disposalTokenSource.Cancel(); _reader = null; @@ -250,8 +259,15 @@ protected override void Dispose(bool disposing) { if (disposing) { - // Set the stream as closed - SetClosed(); + if (_reader != null) + { + // Set the stream as closed + SetClosed(); + } + // Safe to call unconditionally: the _reader != null guard above ensures SetClosed() + // (and thus Cancel()) runs at most once, and CancellationTokenSource.Dispose() + // is idempotent, so repeated Dispose() calls do not throw. + _disposalTokenSource.Dispose(); } base.Dispose(disposing); 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 3ae05e0386..b5a00cde4e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs @@ -275,8 +275,15 @@ protected override void Dispose(bool disposing) { if (disposing) { - // Set the textreader as closed - SetClosed(); + if (!IsClosed) + { + // Set the textreader as closed + SetClosed(); + } + // Safe to call unconditionally: the IsClosed guard above ensures SetClosed() + // (and thus Cancel()) runs at most once, and CancellationTokenSource.Dispose() + // is idempotent, so repeated Dispose() calls do not throw. + _disposalTokenSource.Dispose(); } base.Dispose(disposing); @@ -288,6 +295,15 @@ protected override void Dispose(bool disposing) /// internal void SetClosed() { + // Idempotent: once closed, _reader is null and _disposalTokenSource may already + // have been disposed by Dispose(). Calling Cancel() again would throw + // ObjectDisposedException, so bail out. SqlDataReader can invoke SetClosed() + // after the consumer has already disposed the text reader. + if (IsClosed) + { + return; + } + _disposalTokenSource.Cancel(); _reader = null; _peekedChar = -1; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/CancellationTokenSourceDisposalTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/CancellationTokenSourceDisposalTest.cs new file mode 100644 index 0000000000..376a1df7e6 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/CancellationTokenSourceDisposalTest.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Data; +using System.IO; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + /// + /// Regression tests for the CancellationTokenSource cleanup work (PR dotnet/SqlClient#4009). + /// Disposing a sequential / now disposes its + /// internal disposal CancellationTokenSource. These tests ensure that the subsequent + /// (and unavoidable) call to SqlSequentialStream/SqlSequentialTextReader.SetClosed() made by + /// the owning SqlDataReader does not throw ObjectDisposedException, and that repeated + /// Close()/Dispose() calls remain safe. + /// + [Trait("Set", "2")] + public class CancellationTokenSourceDisposalTest + { + public static IEnumerable ConnectionStrings + { + get + { + foreach (string connectionString in DataTestUtility.GetConnectionStrings(withEnclave: false)) + { + yield return new object[] { connectionString }; + } + } + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [MemberData(nameof(ConnectionStrings))] + public static void SequentialStream_DisposedBeforeReaderClose_DoesNotThrow(string connectionString) + { + using SqlConnection connection = new SqlConnection(connectionString); + connection.Open(); + + using SqlCommand cmd = new SqlCommand( + "SELECT CAST(REPLICATE(CAST('a' AS VARCHAR(MAX)), 8000) AS VARBINARY(MAX))", + connection); + using SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess); + + Assert.True(reader.Read()); + + Stream stream = reader.GetStream(0); + + // Disposing the stream disposes its internal disposal CancellationTokenSource. + stream.Dispose(); + // Double-dispose of the stream must be safe. + stream.Dispose(); + + // The reader still references the stream and calls SetClosed() on it during Close(). + // Before the fix this called Cancel() on the disposed CancellationTokenSource and + // threw ObjectDisposedException. + reader.Close(); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [MemberData(nameof(ConnectionStrings))] + public static void SequentialTextReader_DisposedBeforeReaderClose_DoesNotThrow(string connectionString) + { + using SqlConnection connection = new SqlConnection(connectionString); + connection.Open(); + + using SqlCommand cmd = new SqlCommand( + "SELECT CAST(REPLICATE(CAST('a' AS NVARCHAR(MAX)), 4000) AS NVARCHAR(MAX))", + connection); + using SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess); + + Assert.True(reader.Read()); + + TextReader textReader = reader.GetTextReader(0); + + // Disposing the text reader disposes its internal disposal CancellationTokenSource. + textReader.Dispose(); + // Double-dispose of the text reader must be safe. + textReader.Dispose(); + + // SetClosed() is invoked on the already-disposed text reader during Close(); + // it must not throw ObjectDisposedException. + reader.Close(); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [MemberData(nameof(ConnectionStrings))] + public static void SequentialStream_DisposedThenReaderAdvanced_DoesNotThrow(string connectionString) + { + using SqlConnection connection = new SqlConnection(connectionString); + connection.Open(); + + // Two result sets so we can advance the reader (which closes the active stream) after disposal. + using SqlCommand cmd = new SqlCommand( + "SELECT CAST(REPLICATE(CAST('a' AS VARCHAR(MAX)), 8000) AS VARBINARY(MAX)); SELECT 1", + connection); + using SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess); + + Assert.True(reader.Read()); + + Stream stream = reader.GetStream(0); + stream.Dispose(); + + // Advancing past the result set triggers CloseActiveSequentialStreamAndTextReader(), + // which calls SetClosed() on the disposed stream. Must not throw. + Assert.True(reader.NextResult()); + Assert.True(reader.Read()); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [MemberData(nameof(ConnectionStrings))] + public static void DataReader_MultipleCloseAndDispose_DoesNotThrow(string connectionString) + { + using SqlConnection connection = new SqlConnection(connectionString); + connection.Open(); + + using SqlCommand cmd = new SqlCommand("SELECT 1", connection); + SqlDataReader reader = cmd.ExecuteReader(); + + Assert.True(reader.Read()); + + // Close()/Dispose() dispose the close-cancellation CancellationTokenSource via an + // Interlocked swap; repeated calls must be safe (the swapped-out source is null on + // subsequent calls rather than a disposed instance). + reader.Close(); + reader.Close(); + reader.Dispose(); + reader.Dispose(); + } + } +} From b562c5b98a01d4e9825b4181843a645e531ceeb8 Mon Sep 17 00:00:00 2001 From: campersau Date: Wed, 24 Jun 2026 21:06:32 +0200 Subject: [PATCH 161/298] SqlBatchCommand support CommandBehavior (#4125) * Support SqlBatchCommand.CommandBehavior * Support SqlBatch ExecuteReader CommandBehavior * Update tests * add documentation to ValidateExecuteCommandBehavior * support ExecuteReader(Async)(CommandBehavior) on .NET Framework * reverset GetCommand unification and call GetOptionsResetString manually * cleanup accidental whiltespace * pass CommandBehavior.CloseConnection in example * PR feedback * update docs * PR feedback --- .../Microsoft.Data.SqlClient/SqlBatch.xml | 90 +++++++++++++- .../SqlBatchCommand.xml | 5 +- .../ref/Microsoft.Data.SqlClient.cs | 16 ++- .../src/Microsoft/Data/SqlClient/SqlBatch.cs | 88 +++++++++---- .../Data/SqlClient/SqlCommand.Batch.cs | 2 +- .../Data/SqlClient/SqlCommand.Reader.cs | 11 +- .../tests/ManualTests/SQL/Batch/BatchTests.cs | 116 ++++++++++++++++++ .../Microsoft/Data/SqlClient/SqlBatchTest.cs | 35 ++++++ 8 files changed, 323 insertions(+), 40 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBatchTest.cs diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml index 40f34cba48..d2fdff51d2 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml @@ -1,4 +1,4 @@ - + @@ -179,7 +179,7 @@ The list of contained in the batch in a . - + Sends the to the and builds a . @@ -232,7 +232,85 @@ - + + + An instance of , specifying options for batch execution and data retrieval. + Only and are supported at the batch level. + + + Sends the to the and builds a . + + + + 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. + + + using Microsoft.Data.SqlClient; + + class Program + { + static void Main() + { + string str = "Data Source=(local);Initial Catalog=Northwind;" + + "Integrated Security=SSPI;Encrypt=False"; + RunBatch(str); + } + + static void RunBatch(string connString) + { + using var connection = new SqlConnection(connString); + connection.Open(); + + using var batch = new SqlBatch(connection); + + const int count = 10; + const string parameterName = "parameter"; + for (int i = 0; i < count; i++) + { + var batchCommand = new SqlBatchCommand($"SELECT @{parameterName} as value"); + batchCommand.Parameters.Add(new SqlParameter(parameterName, i)); + batch.BatchCommands.Add(batchCommand); + } + + var results = new List<int>(count); + using (SqlDataReader reader = batch.ExecuteReader(CommandBehavior.CloseConnection)) + { + do + { + while (reader.Read()) + { + results.Add(reader.GetFieldValue<int>(0)); + } + } while (reader.NextResult()); + } + Console.WriteLine(string.Join(", ", results)); + } + } + + + + + A token to cancel the asynchronous operation. + + An asynchronous version of , which sends the to the and builds a . + Exceptions will be reported via the returned Task object. + + A task representing the asynchronous operation. + + An error occurred while executing the batch. + + + The value is invalid. + + + The cancellation token was canceled. This exception is stored into the returned task. + + + + + An instance of , specifying options for batch execution and data retrieval. + Only and are supported at the batch level. + A token to cancel the asynchronous operation. An asynchronous version of , which sends the to the and builds a . @@ -303,6 +381,7 @@ An instance of , specifying options for batch execution and data retrieval. + Only and are supported at the batch level. Executes the batch against its connection, returning a which can be used to access the results. @@ -326,7 +405,10 @@ When the batch returns multiple result sets from different commands, - One of the enumeration values that specifies options for batch execution and data retrieval. + + An instance of , specifying options for batch execution and data retrieval. + Only and are supported at the batch level. + A token to cancel the asynchronous operation. This implementation invokes the method and returns a completed task. The default implementation will return a cancelled task if passed an already cancelled cancellation token. This method accepts a cancellation token that can be used to request the operation to be cancelled early. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml index 8f0be84d25..5145571840 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml @@ -1,4 +1,4 @@ - + @@ -87,7 +87,8 @@ - One of the values, indicating options for statement execution and data retrieval. + An instance of , specifying options for statement execution and data retrieval. + Only and are supported at the statement level. diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs index 96ac073744..f0e785a36e 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs @@ -144,14 +144,26 @@ public class SqlBatch : System.IDisposable, System.IAsyncDisposable #else public System.Threading.Tasks.Task ExecuteNonQueryAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; #endif - /// + /// public Microsoft.Data.SqlClient.SqlDataReader ExecuteReader() => throw null; - /// + /// + #if NET + public new Microsoft.Data.SqlClient.SqlDataReader ExecuteReader(System.Data.CommandBehavior behavior) => throw null; + #else + public Microsoft.Data.SqlClient.SqlDataReader ExecuteReader(System.Data.CommandBehavior behavior) => throw null; + #endif + /// #if NET public new System.Threading.Tasks.Task ExecuteReaderAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; #else public System.Threading.Tasks.Task ExecuteReaderAsync(System.Threading.CancellationToken cancellationToken = default) => throw null; #endif + /// + #if NET + public new System.Threading.Tasks.Task ExecuteReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken = default) => throw null; + #else + public System.Threading.Tasks.Task ExecuteReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken = default) => throw null; + #endif /// #if NET public override object ExecuteScalar() => throw null; 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 a1f79f6f15..d24b9724ac 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatch.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatch.cs @@ -266,24 +266,54 @@ SqlTransaction Transaction } } - /// - public SqlDataReader ExecuteReader() + /// + public SqlDataReader ExecuteReader() => ExecuteReader(CommandBehavior.Default); + + /// + public + #if NET + new + #endif + SqlDataReader ExecuteReader(CommandBehavior behavior) { + ValidateExecuteCommandBehavior(nameof(ExecuteReader), behavior); + CheckDisposed(); SetupBatchCommandExecute(); - return _batchCommand.ExecuteReader(); + return _batchCommand.ExecuteReader(behavior); } - /// + /// public #if NET new #endif - Task ExecuteReaderAsync(CancellationToken cancellationToken = default) + Task ExecuteReaderAsync(CancellationToken cancellationToken = default) => ExecuteReaderAsync(CommandBehavior.Default, cancellationToken); + + /// + public + #if NET + new + #endif + Task ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken = default) { + ValidateExecuteCommandBehavior(nameof(ExecuteReaderAsync), behavior); + CheckDisposed(); SetupBatchCommandExecute(); - return _batchCommand.ExecuteReaderAsync(cancellationToken); + return _batchCommand.ExecuteReaderAsync(behavior, cancellationToken) + .ContinueWith(static (result) => + { + if (result.IsFaulted) + { + throw result.Exception.InnerException; + } + return result.Result; + }, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled, + TaskScheduler.Default + ); } /// @@ -293,7 +323,7 @@ Task ExecuteReaderAsync(CancellationToken cancellationToken = def #else virtual #endif - DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteReader(); + DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteReader(behavior); /// protected @@ -302,24 +332,7 @@ Task ExecuteReaderAsync(CancellationToken cancellationToken = def #else virtual #endif - Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) - { - CheckDisposed(); - SetupBatchCommandExecute(); - return _batchCommand.ExecuteReaderAsync(cancellationToken) - .ContinueWith((result) => - { - if (result.IsFaulted) - { - throw result.Exception.InnerException; - } - return result.Result; - }, - CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnCanceled, - TaskScheduler.Default - ); - } + async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) => await ExecuteReaderAsync(behavior, cancellationToken); private void CheckDisposed() { @@ -350,5 +363,30 @@ private void SetupBatchCommandExecute() } _batchCommand.SetBatchRPCModeReadyToExecute(); } + + /// + /// Validates that the provided is compatible with . + /// + /// The name of the calling method for error reporting. + /// The behavior flags to validate. + /// + /// + /// Only and + /// are supported at the batch level. + /// + /// + /// To apply other behaviors (such as or ), + /// they must be set on individual instances within the batch. + /// + /// + /// Thrown when unsupported behavior flags are detected. + internal static void ValidateExecuteCommandBehavior(string method, CommandBehavior behavior) + { + if (0 != (behavior & ~(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection))) + { + ADP.ValidateCommandBehavior(behavior); + throw ADP.NotSupportedCommandBehavior(behavior & ~(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection), method); + } + } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Batch.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Batch.cs index 7b6d16a420..da7d1e2ed7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Batch.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Batch.cs @@ -37,7 +37,7 @@ internal void AddBatchCommand(SqlBatchCommand batchCommand) { // All batch sql statements must be executed inside sp_executesql, including those // without parameters - BuildExecuteSql(CommandBehavior.Default, commandText, batchCommand.Parameters, ref rpc); + BuildExecuteSql(batchCommand.CommandBehavior, batchCommand.Parameters, ref rpc); } _RPCList.Add(rpc); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs index 1aca08c232..bffe17baf9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs @@ -436,7 +436,6 @@ private _SqlRPC BuildExecute(bool inSchema) // @TODO: Can we return the RPC here like BuildExecute does? private void BuildExecuteSql( CommandBehavior behavior, - string commandText, SqlParameterCollection parameters, ref _SqlRPC rpc) { @@ -457,13 +456,13 @@ private void BuildExecuteSql( SqlParameter sqlParam; // @batch_text - commandText ??= GetCommandText(behavior); + string text = GetCommandText(behavior); sqlParam = rpc.systemParams[0]; - sqlParam.SqlDbType = (commandText.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT + sqlParam.SqlDbType = (text.Length << 1) <= TdsEnums.TYPE_SIZE_LIMIT ? SqlDbType.NVarChar : SqlDbType.NText; - sqlParam.Size = commandText.Length; - sqlParam.Value = commandText; + sqlParam.Size = text.Length; + sqlParam.Value = text; sqlParam.Direction = ParameterDirection.Input; // @batch_params @@ -1446,7 +1445,7 @@ private SqlDataReader RunExecuteReaderTds( else { Debug.Assert(_execType is EXECTYPE.UNPREPARED, "Invalid execType!"); - BuildExecuteSql(cmdBehavior, commandText: null, _parameters, ref rpc); + BuildExecuteSql(cmdBehavior, _parameters, ref rpc); } rpc.options = TdsEnums.RPC_NOMETADATA; 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 01d54f2462..91b57a46b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs @@ -650,6 +650,122 @@ public static async Task ExecuteReaderAsyncMultiple() Assert.Equal(10, resultRowCount); } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static void ExecuteReaderCommandCommandBehaviorSchemaOnlyKeyInfo() + { + System.Collections.ObjectModel.ReadOnlyCollection schema; + + using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) + using (SqlBatch batch = new SqlBatch(conn)) + { + conn.Open(); + + var cmd = new SqlBatchCommand("SELECT * FROM Categories"); + cmd.CommandBehavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo; + batch.BatchCommands.Add(cmd); + + using var reader = batch.ExecuteReader(); + + Assert.False(reader.Read()); + + schema = reader.GetColumnSchema(); + } + + Assert.Equal(4, schema.Count); + Assert.True(schema[0].IsKey); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task ExecuteReaderAsyncCommandCommandBehaviorSchemaOnlyKeyInfo() + { + System.Collections.ObjectModel.ReadOnlyCollection schema; + + using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) + await using (SqlBatch batch = new SqlBatch(conn)) + { + await conn.OpenAsync(); + + var cmd = new SqlBatchCommand("SELECT * FROM Categories"); + cmd.CommandBehavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo; + batch.BatchCommands.Add(cmd); + + using var reader = await batch.ExecuteReaderAsync(); + + Assert.False(await reader.ReadAsync()); + + schema = reader.GetColumnSchema(); + } + + Assert.Equal(4, schema.Count); + Assert.True(schema[0].IsKey); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static void ExecuteReaderCommandBehaviorCloseConnection() + { + int resultSetCount = 0; + int resultRowCount = 0; + + using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) + using (SqlBatch batch = new SqlBatch(conn)) + { + conn.Open(); + + batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1")); + batch.BatchCommands.Add(new SqlBatchCommand("SELECT 2")); + + using (var reader = batch.ExecuteReader(CommandBehavior.CloseConnection)) + { + do + { + resultSetCount += 1; + while (reader.Read()) + { + resultRowCount += 1; + } + } while (reader.NextResult()); + } + + Assert.Equal(ConnectionState.Closed, conn.State); + } + + Assert.Equal(2, resultSetCount); + Assert.Equal(2, resultRowCount); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task ExecuteReaderAsyncCommandBehaviorCloseConnection() + { + int resultSetCount = 0; + int resultRowCount = 0; + + using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) + await using (SqlBatch batch = new SqlBatch(conn)) + { + await conn.OpenAsync(); + + batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1")); + batch.BatchCommands.Add(new SqlBatchCommand("SELECT 2")); + + using (var reader = await batch.ExecuteReaderAsync(CommandBehavior.CloseConnection)) + { + do + { + resultSetCount += 1; + while (await reader.ReadAsync()) + { + resultRowCount += 1; + } + } while (await reader.NextResultAsync()); + } + + Assert.Equal(ConnectionState.Closed, conn.State); + } + + Assert.Equal(2, resultSetCount); + Assert.Equal(2, resultRowCount); + } + private static SqlParameter CreateParameter(string name, SqlDbType type, T value, ParameterDirection direction = ParameterDirection.Input) { var parameter = new SqlParameter(name, type); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBatchTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBatchTest.cs new file mode 100644 index 0000000000..ebed579cd2 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBatchTest.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.Data; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests; + +/// +/// Provides unit tests for verifying the behavior of the SqlBatch class. +/// +public class SqlBatchTest +{ + /// + /// Verifies that SqlBatch.ValidateExecuteCommandBehavior throws an ArgumentOutOfRangeException when an invalid CommandBehavior is specified. + /// + [Fact] + public void InvalidCommandBehaviorValidateExecuteCommandBehavior_Throws() + { + ArgumentOutOfRangeException ex = Assert.Throws(() => SqlBatch.ValidateExecuteCommandBehavior("ExecuteNonQuery", (CommandBehavior)64)); + Assert.Contains("CommandBehavior", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Verifies that SqlBatch.ValidateExecuteCommandBehavior throws an ArgumentOutOfRangeException when a valid but unsupported CommandBehavior is specified. + /// + [Fact] + public void NotSupportedCommandBehaviorValidateExecuteCommandBehavior_Throws() + { + ArgumentOutOfRangeException ex = Assert.Throws(() => SqlBatch.ValidateExecuteCommandBehavior("ExecuteNonQuery", CommandBehavior.KeyInfo)); + Assert.Contains("not supported", ex.Message, StringComparison.OrdinalIgnoreCase); + } +} From 85b304d969e90b77ff0b1ef70a1ff118433568f7 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Thu, 25 Jun 2026 18:01:40 +0530 Subject: [PATCH 162/298] Add release notes for 6.1.6 (#4404) --- CHANGELOG.md | 31 ++++++++ release-notes/6.1/6.1.6.md | 146 ++++++++++++++++++++++++++++++++++++ release-notes/6.1/README.md | 1 + 3 files changed, 178 insertions(+) create mode 100644 release-notes/6.1/6.1.6.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f5fd80d19e..eb1b0ee034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) > **Note:** Releases are sorted in reverse chronological order (newest first). +## [Stable Release 6.1.6] - 2026-06-24 + +This update brings the following changes since the [6.1.5](release-notes/6.1/6.1.5.md) release. +See the [full release notes](release-notes/6.1/6.1.6.md) for detailed descriptions. + +### Added + +- Added Web Account Manager (WAM) broker support for the supported Entra ID authentication modes (Windows only), including the new `ActiveDirectoryAuthenticationProviderOptions` type with a `UseWamBroker` property, an `ActiveDirectoryAuthenticationProvider(ActiveDirectoryAuthenticationProviderOptions options)` constructor overload, and a cross-platform `SetParentActivityOrWindowFunc(Func)` method. + ([#4288](https://github.com/dotnet/SqlClient/pull/4288), + [#4387](https://github.com/dotnet/SqlClient/pull/4387)) + +### Changed + +- Hardened TDS token parsing with data-length bounds checks to prevent unbounded memory allocation from a server spoofing length fields. + ([#4340](https://github.com/dotnet/SqlClient/pull/4340), + [#4359](https://github.com/dotnet/SqlClient/pull/4359)) + +- Updated dependencies ([#4387](https://github.com/dotnet/SqlClient/pull/4387)): + - Updated `Microsoft.Identity.Client` to 4.84.2 (was 4.80.0) + - Added `Microsoft.Identity.Client.Broker` 4.84.2 + +### Fixed + +- Fixed a `NullReferenceException` in `SqlDataReader.GetChars` on the PLP + `SequentialAccess` path when a `null` buffer was passed with a negative `bufferIndex`; it now throws `ArgumentOutOfRangeException`. + ([#4159](https://github.com/dotnet/SqlClient/pull/4159), + [#4205](https://github.com/dotnet/SqlClient/pull/4205)) + +- Fixed column master key (CMK) signature verification caching where a cached verification failure could subsequently be reported as a valid signature. + ([#4339](https://github.com/dotnet/SqlClient/pull/4339), + [#4356](https://github.com/dotnet/SqlClient/pull/4356)) + ## [Preview Release 7.1.0-preview1] - 2026-04-29 This update brings the following changes since the [7.0.0](release-notes/7.0/7.0.0.md) release. diff --git a/release-notes/6.1/6.1.6.md b/release-notes/6.1/6.1.6.md new file mode 100644 index 0000000000..c2119131a4 --- /dev/null +++ b/release-notes/6.1/6.1.6.md @@ -0,0 +1,146 @@ +# Release Notes + +## Stable Release 6.1.6 - 2026-06-24 + +This update brings the following changes since the [6.1.5](6.1.5.md) release: + +### Added + +#### WAM broker support for the supported Entra ID authentication modes (Windows only) + +*What Changed:* + +- Added support for the [Web Account Manager (WAM)](https://learn.microsoft.com/entra/msal/dotnet/acquiring-tokens/desktop-mobile/wam) broker for the supported Microsoft Entra ID authentication modes. A new `ActiveDirectoryAuthenticationProviderOptions` options bag and a corresponding `ActiveDirectoryAuthenticationProvider(ActiveDirectoryAuthenticationProviderOptions options)` constructor were introduced, exposing a `UseWamBroker` property (alongside `ApplicationClientId` and `DeviceCodeFlowCallback`). + ([#4288](https://github.com/dotnet/SqlClient/pull/4288), [#4387](https://github.com/dotnet/SqlClient/pull/4387)) +- Added a cross-platform `SetParentActivityOrWindowFunc(Func parentActivityOrWindowFunc)` method so callers can supply a parent window handle on Windows or a parent `Activity`/`UIViewController` on Android/iOS/MAUI. + +*Who Benefits:* + +- Applications using `ActiveDirectoryInteractive` and other supported Entra ID authentication modes on Windows benefit from the WAM broker's improved security (tokens are brokered by the OS), single sign-on with the logged-in Windows account, and support for Conditional Access and Windows Hello. + +*Impact:* + +- When you supply your `ApplicationClientId`, WAM is opt-in via `ActiveDirectoryAuthenticationProviderOptions.UseWamBroker`. Consider enabling it when you want OS-brokered tokens, single sign-on with the signed-in Windows account, Windows Hello, and Conditional Access support. +- `UseWamBroker` is a **Windows-only** setting and has no effect on non-Windows platforms, where interactive Entra ID flows always use the system browser. +- Prefer the new options-bag constructor over the positional-argument overloads in new code. + +```c# +var options = new ActiveDirectoryAuthenticationProviderOptions +{ + ApplicationClientId = "", + // Enable WAM (Windows only) for OS-brokered tokens, SSO, Windows Hello, and Conditional Access. + UseWamBroker = true, +}; +var provider = new ActiveDirectoryAuthenticationProvider(options); +// Supply the parent window/activity that owns the interactive sign-in prompt. +provider.SetParentActivityOrWindowFunc(() => parentWindowHandle); +SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, provider); +``` + + +### Changed + +#### Hardened TDS token parsing with data-length bounds checks + +*What Changed:* + +- Added bounds checking when parsing TDS token data lengths. The parser now validates the declared length of incoming token data against the available buffer before reading, rejecting malformed or out-of-range length values instead of reading past the intended boundary. + ([#4340](https://github.com/dotnet/SqlClient/pull/4340), [#4359](https://github.com/dotnet/SqlClient/pull/4359)) + +*Who Benefits:* + +- All consumers benefit from improved resilience against malformed or hostile TDS responses. A server (or man-in-the-middle) sending an invalid token length can no longer drive the parser to read beyond the declared payload. + +*Impact:* + +- Connections that previously parsed malformed token streams loosely will now fail fast with a clear protocol error. Well-behaved SQL Server responses are unaffected. + +#### Other changes + +- Updated dependencies ([#4387](https://github.com/dotnet/SqlClient/pull/4387)): + - Updated `Microsoft.Identity.Client` to 4.84.2 (was 4.80.0) + - Added `Microsoft.Identity.Client.Broker` 4.84.2 + +### Fixed + +- Fixed a `NullReferenceException` in `SqlDataReader.GetChars` on the PLP + `CommandBehavior.SequentialAccess` path when a `null` buffer was passed together with a negative `bufferIndex`. The call now correctly throws `ArgumentOutOfRangeException` for the `bufferIndex` parameter. + ([#4159](https://github.com/dotnet/SqlClient/pull/4159), [#4205](https://github.com/dotnet/SqlClient/pull/4205)) + +- Fixed column master key (CMK) signature verification caching where `GetSignatureVerificationResult` returned whether the cache key existed rather than the cached value itself. Once a signature verification failure was cached as `false`, subsequent lookups incorrectly returned `true`, causing the caller to skip re-verification and treat the column master key as having a valid signature. + ([#4339](https://github.com/dotnet/SqlClient/pull/4339), [#4356](https://github.com/dotnet/SqlClient/pull/4356)) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64, Windows ARM64) +- .NET 8.0+ (Windows x86, Windows x64, Windows ARM64, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Linux, macOS) + +### Dependencies + +#### .NET Framework 4.6.2 + +- Azure.Core 1.50.0 +- Azure.Identity 1.17.1 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.84.2 +- Microsoft.Identity.Client.Broker 4.84.2 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- System.Buffers 4.6.1 +- System.Data.Common 4.3.0 +- System.Diagnostics.DiagnosticSource 8.0.1 +- System.IdentityModel.Tokens.Jwt 7.7.1 +- System.Memory 4.6.3 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 8.0.6 +- System.Text.RegularExpressions 4.3.1 + +#### .NET 8.0 + +- Azure.Core 1.50.0 +- Azure.Identity 1.17.1 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.84.2 +- Microsoft.Identity.Client.Broker 4.84.2 +- 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.Diagnostics.DiagnosticSource 8.0.1 +- System.IdentityModel.Tokens.Jwt 7.7.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET 9.0 + +- Azure.Core 1.50.0 +- Azure.Identity 1.17.1 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.11 +- Microsoft.Identity.Client 4.84.2 +- Microsoft.Identity.Client.Broker 4.84.2 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.11 +- System.Diagnostics.DiagnosticSource 9.0.11 +- System.IdentityModel.Tokens.Jwt 7.7.1 +- System.Security.Cryptography.Pkcs 9.0.11 + +#### .NET Standard 2.0 + +- Azure.Core 1.50.0 +- Azure.Identity 1.17.1 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.84.2 +- Microsoft.Identity.Client.Broker 4.84.2 +- 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.Diagnostics.DiagnosticSource 8.0.1 +- System.IdentityModel.Tokens.Jwt 7.7.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 8.0.6 diff --git a/release-notes/6.1/README.md b/release-notes/6.1/README.md index ed5770b672..e119d5baf1 100644 --- a/release-notes/6.1/README.md +++ b/release-notes/6.1/README.md @@ -4,6 +4,7 @@ The following Microsoft.Data.SqlClient 6.1 stable releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2026-06-24 | 6.1.6 | [Release Notes](6.1.6.md) | | 2026-04-27 | 6.1.5 | [Release Notes](6.1.5.md) | | 2026-01-15 | 6.1.4 | [Release Notes](6.1.4.md) | | 2025-11-12 | 6.1.3 | [Release Notes](6.1.3.md) | From 08aa6489b6e63c850e8a423847e273b97bb647ec Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Thu, 25 Jun 2026 22:01:50 +0530 Subject: [PATCH 163/298] Add release notes for 7.0.2 (#4403) --- CHANGELOG.md | 45 +++++++ release-notes/7.0/7.0.2.md | 124 ++++++++++++++++++ release-notes/7.0/README.md | 1 + .../Extensions/Abstractions/7.0/7.0.2.md | 21 +++ .../Extensions/Abstractions/7.0/README.md | 10 ++ release-notes/Extensions/Azure/7.0/7.0.2.md | 76 +++++++++++ release-notes/Extensions/Azure/7.0/README.md | 10 ++ release-notes/Internal/Logging/7.0/7.0.2.md | 24 ++++ release-notes/Internal/Logging/7.0/README.md | 13 ++ release-notes/README.md | 9 +- .../AzureKeyVaultProvider/7.0/7.0.2.md | 23 ++++ .../AzureKeyVaultProvider/7.0/README.md | 1 + 12 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 release-notes/7.0/7.0.2.md create mode 100644 release-notes/Extensions/Abstractions/7.0/7.0.2.md create mode 100644 release-notes/Extensions/Abstractions/7.0/README.md create mode 100644 release-notes/Extensions/Azure/7.0/7.0.2.md create mode 100644 release-notes/Extensions/Azure/7.0/README.md create mode 100644 release-notes/Internal/Logging/7.0/7.0.2.md create mode 100644 release-notes/Internal/Logging/7.0/README.md create mode 100644 release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.2.md diff --git a/CHANGELOG.md b/CHANGELOG.md index eb1b0ee034..7725184521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,51 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) > **Note:** Releases are sorted in reverse chronological order (newest first). +## [Stable Release 7.0.2] - 2026-06-24 + +This update brings the following changes since the [7.0.1](release-notes/7.0/7.0.1.md) release. +See the [full release notes](release-notes/7.0/7.0.2.md) for detailed descriptions. + +> **Important — package version alignment:** Starting with 7.0.2, the `Microsoft.Data.SqlClient` driver and its companion packages share a single aligned version. The following packages now ship together as `7.0.2`: +> +> - `Microsoft.Data.SqlClient` +> - `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` +> - `Microsoft.Data.SqlClient.Extensions.Azure` +> - `Microsoft.Data.SqlClient.Extensions.Abstractions` +> - `Microsoft.Data.SqlClient.Internal.Logging` +> +> (`Microsoft.SqlServer.Server` continues to version independently and remains at `1.0.0`.) +> +> Applications must reference the same versions of `Microsoft.Data.SqlClient` and its extensions for best compatibility. In particular, applications that reference `Microsoft.Data.SqlClient.Extensions.Azure` must upgrade it to `7.0.2` when upgrading `Microsoft.Data.SqlClient` to `7.0.2`. + +> **Breaking change (.NET Framework only):** As part of this alignment, the `AssemblyVersion` of `Microsoft.Data.SqlClient.Extensions.Azure`, `Microsoft.Data.SqlClient.Extensions.Abstractions`, and `Microsoft.Data.SqlClient.Internal.Logging` changed from `1.0.0.0` to `7.0.0.0`. On .NET Framework, `AssemblyVersion` is part of the strong-name identity, so applications that drop these assemblies into an existing deployment without rebuilding must rebuild against the 7.0.2 packages (or add binding redirects). Applications on .NET / .NET Core are not affected. `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` already used a `7.x` assembly version and is unaffected. + +### Fixed + +- Fixed a `NullReferenceException` in `SqlCommand.Cancel()` when the active connection has already been torn down. + ([#4372](https://github.com/dotnet/SqlClient/pull/4372), + [#4373](https://github.com/dotnet/SqlClient/pull/4373)) + +- Fixed a `NullReferenceException` in `SqlDataReader.GetBytes`/`GetChars` when called with a `null` destination buffer. + ([#4159](https://github.com/dotnet/SqlClient/pull/4159), + [#4206](https://github.com/dotnet/SqlClient/pull/4206)) + +- Fixed Always Encrypted column master key signature verification incorrectly reusing cached results. + ([#4339](https://github.com/dotnet/SqlClient/pull/4339), + [#4343](https://github.com/dotnet/SqlClient/pull/4343)) + +### Changed + +- Hardened TDS token parsing by adding data-length bounds checks for token and feature-extension-acknowledgment data. + ([#4340](https://github.com/dotnet/SqlClient/pull/4340), + [#4358](https://github.com/dotnet/SqlClient/pull/4358)) + +- Released `Microsoft.Data.SqlClient.Extensions.Azure 7.0.2`, adding WAM broker support for Entra ID authentication modes on Windows. See [release notes](release-notes/Extensions/Azure/7.0/7.0.2.md). + ([#4288](https://github.com/dotnet/SqlClient/pull/4288), + [#4388](https://github.com/dotnet/SqlClient/pull/4388)) + +- Re-shipped `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider`, `Microsoft.Data.SqlClient.Extensions.Abstractions`, and `Microsoft.Data.SqlClient.Internal.Logging` as `7.0.2` (version alignment only, no functional changes). See release notes for [AKV](release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.2.md), [Abstractions](release-notes/Extensions/Abstractions/7.0/7.0.2.md), and [Logging](release-notes/Internal/Logging/7.0/7.0.2.md). + ## [Stable Release 6.1.6] - 2026-06-24 This update brings the following changes since the [6.1.5](release-notes/6.1/6.1.5.md) release. diff --git a/release-notes/7.0/7.0.2.md b/release-notes/7.0/7.0.2.md new file mode 100644 index 0000000000..d1ea72a8db --- /dev/null +++ b/release-notes/7.0/7.0.2.md @@ -0,0 +1,124 @@ +# Release Notes + +## Stable Release 7.0.2 - 2026-06-24 + +This update brings the following changes since the [7.0.1](7.0.1.md) release: + +> **Important — package version alignment:** Starting with 7.0.2, the `Microsoft.Data.SqlClient` driver and its companion packages share a single aligned version. The following packages now ship together as `7.0.2`: +> +> - `Microsoft.Data.SqlClient` +> - `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` +> - `Microsoft.Data.SqlClient.Extensions.Azure` +> - `Microsoft.Data.SqlClient.Extensions.Abstractions` +> - `Microsoft.Data.SqlClient.Internal.Logging` +> +> (`Microsoft.SqlServer.Server` continues to version independently and remains at `1.0.0`.) +> +> Applications must reference the same versions of `Microsoft.Data.SqlClient` and its extensions for best compatibility. In particular, applications that reference `Microsoft.Data.SqlClient.Extensions.Azure` must upgrade it to `7.0.2` when upgrading `Microsoft.Data.SqlClient` to `7.0.2`. + +> **Breaking change (.NET Framework only):** As part of this alignment, the `AssemblyVersion` of `Microsoft.Data.SqlClient.Extensions.Azure`, `Microsoft.Data.SqlClient.Extensions.Abstractions`, and `Microsoft.Data.SqlClient.Internal.Logging` changed from `1.0.0.0` to `7.0.0.0` (the `Microsoft.Data.SqlClient` and `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` assembly versions are unchanged). On .NET Framework, `AssemblyVersion` is part of the strong-name identity, so applications that drop these assemblies into an existing deployment without rebuilding must rebuild against the 7.0.2 packages (or add binding redirects). Applications on .NET / .NET Core are not affected. + +### Fixed + +- Fixed a `NullReferenceException` in `SqlCommand.Cancel()`. The diagnostic message built during cancellation dereferenced the active connection directly; it now uses a null-conditional access so cancellation no longer throws when the connection has already been torn down. + ([#4372](https://github.com/dotnet/SqlClient/pull/4372), + [#4373](https://github.com/dotnet/SqlClient/pull/4373)) + +- Fixed a `NullReferenceException` in `SqlDataReader` when calling `GetBytes`/`GetChars` with a `null` destination buffer. The argument-validation path that constructs the `InvalidDestinationBufferIndex` exception now guards against the null buffer so the correct `ArgumentException` is surfaced instead of an NRE. + ([#4159](https://github.com/dotnet/SqlClient/pull/4159), + [#4206](https://github.com/dotnet/SqlClient/pull/4206)) + +- Fixed Always Encrypted column master key signature verification incorrectly reusing cached results. The `SignatureVerificationCache` lookup logic was corrected so signature verification outcomes are cached and retrieved against the correct key, preventing stale or mismatched verification results. + ([#4339](https://github.com/dotnet/SqlClient/pull/4339), + [#4343](https://github.com/dotnet/SqlClient/pull/4343)) + +### Changed + +#### Hardened TDS token parsing with data-length bounds checks + +*What Changed:* + +- Added bounds checking when parsing TDS token and feature-extension-acknowledgment data lengths. The parser now validates the declared length of incoming token data against the available buffer before reading, rejecting malformed or out-of-range length values instead of reading past the intended boundary. + ([#4340](https://github.com/dotnet/SqlClient/pull/4340), + [#4358](https://github.com/dotnet/SqlClient/pull/4358)) + +*Who Benefits:* + +- All consumers benefit from improved resilience against malformed or hostile TDS responses. A server (or man-in-the-middle) sending an invalid token length can no longer drive the parser to read beyond the declared payload. + +*Impact:* + +- Connections that previously parsed malformed token streams loosely will now fail fast with a clear protocol error. Well-behaved SQL Server responses are unaffected. + +## Contributors + +We thank the following public contributors. Their efforts toward this project are very much appreciated. + +- [SimonCropp](https://github.com/SimonCropp) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64, Windows ARM64) +- .NET 8.0+ (Windows x86, Windows x64, Windows ARM, Windows ARM64, Linux, macOS) + +### Dependencies + +#### .NET 9.0 + +- Microsoft.Bcl.Cryptography 9.0.13 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.0.2 +- Microsoft.Data.SqlClient.Internal.Logging 7.0.2 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.13 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.13 +- System.Security.Cryptography.Pkcs 9.0.13 + +#### .NET 8.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.0.2 +- Microsoft.Data.SqlClient.Internal.Logging 7.0.2 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET Standard 2.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.0.2 +- Microsoft.Data.SqlClient.Internal.Logging 7.0.2 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 + +#### .NET Framework 4.6.2+ + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.0.2 +- Microsoft.Data.SqlClient.Internal.Logging 7.0.2 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- System.Buffers 4.6.1 +- System.Data.Common 4.3.0 +- System.Diagnostics.DiagnosticSource 10.0.3 +- System.Memory 4.6.3 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 +- System.ValueTuple 4.6.2 diff --git a/release-notes/7.0/README.md b/release-notes/7.0/README.md index 5b37ec17d8..aad84057e1 100644 --- a/release-notes/7.0/README.md +++ b/release-notes/7.0/README.md @@ -4,6 +4,7 @@ The following Microsoft.Data.SqlClient 7.0 releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2026-06-24 | 7.0.2 | [Release Notes](7.0.2.md) | | 2026-04-23 | 7.0.1 | [Release Notes](7.0.1.md) | | 2026-03-17 | 7.0.0 | [Release Notes](7.0.0.md) | | 2026-03-05 | 7.0.0-preview4.26064.3 | [Release Notes](7.0.0-preview4.md) | diff --git a/release-notes/Extensions/Abstractions/7.0/7.0.2.md b/release-notes/Extensions/Abstractions/7.0/7.0.2.md new file mode 100644 index 0000000000..cf742d77dd --- /dev/null +++ b/release-notes/Extensions/Abstractions/7.0/7.0.2.md @@ -0,0 +1,21 @@ +# Release Notes + +## Stable Release 7.0.2 - 2026-06-24 + +This release version-aligns `Microsoft.Data.SqlClient.Extensions.Abstractions` with the core `Microsoft.Data.SqlClient` driver version (`7.0.2`). The previous release of this package was `1.0.0`. + +There are no functional or API changes in this release. See the core [Microsoft.Data.SqlClient 7.0.2](../../../7.0/7.0.2.md) release notes for the driver-family changes shipped alongside this version. + +> **Version alignment:** Starting with this release, this package's version tracks the core `Microsoft.Data.SqlClient` driver version. + +> **Breaking change (.NET Framework only):** As part of this alignment, the assembly's `AssemblyVersion` changed from `1.0.0.0` to `7.0.0.0`. On .NET Framework, `AssemblyVersion` is part of the strong-name identity, so applications that replace this assembly in an existing deployment without rebuilding must rebuild against `7.0.2` (or add a binding redirect). Applications on .NET / .NET Core are not affected. + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- Microsoft.Data.SqlClient.Internal.Logging 7.0.2 diff --git a/release-notes/Extensions/Abstractions/7.0/README.md b/release-notes/Extensions/Abstractions/7.0/README.md new file mode 100644 index 0000000000..24f9eff90a --- /dev/null +++ b/release-notes/Extensions/Abstractions/7.0/README.md @@ -0,0 +1,10 @@ +# Microsoft.Data.SqlClient.Extensions.Abstractions 7.0 Releases + +The following `Microsoft.Data.SqlClient.Extensions.Abstractions` +7.0 releases have been shipped: + +> **Breaking change (.NET Framework only):** Starting with 7.0.2, the assembly's `AssemblyVersion` is `7.0.0.0` (previously `1.0.0.0`). On .NET Framework this changes the strong-name identity, so applications that replace this assembly without rebuilding must rebuild against 7.0.2 or add a binding redirect. Applications on .NET / .NET Core are not affected. + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2026-06-24 | 7.0.2 | [Release Notes](7.0.2.md) | diff --git a/release-notes/Extensions/Azure/7.0/7.0.2.md b/release-notes/Extensions/Azure/7.0/7.0.2.md new file mode 100644 index 0000000000..c4945d83c8 --- /dev/null +++ b/release-notes/Extensions/Azure/7.0/7.0.2.md @@ -0,0 +1,76 @@ +# Release Notes + +## Stable Release 7.0.2 - 2026-06-24 + +This update brings the following changes since the [1.0.0](../1.0/1.0.0.md) release of `Microsoft.Data.SqlClient.Extensions.Azure`. + +> **Version alignment:** Starting with this release, the package version is aligned with the core `Microsoft.Data.SqlClient` driver version (`7.0.2`). The previous release of this package was `1.0.0`. + +> **Breaking change (.NET Framework only):** As part of this alignment, the assembly's `AssemblyVersion` changed from `1.0.0.0` to `7.0.0.0`. On .NET Framework, `AssemblyVersion` is part of the strong-name identity, so applications that replace this assembly in an existing deployment without rebuilding must rebuild against `7.0.2` (or add a binding redirect). Applications on .NET / .NET Core are not affected. + +### Added + +#### WAM broker support for Entra ID authentication (Windows only) + +*What Changed:* + +- Added support for the [Web Account Manager (WAM)](https://learn.microsoft.com/entra/msal/dotnet/acquiring-tokens/desktop-mobile/wam) broker for Microsoft Entra ID authentication flows. A new `ActiveDirectoryAuthenticationProviderOptions` options bag and a corresponding `ActiveDirectoryAuthenticationProvider(ActiveDirectoryAuthenticationProviderOptions options)` constructor were introduced, exposing a `UseWamBroker` property (alongside `ApplicationClientId` and `DeviceCodeFlowCallback`). +- Added a cross-platform `SetParentActivityOrWindowFunc(Func?)` method so callers can supply the parent window/activity used for interactive authentication UI — a window handle on Windows (where it also integrates with the WAM broker), or an `Activity`/`UIViewController` on Android/iOS. This supersedes the .NET Framework-only `SetIWin32WindowFunc`. + ([#4288](https://github.com/dotnet/SqlClient/pull/4288), + [#4388](https://github.com/dotnet/SqlClient/pull/4388)) + +*Who Benefits:* + +- Applications using `ActiveDirectoryInteractive` and other supported Entra ID authentication modes on Windows benefit from the WAM broker's improved security (tokens are brokered by the OS), single sign-on with the logged-in Windows account, and support for Conditional Access and Windows Hello. + +*Impact:* + +- Enable the WAM broker by setting `ActiveDirectoryAuthenticationProviderOptions.UseWamBroker` to `true` and passing the options to the new constructor. Consider enabling it when you want OS-brokered token security, single sign-on with the signed-in Windows account, or support for Conditional Access and Windows Hello. +- `UseWamBroker` is a **Windows-only** setting and has no effect on non-Windows platforms, where interactive Entra ID flows always use the system browser. +- Prefer the new options-bag constructor over the positional-argument overloads in new code. + +```c# +var options = new ActiveDirectoryAuthenticationProviderOptions +{ + ApplicationClientId = "", + UseWamBroker = true, // Windows only +}; +var provider = new ActiveDirectoryAuthenticationProvider(options); +SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, provider); +``` + +- A new `AzureSqlConnector` sample demonstrating interactive Entra ID authentication with and without the WAM broker was added under [doc/apps/AzureSqlConnector](../../../../doc/apps/AzureSqlConnector/README.md). + +### Changed + +- Updated Dependencies + ([#4288](https://github.com/dotnet/SqlClient/pull/4288)): + - Added `Microsoft.Identity.Client.Broker` v4.84.2 (required for WAM broker support). + - Updated `Microsoft.Identity.Client` to v4.84.2. + +## Target Platform Support + +- .NET Standard 2.0 +- .NET Framework 4.6.2+ + +### Dependencies + +#### .NET Standard 2.0 + +- Azure.Core 1.51.1 +- Azure.Identity 1.18.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.0.2 +- Microsoft.Data.SqlClient.Internal.Logging 7.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.84.2 +- Microsoft.Identity.Client.Broker 4.84.2 + +#### .NET Framework 4.6.2+ + +- Azure.Core 1.51.1 +- Azure.Identity 1.18.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.0.2 +- Microsoft.Data.SqlClient.Internal.Logging 7.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.84.2 +- Microsoft.Identity.Client.Broker 4.84.2 diff --git a/release-notes/Extensions/Azure/7.0/README.md b/release-notes/Extensions/Azure/7.0/README.md new file mode 100644 index 0000000000..93b90c690d --- /dev/null +++ b/release-notes/Extensions/Azure/7.0/README.md @@ -0,0 +1,10 @@ +# Microsoft.Data.SqlClient.Extensions.Azure 7.0 Releases + +The following `Microsoft.Data.SqlClient.Extensions.Azure` +7.0 releases have been shipped: + +> **Breaking change (.NET Framework only):** Starting with 7.0.2, the assembly's `AssemblyVersion` is `7.0.0.0` (previously `1.0.0.0`). On .NET Framework this changes the strong-name identity, so applications that replace this assembly without rebuilding must rebuild against 7.0.2 or add a binding redirect. Applications on .NET / .NET Core are not affected. + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2026-06-24 | 7.0.2 | [Release Notes](7.0.2.md) | diff --git a/release-notes/Internal/Logging/7.0/7.0.2.md b/release-notes/Internal/Logging/7.0/7.0.2.md new file mode 100644 index 0000000000..5a1873006d --- /dev/null +++ b/release-notes/Internal/Logging/7.0/7.0.2.md @@ -0,0 +1,24 @@ +# Release Notes + +## Stable Release 7.0.2 - 2026-06-24 + +> **Note:** This package is for internal use by other Microsoft.Data.SqlClient packages only +> and should not be referenced directly by application code. + +This release version-aligns `Microsoft.Data.SqlClient.Internal.Logging` with the core `Microsoft.Data.SqlClient` driver version (`7.0.2`). The previous release of this package was `1.0.0`. + +There are no functional or API changes in this release. See the core [Microsoft.Data.SqlClient 7.0.2](../../../7.0/7.0.2.md) release notes for the driver-family changes shipped alongside this version. + +> **Version alignment:** Starting with this release, this package's version tracks the core `Microsoft.Data.SqlClient` driver version. + +> **Breaking change (.NET Framework only):** As part of this alignment, the assembly's `AssemblyVersion` changed from `1.0.0.0` to `7.0.0.0`. On .NET Framework, `AssemblyVersion` is part of the strong-name identity, so applications that replace this assembly in an existing deployment without rebuilding must rebuild against `7.0.2` (or add a binding redirect). Applications on .NET / .NET Core are not affected. + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- None diff --git a/release-notes/Internal/Logging/7.0/README.md b/release-notes/Internal/Logging/7.0/README.md new file mode 100644 index 0000000000..403c61db07 --- /dev/null +++ b/release-notes/Internal/Logging/7.0/README.md @@ -0,0 +1,13 @@ +# Microsoft.Data.SqlClient.Internal.Logging 7.0 Releases + +> **Note:** This package is for internal use by other Microsoft.Data.SqlClient packages only +> and should not be referenced directly by application code. + +The following `Microsoft.Data.SqlClient.Internal.Logging` +7.0 releases have been shipped: + +> **Breaking change (.NET Framework only):** Starting with 7.0.2, the assembly's `AssemblyVersion` is `7.0.0.0` (previously `1.0.0.0`). On .NET Framework this changes the strong-name identity, so applications that replace this assembly without rebuilding must rebuild against 7.0.2 or add a binding redirect. Applications on .NET / .NET Core are not affected. + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2026-06-24 | 7.0.2 | [Release Notes](7.0.2.md) | diff --git a/release-notes/README.md b/release-notes/README.md index f75cc4dfb9..dc90822a2d 100644 --- a/release-notes/README.md +++ b/release-notes/README.md @@ -40,29 +40,32 @@ The latest stable release is # Microsoft.Data.SqlClient.Extensions.Abstractions Release Notes The latest release is -[Microsoft.Data.SqlClient.Extensions.Abstractions 1.0](Extensions/Abstractions/1.0). +[Microsoft.Data.SqlClient.Extensions.Abstractions 7.0](Extensions/Abstractions/7.0). ## Release Information +- [Microsoft.Data.SqlClient.Extensions.Abstractions 7.0](Extensions/Abstractions/7.0) - [Microsoft.Data.SqlClient.Extensions.Abstractions 1.0](Extensions/Abstractions/1.0) # Microsoft.Data.SqlClient.Extensions.Azure Release Notes The latest release is -[Microsoft.Data.SqlClient.Extensions.Azure 1.0](Extensions/Azure/1.0). +[Microsoft.Data.SqlClient.Extensions.Azure 7.0](Extensions/Azure/7.0). ## Release Information +- [Microsoft.Data.SqlClient.Extensions.Azure 7.0](Extensions/Azure/7.0) - [Microsoft.Data.SqlClient.Extensions.Azure 1.0](Extensions/Azure/1.0) # Microsoft.Data.SqlClient.Internal.Logging Release Notes The latest release is -[Microsoft.Data.SqlClient.Internal.Logging 1.0](Internal/Logging/1.0). +[Microsoft.Data.SqlClient.Internal.Logging 7.0](Internal/Logging/7.0). ## Release Information +- [Microsoft.Data.SqlClient.Internal.Logging 7.0](Internal/Logging/7.0) - [Microsoft.Data.SqlClient.Internal.Logging 1.0](Internal/Logging/1.0) # Microsoft.SqlServer.Server Release Notes diff --git a/release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.2.md b/release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.2.md new file mode 100644 index 0000000000..0e3f0c43de --- /dev/null +++ b/release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.2.md @@ -0,0 +1,23 @@ +# Release Notes + +## Stable Release 7.0.2 - 2026-06-24 + +This release version-aligns `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` with the core `Microsoft.Data.SqlClient` driver version (`7.0.2`). The previous release of this package was [7.0.0](7.0.0.md); the package was not re-shipped for the `7.0.1` hotfix. + +There are no functional or API changes in this release. See the core [Microsoft.Data.SqlClient 7.0.2](../../../7.0/7.0.2.md) release notes for the driver-family changes shipped alongside this version. + +> **Version alignment:** Starting with this release, this package's version tracks the core `Microsoft.Data.SqlClient` driver version. + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- Azure.Core 1.51.1 +- Azure.Security.KeyVault.Keys 4.9.0 +- Microsoft.Data.SqlClient 7.0.2 +- Microsoft.Data.SqlClient.Internal.Logging 7.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 diff --git a/release-notes/add-ons/AzureKeyVaultProvider/7.0/README.md b/release-notes/add-ons/AzureKeyVaultProvider/7.0/README.md index 22593ef05c..a51fe94635 100644 --- a/release-notes/add-ons/AzureKeyVaultProvider/7.0/README.md +++ b/release-notes/add-ons/AzureKeyVaultProvider/7.0/README.md @@ -5,5 +5,6 @@ The following `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` | Release Date | Description | Notes | | :-- | :-- | :--: | +| 2026-06-24 | 7.0.2 | [Release Notes](7.0.2.md) | | 2026-03-17 | 7.0.0 | [Release Notes](7.0.0.md) | | 2026-03-05 | 7.0.0-preview1.26064.3 | [Release Notes](7.0.0-preview1.md) | From 56d2b5638e5057cf445ea9a6d356ddd34e1dc57c Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Thu, 25 Jun 2026 12:14:12 -0700 Subject: [PATCH 164/298] Upgrade gh aw workflows (#4406) * Upgrade gh aw workflows * Update verify action --- ...orkflows.agent.md => agentic-workflows.md} | 66 +- .github/aw/actions-lock.json | 23 +- .github/skills/agentic-workflows/SKILL.md | 80 +++ .github/workflows/copilot-setup-steps.yml | 4 +- .github/workflows/issue-triage.lock.yml | 607 ++++++++++++------ .github/workflows/verify-aw-lock.yml | 4 +- 6 files changed, 562 insertions(+), 222 deletions(-) rename .github/agents/{agentic-workflows.agent.md => agentic-workflows.md} (69%) create mode 100644 .github/skills/agentic-workflows/SKILL.md diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.md similarity index 69% rename from .github/agents/agentic-workflows.agent.md rename to .github/agents/agentic-workflows.md index cd376c5b1b..b824e60580 100644 --- a/.github/agents/agentic-workflows.agent.md +++ b/.github/agents/agentic-workflows.md @@ -1,5 +1,6 @@ --- -description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing +name: Agentic Workflows +description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing. disable-model-invocation: true --- @@ -13,13 +14,16 @@ This is a **dispatcher agent** that routes your request to the appropriate speci - **Creating new workflows**: Routes to `create` prompt - **Updating existing workflows**: Routes to `update` prompt -- **Debugging workflows**: Routes to `debug` prompt +- **Debugging workflows**: Routes to `debug` prompt - **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt - **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments - **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt - **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes - **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs +- **Rendering ASCII charts in markdown**: Routes to `asciicharts` guide — consult this whenever the workflow needs compact charts that render reliably in GitHub issues, comments, or discussions - **CLI commands and triggering workflows**: Routes to `cli-commands` guide — consult this whenever the user asks how to run, compile, debug, or manage workflows from the command line, or when they need the MCP tool equivalent of a `gh aw` command +- **Reducing token consumption / cost optimization**: Routes to `token-optimization` guide — consult this whenever the user asks how to reduce token usage, lower costs, speed up workflows, or measure the impact of prompt changes with experiments +- **Choosing workflow architectures and design patterns**: Routes to `patterns` guide — consult this whenever the user asks for strategy, architecture, operating models, or pattern selection for agentic workflows Workflows may optionally include: @@ -31,7 +35,7 @@ Workflows may optionally include: - Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` - Workflow lock files: `.github/workflows/*.lock.yml` - Shared components: `.github/workflows/shared/*.md` -- Configuration: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/github-agentic-workflows.md +- Configuration: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/github-agentic-workflows.md` ## Problems This Solves @@ -50,30 +54,32 @@ When you interact with this agent, it will: ## Available Prompts +> **Note**: The prompt and reference files listed below are located in the [`github/gh-aw`](https://github.com/github/gh-aw) repository and are **not available locally** in this repository. Load them from their public URLs. + ### Create New Workflow **Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/create-agentic-workflow.md +**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/create-agentic-workflow.md` **Use cases**: - "Create a workflow that triages issues" - "I need a workflow to label pull requests" - "Design a weekly research automation" -### Update Existing Workflow +### Update Existing Workflow **Load when**: User wants to modify, improve, or refactor an existing workflow -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/update-agentic-workflow.md +**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/update-agentic-workflow.md` **Use cases**: - "Add web-fetch tool to the issue-classifier workflow" - "Update the PR reviewer to use discussions instead of issues" - "Improve the prompt for the weekly-research workflow" -### Debug Workflow +### Debug Workflow **Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/debug-agentic-workflow.md +**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/debug-agentic-workflow.md` **Use cases**: - "Why is this workflow failing?" @@ -83,7 +89,7 @@ When you interact with this agent, it will: ### Upgrade Agentic Workflows **Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/upgrade-agentic-workflows.md +**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/upgrade-agentic-workflows.md` **Use cases**: - "Upgrade all workflows to the latest version" @@ -93,7 +99,7 @@ When you interact with this agent, it will: ### Create a Report-Generating Workflow **Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/report.md +**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/report.md` **Use cases**: - "Create a weekly CI health report" @@ -103,7 +109,7 @@ When you interact with this agent, it will: ### Create Shared Agentic Workflow **Load when**: User wants to create a reusable workflow component or wrap an MCP server -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/create-shared-agentic-workflow.md +**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/create-shared-agentic-workflow.md` **Use cases**: - "Create a shared component for Notion integration" @@ -113,7 +119,7 @@ When you interact with this agent, it will: ### Fix Dependabot PRs **Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/dependabot.md +**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/dependabot.md` **Use cases**: - "Fix the open Dependabot PRs for npm dependencies" @@ -123,7 +129,7 @@ When you interact with this agent, it will: ### Analyze Test Coverage **Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. -**Prompt file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/test-coverage.md +**Prompt file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/test-coverage.md` **Use cases**: - "Create a workflow that comments coverage on PRs" @@ -133,7 +139,7 @@ When you interact with this agent, it will: ### CLI Commands Reference **Load when**: The user asks how to run, compile, debug, or manage workflows from the command line; needs the MCP tool equivalent of a `gh aw` command; or is in a restricted environment (e.g., Copilot Cloud) without direct CLI access. -**Reference file**: https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/cli-commands.md +**Reference file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/cli-commands.md` **Use cases**: - "How do I trigger workflow X on the main branch?" @@ -141,12 +147,36 @@ When you interact with this agent, it will: - "I'm in Copilot Cloud — how do I compile a workflow?" - "Show me all available gh aw commands" +### Token Consumption Optimization +**Load when**: The user asks how to reduce token usage, lower workflow costs, make a workflow faster or cheaper, or measure the impact of prompt or configuration changes. + +**Reference file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/token-optimization.md` + +**Use cases**: +- "How do I reduce the token cost of this workflow?" +- "My workflow is too expensive — how do I optimize it?" +- "How do I compare token usage between two runs?" +- "Should I use gh-proxy or the MCP server?" +- "How do I use sub-agents to reduce costs?" +- "How do I measure the impact of a prompt change?" + +### Workflow Pattern Selection +**Load when**: The user asks for architecture, strategy, operating model selection, or pattern recommendations for building agentic workflows. + +**Reference file**: `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/patterns.md` + +**Use cases**: +- "Which pattern should I use for multi-repo rollout?" +- "How should I structure this workflow architecture?" +- "What pattern fits slash-command triage?" +- "Should this be DispatchOps or DailyOps?" + ## Instructions When a user interacts with you: 1. **Identify the task type** from the user's request -2. **Load the appropriate prompt** from the GitHub repository URLs listed above +2. **Load the appropriate prompt** from the URLs listed above 3. **Follow the loaded prompt's instructions** exactly 4. **If uncertain**, ask clarifying questions to determine the right prompt @@ -185,12 +215,12 @@ gh aw compile --validate ## Important Notes -- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/github-agentic-workflows.md for complete documentation +- Always reference the instructions file at `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/github-agentic-workflows.md` for complete documentation - Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud - Workflows must be compiled to `.lock.yml` files before running in GitHub Actions - **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF - Follow security best practices: minimal permissions, explicit network access, no template injection -- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. +- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/network.md` for the full list of valid ecosystem identifiers and domain patterns. - **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. - **Triggering runs**: Always use `gh aw run ` to trigger a workflow on demand — not `gh workflow run .lock.yml`. `gh aw run` handles workflow resolution by short name, input parsing and validation, and correct run-tracking for agentic workflows. Use `--ref ` to run on a specific branch. -- **CLI commands reference**: For a complete guide on all `gh aw` commands and their MCP tool equivalents (for restricted environments), see https://github.com/github/gh-aw/blob/v0.72.1/.github/aw/cli-commands.md +- **CLI commands reference**: For a complete guide on all `gh aw` commands and their MCP tool equivalents (for restricted environments), see `https://raw.githubusercontent.com/github/gh-aw/main/.github/aw/cli-commands.md` diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 79ade5bd6f..64a0a0a93a 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -5,27 +5,10 @@ "version": "v9.0.0", "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" }, - "github/gh-aw-actions/setup@v0.72.1": { + "github/gh-aw-actions/setup@v0.80.9": { "repo": "github/gh-aw-actions/setup", - "version": "v0.72.1", - "sha": "bc56a0cad2f450c562810785ef38649c04db812a" - } - }, - "containers": { - "ghcr.io/github/gh-aw-firewall/agent:0.25.41": { - "image": "ghcr.io/github/gh-aw-firewall/agent:0.25.41", - "digest": "sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770", - "pinned_image": "ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770" - }, - "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41": { - "image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41", - "digest": "sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0", - "pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0" - }, - "ghcr.io/github/gh-aw-firewall/squid:0.25.41": { - "image": "ghcr.io/github/gh-aw-firewall/squid:0.25.41", - "digest": "sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4", - "pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4" + "version": "v0.80.9", + "sha": "8c7d04ebf1ece56cd381446125da3e0f6896294a" } } } diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md new file mode 100644 index 0000000000..ee714d339d --- /dev/null +++ b/.github/skills/agentic-workflows/SKILL.md @@ -0,0 +1,80 @@ +--- +name: agentic-workflows +description: Route gh-aw workflow design/create/debug/upgrade requests to the right prompts. +--- + +# Agentic Workflows Router + +Use this skill when a user asks to design, create, update, debug, or upgrade GitHub Agentic Workflows in this repository. + +This skill is a dispatcher: identify the task type, load the matching workflow prompt/skill file, and follow it directly. Keep responses concise and ask a clarifying question if the correct prompt is unclear. + +Read only the files you need: +Load these files from `github/gh-aw` (they are not available locally). +- `.github/aw/agentic-chat.md` +- `.github/aw/agentic-workflows-mcp.md` +- `.github/aw/asciicharts.md` +- `.github/aw/campaign.md` +- `.github/aw/charts-trending.md` +- `.github/aw/charts.md` +- `.github/aw/cli-commands.md` +- `.github/aw/context.md` +- `.github/aw/create-agentic-workflow.md` +- `.github/aw/create-shared-agentic-workflow.md` +- `.github/aw/debug-agentic-workflow.md` +- `.github/aw/dependabot.md` +- `.github/aw/deployment-status.md` +- `.github/aw/experiments.md` +- `.github/aw/github-agentic-workflows.md` +- `.github/aw/github-mcp-server.md` +- `.github/aw/llms.md` +- `.github/aw/mcp-clis.md` +- `.github/aw/memory.md` +- `.github/aw/messages.md` +- `.github/aw/network.md` +- `.github/aw/optimize-agentic-workflow.md` +- `.github/aw/patterns.md` +- `.github/aw/pr-reviewer.md` +- `.github/aw/report.md` +- `.github/aw/reuse.md` +- `.github/aw/safe-outputs-automation.md` +- `.github/aw/safe-outputs-content.md` +- `.github/aw/safe-outputs-management.md` +- `.github/aw/safe-outputs-runtime.md` +- `.github/aw/safe-outputs.md` +- `.github/aw/serena-tool.md` +- `.github/aw/shared-safe-jobs.md` +- `.github/aw/skills.md` +- `.github/aw/subagents.md` +- `.github/aw/syntax-agentic.md` +- `.github/aw/syntax-core.md` +- `.github/aw/syntax-tools-imports.md` +- `.github/aw/syntax.md` +- `.github/aw/test-coverage.md` +- `.github/aw/test-expression.md` +- `.github/aw/token-optimization.md` +- `.github/aw/triggers.md` +- `.github/aw/update-agentic-workflow.md` +- `.github/aw/upgrade-agentic-workflows.md` +- `.github/aw/visual-regression.md` +- `.github/aw/workflow-constraints.md` +- `.github/aw/workflow-editing.md` +- `.github/aw/workflow-patterns.md` + +- `.github/skills/agentic-workflow-designer/SKILL.md` +After loading the matching workflow prompt or skill, follow it directly: +- Design workflows from scratch via interview: `skills/agentic-workflow-designer/SKILL.md` +- Create new workflows: `.github/aw/create-agentic-workflow.md` +- Update existing workflows: `.github/aw/update-agentic-workflow.md` +- Debug, audit, or investigate workflows: `.github/aw/debug-agentic-workflow.md` +- Upgrade workflows and fix deprecations: `.github/aw/upgrade-agentic-workflows.md` +- Create shared components or MCP wrappers: `.github/aw/create-shared-agentic-workflow.md` +- Create report-generating workflows: `.github/aw/report.md` +- Fix Dependabot manifest PRs: `.github/aw/dependabot.md` +- Analyze coverage workflows: `.github/aw/test-coverage.md` +- Render compact markdown charts: `.github/aw/asciicharts.md` +- Map CLI commands to MCP usage: `.github/aw/cli-commands.md` +- Choose workflow architecture and patterns: `.github/aw/patterns.md` +- Optimize token usage and cost: `.github/aw/token-optimization.md` + +When the task involves OTEL, OTLP, traces, observability backends, or telemetry-driven analysis, also read and follow `skills/otel-queries/SKILL.md` after loading the matching workflow prompt or skill. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 0ae42f6f5c..90ca026d37 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -21,6 +21,6 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - name: Install gh-aw extension - uses: github/gh-aw-actions/setup-cli@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup-cli@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: - version: v0.72.1 + version: v0.80.9 diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 468ce93488..886e8e2e63 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,5 +1,7 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c9a288bea873cc74dd33e81e0f5784e167d990ee0e2645c1722106b45ae1f77d","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41","digest":"sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41","digest":"sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41","digest":"sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c9a288bea873cc74dd33e81e0f5784e167d990ee0e2645c1722106b45ae1f77d","body_hash":"99b21e9ba167d3bfbcfaeb3b04c515fc42b56679f0ee175c119bc5843f034d31","compiler_version":"v0.80.9","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.63"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8c7d04ebf1ece56cd381446125da3e0f6896294a","version":"v0.80.9"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.7","digest":"sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7","digest":"sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.7","digest":"sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.27","digest":"sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.80.9). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +16,6 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.72.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -30,23 +31,25 @@ # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 +# - github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 -# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f +# - ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 +# - ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7 +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "SqlClient Issue Auto-Triage" -"on": +on: issue_comment: types: - created @@ -82,13 +85,20 @@ jobs: permissions: actions: read contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} text: ${{ steps.sanitized.outputs.text }} @@ -96,30 +106,33 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.72.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AGENT_VERSION: "1.0.63" + GH_AW_INFO_CLI_VERSION: "v0.80.9" GH_AW_INFO_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.41" + GH_AW_INFO_AWF_VERSION: "v0.27.7" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -130,13 +143,58 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuetriage-${{ github.run_id }} + restore-keys: agentic-workflow-usage-issuetriage- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_WORKFLOW_ID: "issue-triage" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); + await main(); - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false sparse-checkout: | .github .agents + .antigravity .claude .codex .crush @@ -147,8 +205,8 @@ jobs: fetch-depth: 1 - name: Save agent config folders for base branch restoration env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" # poutine:ignore untrusted_checkout_exec run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file @@ -166,7 +224,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.72.1" + GH_AW_COMPILED_VERSION: "v0.80.9" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -188,11 +246,11 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -201,57 +259,57 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_671235e4d67f4971_EOF' + cat << 'GH_AW_PROMPT_09a4b5a8a8b33a81_EOF' - GH_AW_PROMPT_671235e4d67f4971_EOF + GH_AW_PROMPT_09a4b5a8a8b33a81_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_671235e4d67f4971_EOF' + cat << 'GH_AW_PROMPT_09a4b5a8a8b33a81_EOF' Tools: add_comment, add_labels, remove_labels, missing_tool, missing_data, noop - GH_AW_PROMPT_671235e4d67f4971_EOF + GH_AW_PROMPT_09a4b5a8a8b33a81_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_671235e4d67f4971_EOF' + cat << 'GH_AW_PROMPT_09a4b5a8a8b33a81_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - GH_AW_PROMPT_671235e4d67f4971_EOF + GH_AW_PROMPT_09a4b5a8a8b33a81_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_671235e4d67f4971_EOF' + cat << 'GH_AW_PROMPT_09a4b5a8a8b33a81_EOF' {{#runtime-import .github/workflows/issue-triage.md}} - GH_AW_PROMPT_671235e4d67f4971_EOF + GH_AW_PROMPT_09a4b5a8a8b33a81_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -268,11 +326,11 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -289,11 +347,11 @@ jobs: return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, @@ -319,17 +377,20 @@ jobs: include-hidden-files: true path: | /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/aw-prompts/prompt-template.txt /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills if-no-files-found: ignore retention-days: 1 agent: needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest environment: issue-triage permissions: @@ -344,29 +405,38 @@ jobs: GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs GH_AW_WORKFLOW_ID_SANITIZED: issuetriage outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -376,7 +446,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Create gh-aw temp directory @@ -387,21 +457,14 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -413,11 +476,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.7 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -433,24 +496,28 @@ jobs: - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Restore inline sub-agents from activation artifact env: GH_AW_SUB_AGENT_DIR: ".github/agents" GH_AW_SUB_AGENT_EXT: ".agent.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2171a9d22dc2eb73_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_723da9f6aa534eb7_EOF' {"add_comment":{"hide_older_comments":true,"max":1},"add_labels":{"allowed":["Auto-Triage: Waiting for Author"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"remove_labels":{"allowed":["Auto-Triage: Waiting for Author"],"max":1},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_2171a9d22dc2eb73_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_723da9f6aa534eb7_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -606,53 +673,14 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -672,17 +700,22 @@ jobs: export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.27' - mkdir -p /home/runner/.copilot + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e021cf5fa19f3aa2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4b806ec8e0233008_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -700,10 +733,26 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -721,7 +770,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_e021cf5fa19f3aa2_EOF + GH_AW_MCP_CONFIG_4b806ec8e0233008_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -749,25 +798,65 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41,squid=sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4,agent=sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770,api-proxy=sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.7/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.7,squid=sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96,agent=sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c,api-proxy=sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6,cli-proxy=sha256:4757f198a3fa20f88bdbe70be7ae1a05f127d9c0a9e96a5d6460ef40c08fc83d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + python3 - <<'PY' + import json,os,subprocess as sp + from pathlib import Path + try: + p=Path(os.environ["RUNNER_TEMP"])/"gh-aw"/"awf-config.json" + c=json.loads(p.read_text()) + c["chroot"]={"binariesSourcePath":"/tmp/gh-aw","identity":{"user":sp.check_output(["id","-un"],text=True).strip(),"uid":int(sp.check_output(["id","-u"],text=True)),"gid":int(sp.check_output(["id","-g"],text=True)),"home":"/tmp/gh-aw/home"}} + out=json.dumps(c,separators=(",",":"),ensure_ascii=False)+"\n" + p.write_text(out) + Path("/tmp/gh-aw/awf-config.json").write_text(out) + except Exception as e: + raise SystemExit(f"chroot config patch failed: {e}") from e + PY + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.72.1 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.80.9 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -781,25 +870,19 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors + RUNNER_TEMP: ${{ runner.temp }} + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Detect agent errors if: always() + id: detect-agent-errors continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -951,17 +1034,17 @@ jobs: - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim environment: issue-triage permissions: contents: read - discussions: write issues: write pull-requests: write concurrency: group: "gh-aw-conclusion-issue-triage" cancel-in-progress: false + queue: max outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -970,15 +1053,18 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -993,6 +1079,86 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuetriage-${{ github.run_id }} + restore-keys: agentic-workflow-usage-issuetriage- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuetriage-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1000,9 +1166,14 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "false" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "issue-triage" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1016,6 +1187,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} @@ -1033,6 +1205,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1047,6 +1220,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1061,12 +1235,19 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "issue-triage" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1074,6 +1255,9 @@ jobs: GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" @@ -1094,24 +1278,29 @@ jobs: if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest + environment: issue-triage permissions: contents: read outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1128,7 +1317,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1137,7 +1326,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41@sha256:cb2b565d070116d4b67e355775340528b5a2c3cb18b2c9049638bcc2df681770 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41@sha256:fadd0de387209f69a9a7a1b8722bb5e7fdfb80ba9749a5c60f0e4cd7582a74d0 ghcr.io/github/gh-aw-firewall/squid:0.25.41@sha256:1260445d25968dbf3ae70143964177a0e5914cf2ce07a6117f7d3caec6c3e3c4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 - name: Check if detection needed id: detection_guard if: always() @@ -1156,13 +1345,17 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json + rm -f "$HOME/.copilot/mcp-config.json" rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then + echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." + fi cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true for f in /tmp/gh-aw/aw-*.patch; do [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true @@ -1196,11 +1389,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.7 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1209,23 +1402,53 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.7/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.7,squid=sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96,agent=sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c,api-proxy=sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6,cli-proxy=sha256:4757f198a3fa20f88bdbe70be7ae1a05f127d9c0a9e96a5d6460ef40c08fc83d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.72.1 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.80.9 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1238,7 +1461,21 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner + RUNNER_TEMP: ${{ runner.temp }} + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -1253,6 +1490,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | @@ -1263,10 +1501,11 @@ jobs: await main(); } catch (loadErr) { const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); core.error(msg); core.setOutput('reason', 'parse_error'); - if (continueOnError) { + if (continueOnError && !detectionExecutionFailed) { core.warning('\u26A0\uFE0F ' + msg); core.setOutput('conclusion', 'warning'); core.setOutput('success', 'false'); @@ -1287,20 +1526,24 @@ jobs: environment: issue-triage permissions: contents: read - discussions: write issues: write pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-triage" GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_ENGINE_VERSION: "1.0.63" + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "issue-triage" GH_AW_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-triage.md" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} @@ -1313,15 +1556,18 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "SqlClient Issue Auto-Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1339,7 +1585,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1350,6 +1596,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} diff --git a/.github/workflows/verify-aw-lock.yml b/.github/workflows/verify-aw-lock.yml index 1d37e610e8..1381792980 100644 --- a/.github/workflows/verify-aw-lock.yml +++ b/.github/workflows/verify-aw-lock.yml @@ -16,9 +16,9 @@ jobs: - uses: actions/checkout@v4 - name: Install gh-aw extension - uses: github/gh-aw-actions/setup-cli@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup-cli@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: - version: v0.72.1 + version: v0.80.9 - name: Recompile agentic workflows run: gh aw compile From 3795144b1f96c7b8b7f39ec4f924412488f5dd61 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:20:37 +0200 Subject: [PATCH 165/298] Tests | Address additional flaky tests (#4305) * Update LocalAppContextSwitchesTest to use RAII helper This prevents checks of the default AppContext switch values which would run in parallel to other tests which modify these values via the RAII helper. * Address duplicate AKV key names * Swap System.Random with the same RNG which SqlClient uses. * Make a fixed number of attempts to reattempt key creation if a key with the generated name already exists. * Remove hardcoded stored procedure name * Accept either a SqlException or an InvalidOperationException to indicate operation cancellation * Update assertion for UseLegacyFailoverAlternationOnLoginSqlErrors * Correct test failures LocalAppContextSwitchesHelper was returning the field values, skipping the default values exposed by LocalAppContextSwitches when the AppContext switch had not been set one way or another. * Modify AppContext switch helper to include UseOverallConnectTimeoutForPoolWait --------- Co-authored-by: Malcolm Daigle Co-authored-by: Malcolm Daigle --- .../Fixtures/AzureKeyVaultKeyFixtureBase.cs | 34 +++++- .../Common/LocalAppContextSwitchesHelper.cs | 109 +++++++++++------- .../tests/ManualTests/SQL/Batch/BatchTests.cs | 56 ++------- .../SQL/SqlCommand/SqlCommandCancelTest.cs | 8 +- .../SqlClient/LocalAppContextSwitchesTest.cs | 37 +++--- 5 files changed, 130 insertions(+), 114 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs index e6e3bf4996..b2232b3f69 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.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.Security.Cryptography; using Azure.Core; using Azure.Security.KeyVault.Keys; @@ -18,20 +19,41 @@ namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures; public abstract class AzureKeyVaultKeyFixtureBase : IDisposable { private readonly KeyClient _keyClient; - private readonly Random _randomGenerator; + private readonly RandomNumberGenerator _randomGenerator; private readonly List _createdKeys = new List(); protected AzureKeyVaultKeyFixtureBase(Uri keyVaultUri, TokenCredential keyVaultToken) { _keyClient = new KeyClient(keyVaultUri, keyVaultToken); - _randomGenerator = new Random(); + _randomGenerator = RandomNumberGenerator.Create(); } protected Uri CreateKey(string name, int keySize) { - CreateRsaKeyOptions createOptions = new CreateRsaKeyOptions(GenerateUniqueName(name)) { KeySize = keySize }; - KeyVaultKey created = _keyClient.CreateRsaKey(createOptions); + const int MaxConflictResolutions = 5; + KeyVaultKey created; + int i = 0; + + while (true) + { + CreateRsaKeyOptions createOptions = new CreateRsaKeyOptions(GenerateUniqueName(name)) { KeySize = keySize }; + + try + { + created = _keyClient.CreateRsaKey(createOptions); + break; + } + // It's possible for a key to already exist with the same name, even in a deleted state. If so, CreateRsaKey + // will throw an exception with HTTP status code 409 (Conflict.) + // We can't assume we possess permissions to purge or to recover the key, so regenerate the name and try again. + // Only make MaxConflictResolutions attempts, to avoid possible infinite loops. + catch (Azure.RequestFailedException conflictException) + when (conflictException.Status == 409 && i < MaxConflictResolutions) + { + i++; + } + } _createdKeys.Add(created); return created.Id; @@ -41,7 +63,7 @@ private string GenerateUniqueName(string name) { byte[] rndBytes = new byte[16]; - _randomGenerator.NextBytes(rndBytes); + _randomGenerator.GetBytes(rndBytes); return name + "-" + BitConverter.ToString(rndBytes); } @@ -64,5 +86,7 @@ protected virtual void Dispose(bool disposing) continue; } } + + _randomGenerator.Dispose(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index ce64aea779..5c07d37a32 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -214,7 +214,8 @@ public void Dispose() #region Switch Value Getters and Setters - // These properties get or set the like-named underlying switch field value. + // These properties get the like-named underlying switch *property* value and set the underlying + // switch *field* value. This allows tests to verify the default switch values. // // They all throw if the value cannot be retrieved or set. @@ -224,7 +225,7 @@ public void Dispose() /// public bool? DisableTnirByDefault { - get => GetSwitchValue("s_disableTnirByDefault"); + get => GetSwitchPropertyValue(nameof(DisableTnirByDefault)); set => SetSwitchValue("s_disableTnirByDefault", value); } #endif @@ -234,7 +235,7 @@ public bool? DisableTnirByDefault /// public bool? EnableMultiSubnetFailoverByDefault { - get => GetSwitchValue("s_enableMultiSubnetFailoverByDefault"); + get => GetSwitchPropertyValue(nameof(EnableMultiSubnetFailoverByDefault)); set => SetSwitchValue("s_enableMultiSubnetFailoverByDefault", value); } @@ -244,7 +245,7 @@ public bool? EnableMultiSubnetFailoverByDefault /// public bool? GlobalizationInvariantMode { - get => GetSwitchValue("s_globalizationInvariantMode"); + get => GetSwitchPropertyValue(nameof(GlobalizationInvariantMode)); set => SetSwitchValue("s_globalizationInvariantMode", value); } #endif @@ -254,7 +255,7 @@ public bool? GlobalizationInvariantMode /// public bool? IgnoreServerProvidedFailoverPartner { - get => GetSwitchValue("s_ignoreServerProvidedFailoverPartner"); + get => GetSwitchPropertyValue(nameof(IgnoreServerProvidedFailoverPartner)); set => SetSwitchValue("s_ignoreServerProvidedFailoverPartner", value); } @@ -263,7 +264,7 @@ public bool? IgnoreServerProvidedFailoverPartner /// public bool? UseLegacyFailoverAlternationOnLoginSqlErrors { - get => GetSwitchValue("s_useLegacyFailoverAlternationOnLoginSqlErrors"); + get => GetSwitchPropertyValue(nameof(UseLegacyFailoverAlternationOnLoginSqlErrors)); set => SetSwitchValue("s_useLegacyFailoverAlternationOnLoginSqlErrors", value); } @@ -272,7 +273,7 @@ public bool? UseLegacyFailoverAlternationOnLoginSqlErrors /// public bool? LegacyRowVersionNullBehavior { - get => GetSwitchValue("s_legacyRowVersionNullBehavior"); + get => GetSwitchPropertyValue(nameof(LegacyRowVersionNullBehavior)); set => SetSwitchValue("s_legacyRowVersionNullBehavior", value); } @@ -281,7 +282,7 @@ public bool? LegacyRowVersionNullBehavior /// public bool? LegacyVarTimeZeroScaleBehaviour { - get => GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour"); + get => GetSwitchPropertyValue(nameof(LegacyVarTimeZeroScaleBehaviour)); set => SetSwitchValue("s_legacyVarTimeZeroScaleBehaviour", value); } @@ -290,7 +291,7 @@ public bool? LegacyVarTimeZeroScaleBehaviour /// public bool? MakeReadAsyncBlocking { - get => GetSwitchValue("s_makeReadAsyncBlocking"); + get => GetSwitchPropertyValue(nameof(MakeReadAsyncBlocking)); set => SetSwitchValue("s_makeReadAsyncBlocking", value); } @@ -299,7 +300,7 @@ public bool? MakeReadAsyncBlocking /// public bool? SuppressInsecureTlsWarning { - get => GetSwitchValue("s_suppressInsecureTlsWarning"); + get => GetSwitchPropertyValue(nameof(SuppressInsecureTlsWarning)); set => SetSwitchValue("s_suppressInsecureTlsWarning", value); } @@ -308,7 +309,7 @@ public bool? SuppressInsecureTlsWarning /// public bool? TruncateScaledDecimal { - get => GetSwitchValue("s_truncateScaledDecimal"); + get => GetSwitchPropertyValue(nameof(TruncateScaledDecimal)); set => SetSwitchValue("s_truncateScaledDecimal", value); } @@ -317,7 +318,7 @@ public bool? TruncateScaledDecimal /// public bool? UseCompatibilityAsyncBehaviour { - get => GetSwitchValue("s_useCompatibilityAsyncBehaviour"); + get => GetSwitchPropertyValue(nameof(UseCompatibilityAsyncBehaviour)); set => SetSwitchValue("s_useCompatibilityAsyncBehaviour", value); } @@ -326,7 +327,7 @@ public bool? UseCompatibilityAsyncBehaviour /// public bool? UseCompatibilityProcessSni { - get => GetSwitchValue("s_useCompatibilityProcessSni"); + get => GetSwitchPropertyValue(nameof(UseCompatibilityProcessSni)); set => SetSwitchValue("s_useCompatibilityProcessSni", value); } @@ -335,7 +336,7 @@ public bool? UseCompatibilityProcessSni /// public bool? UseConnectionPoolV2 { - get => GetSwitchValue("s_useConnectionPoolV2"); + get => GetSwitchPropertyValue(nameof(UseConnectionPoolV2)); set => SetSwitchValue("s_useConnectionPoolV2", value); } @@ -344,7 +345,7 @@ public bool? UseConnectionPoolV2 /// public bool? UseLegacyIdleTimeoutBehavior { - get => GetSwitchValue("s_useLegacyIdleTimeoutBehavior"); + get => GetSwitchPropertyValue(nameof(UseLegacyIdleTimeoutBehavior)); set => SetSwitchValue("s_useLegacyIdleTimeoutBehavior", value); } @@ -353,7 +354,7 @@ public bool? UseLegacyIdleTimeoutBehavior /// public bool? UseOverallConnectTimeoutForPoolWait { - get => GetSwitchValue("s_useOverallConnectTimeoutForPoolWait"); + get => GetSwitchPropertyValue(nameof(UseOverallConnectTimeoutForPoolWait)); set => SetSwitchValue("s_useOverallConnectTimeoutForPoolWait", value); } @@ -363,7 +364,7 @@ public bool? UseOverallConnectTimeoutForPoolWait /// public bool? UseManagedNetworking { - get => GetSwitchValue("s_useManagedNetworking"); + get => GetSwitchPropertyValue(nameof(UseManagedNetworking)); set => SetSwitchValue("s_useManagedNetworking", value); } #endif @@ -373,7 +374,7 @@ public bool? UseManagedNetworking /// public bool? UseMinimumLoginTimeout { - get => GetSwitchValue("s_useMinimumLoginTimeout"); + get => GetSwitchPropertyValue(nameof(UseMinimumLoginTimeout)); set => SetSwitchValue("s_useMinimumLoginTimeout", value); } @@ -386,19 +387,7 @@ public bool? UseMinimumLoginTimeout /// private static bool? GetSwitchValue(string fieldName) { - var assembly = Assembly.GetAssembly(typeof(SqlConnection)); - if (assembly is null) - { - throw new InvalidOperationException( - "Could not get assembly for Microsoft.Data.SqlClient"); - } - - var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); - if (type is null) - { - throw new InvalidOperationException( - "Could not get type LocalAppContextSwitches"); - } + var type = GetLocalAppContextSwitchesType(); var field = type.GetField( fieldName, @@ -433,19 +422,7 @@ public bool? UseMinimumLoginTimeout /// private static void SetSwitchValue(string fieldName, bool? value) { - var assembly = Assembly.GetAssembly(typeof(SqlConnection)); - if (assembly is null) - { - throw new InvalidOperationException( - "Could not get assembly for Microsoft.Data.SqlClient"); - } - - var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); - if (type is null) - { - throw new InvalidOperationException( - "Could not get type LocalAppContextSwitches"); - } + var type = GetLocalAppContextSwitchesType(); var field = type.GetField( fieldName, @@ -470,5 +447,49 @@ private static void SetSwitchValue(string fieldName, bool? value) field.SetValue(null, Enum.ToObject(field.FieldType, byteValue)); } + /// + /// Use reflection to get a switch property value from LocalAppContextSwitches. + /// + /// + /// Each property in LocalAppContextSwitchHelper corresponds to a like-named property in + /// LocalAppContextSwitches, which may return a different value when the AppContext switch + /// has not been set. + /// + private static bool GetSwitchPropertyValue(string propertyName) + { + var type = GetLocalAppContextSwitchesType(); + var property = type.GetProperty( + propertyName, + BindingFlags.Static | BindingFlags.Public); + + if (property == null) + { + throw new InvalidOperationException( + $"Property '{propertyName}' not found in LocalAppContextSwitches"); + } + + object? value = property.GetValue(null); + + return value is bool boolValue + ? boolValue + : throw new InvalidOperationException($"Property '{propertyName}' is not of type bool."); + } + + private static Type GetLocalAppContextSwitchesType() + { + var assembly = Assembly.GetAssembly(typeof(SqlConnection)); + if (assembly is null) + { + throw new InvalidOperationException("Could not get assembly for Microsoft.Data.SqlClient"); + } + + var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); + if (type is null) + { + throw new InvalidOperationException("Could not get type LocalAppContextSwitches"); + } + return type; + } + #endregion } 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 91b57a46b4..49674679b7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs @@ -7,6 +7,7 @@ using System.Data; using System.Data.Common; using System.Threading.Tasks; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -378,10 +379,14 @@ public static void ExceptionWithoutBatchContainsNoBatch() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void ParameterInOutAndReturn() { - string create = - @" -CREATE PROCEDURE TestInAndOutParams - @Input int, + SqlParameter input = CreateParameter("@Input", SqlDbType.Int, 2); + SqlParameter inputOutput = CreateParameter("@InOut", SqlDbType.Int, 4, ParameterDirection.InputOutput); + SqlParameter output = CreateParameter("@Output", SqlDbType.Int, DBNull.Value, ParameterDirection.Output); + SqlParameter returned = CreateParameter("@RETURN_VALUE", SqlDbType.Int, DBNull.Value, ParameterDirection.ReturnValue); + + using (SqlConnection conn = new(DataTestUtility.TCPConnectionString)) + using (StoredProcedure spTestInAndOutParams = new(conn, "TestInAndOutParams", @" + @Input int, @InOut int OUTPUT, @Output int = default OUTPUT AS @@ -389,26 +394,14 @@ CREATE PROCEDURE TestInAndOutParams SET NOCOUNT ON; SELECT @InOut = 2 * @InOut, @Output = 2 * @Input RETURN @Input -END"; - string drop = "DROP PROCEDURE TestInAndOutParams"; - - SqlParameter input = CreateParameter("@Input", SqlDbType.Int, 2); - SqlParameter inputOutput = CreateParameter("@InOut", SqlDbType.Int, 4, ParameterDirection.InputOutput); - SqlParameter output = CreateParameter("@Output", SqlDbType.Int, DBNull.Value, ParameterDirection.Output); - SqlParameter returned = CreateParameter("@RETURN_VALUE", SqlDbType.Int, DBNull.Value, ParameterDirection.ReturnValue); - try +END")) { - TryExecuteNonQueryCommand(drop); - ExecuteNonQueryCommand(create); - - using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) using (SqlBatch batch = new SqlBatch(conn)) { - conn.Open(); batch.Commands.Add(new SqlBatchCommand("SELECT @@VERSION")); batch.Commands.Add( new SqlBatchCommand( - "TestInAndOutParams", + spTestInAndOutParams.Name, CommandType.StoredProcedure, new[] { input, inputOutput, output, returned } ) @@ -417,10 +410,6 @@ RETURN @Input batch.ExecuteNonQuery(); } } - finally - { - TryExecuteNonQueryCommand(drop); - } Assert.Equal(8, Convert.ToInt32(inputOutput.Value)); Assert.Equal(4, Convert.ToInt32(output.Value)); @@ -773,28 +762,5 @@ private static SqlParameter CreateParameter(string name, SqlDbType type, T va parameter.Value = value; return parameter; } - - private static void ExecuteNonQueryCommand(string command) - { - using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) - using (SqlCommand cmd = conn.CreateCommand()) - { - conn.Open(); - cmd.CommandText = command; - cmd.ExecuteNonQuery(); - } - } - private static bool TryExecuteNonQueryCommand(string command) - { - try - { - ExecuteNonQueryCommand(command); - return true; - } - catch - { - } - return false; - } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs index dc97d98052..830f1bb733 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCommand/SqlCommandCancelTest.cs @@ -375,7 +375,9 @@ private static void ExecuteCommandCancelExpected(object state) string errorMessage = SystemDataResourceManager.Instance.SQL_OperationCancelled; string errorMessageSevereFailure = SystemDataResourceManager.Instance.SQL_SevereError; - DataTestUtility.ExpectFailure(() => + // This could fail with either a SqlException or an InvalidOperationException depending on timing, + // so we will accept either but require the message to match expected cancellation messages + DataTestUtility.ExpectFailure(() => { threadsReady.SignalAndWait(); using (SqlDataReader r = command.ExecuteReader()) @@ -387,7 +389,9 @@ private static void ExecuteCommandCancelExpected(object state) } } while (r.NextResult()); } - }, new string[] { errorMessage, errorMessageSevereFailure }); + }, + new string[] { errorMessage, errorMessageSevereFailure }, + customExceptionVerifier: (ex) => ex is SqlException or InvalidOperationException); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index 67837e10f2..e2007280a9 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -38,6 +38,7 @@ public void TestDefaultAppContextSwitchValues() switchesHelper.UseCompatibilityAsyncBehaviour = null; switchesHelper.UseCompatibilityProcessSni = null; switchesHelper.UseConnectionPoolV2 = null; + switchesHelper.UseLegacyIdleTimeoutBehavior = null; switchesHelper.UseMinimumLoginTimeout = null; #if NET switchesHelper.GlobalizationInvariantMode = null; @@ -49,28 +50,28 @@ public void TestDefaultAppContextSwitchValues() switchesHelper.DisableTnirByDefault = null; #endif - Assert.False(LocalAppContextSwitches.LegacyRowVersionNullBehavior); - Assert.False(LocalAppContextSwitches.SuppressInsecureTlsWarning); - Assert.False(LocalAppContextSwitches.MakeReadAsyncBlocking); - Assert.True(LocalAppContextSwitches.UseMinimumLoginTimeout); - Assert.True(LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour); - Assert.True(LocalAppContextSwitches.UseCompatibilityProcessSni); - Assert.True(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); - Assert.True(LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior); - Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); - Assert.False(LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait); - Assert.False(LocalAppContextSwitches.TruncateScaledDecimal); - Assert.False(LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner); - Assert.False(LocalAppContextSwitches.UseLegacyFailoverAlternationOnLoginSqlErrors); - Assert.False(LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault); + Assert.False(switchesHelper.LegacyRowVersionNullBehavior); + Assert.False(switchesHelper.SuppressInsecureTlsWarning); + Assert.False(switchesHelper.MakeReadAsyncBlocking); + Assert.True(switchesHelper.UseMinimumLoginTimeout); + Assert.True(switchesHelper.LegacyVarTimeZeroScaleBehaviour); + Assert.True(switchesHelper.UseCompatibilityProcessSni); + Assert.True(switchesHelper.UseCompatibilityAsyncBehaviour); + Assert.True(switchesHelper.UseLegacyIdleTimeoutBehavior); + Assert.False(switchesHelper.UseConnectionPoolV2); + Assert.False(switchesHelper.UseOverallConnectTimeoutForPoolWait); + Assert.False(switchesHelper.TruncateScaledDecimal); + Assert.False(switchesHelper.IgnoreServerProvidedFailoverPartner); + Assert.False(switchesHelper.UseLegacyFailoverAlternationOnLoginSqlErrors); + Assert.False(switchesHelper.EnableMultiSubnetFailoverByDefault); #if NET - Assert.False(LocalAppContextSwitches.GlobalizationInvariantMode); + Assert.False(switchesHelper.GlobalizationInvariantMode); #endif #if NET && _WINDOWS - Assert.False(LocalAppContextSwitches.UseManagedNetworking); + Assert.False(switchesHelper.UseManagedNetworking); #endif #if NETFRAMEWORK - Assert.False(LocalAppContextSwitches.DisableTnirByDefault); + Assert.False(switchesHelper.DisableTnirByDefault); #endif } } From bfd81b6ae1530f950e3cdd34206b8981ee77f0a2 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 26 Jun 2026 06:39:19 -0300 Subject: [PATCH 166/298] Add BuildAll target (#4290) * Add BuildAll target, CodeQL update, and ref csproj cleanup * Replace ancillary BuildTests with first-class per-project targets * Use TargetOs instead of OS in test projects for TFM selection Test projects previously used the built-in $(OS) property to gate net462 inclusion. This meant passing -p:TargetOs=Windows_NT from build.proj had no effect on test TFM selection. Migrate all test csproj files and Directory.Build.props to define TargetOs (defaulting to $(OS)) and condition on it, matching the pattern already used by the driver project. This allows BuildAll to build all TFMs regardless of host OS. --- .github/workflows/codeql.yml | 2 +- BUILDGUIDE.md | 46 +- build.proj | 429 ++++++++++++++++-- .../Azure/test/Azure.Test.csproj | 10 +- ...Microsoft.Data.SqlClient.TestCommon.csproj | 10 +- .../CustomRetryLogicProvider.csproj | 10 +- ...soft.Data.SqlClient.FunctionalTests.csproj | 10 +- ...icrosoft.Data.SqlClient.ManualTests.csproj | 10 +- .../tests/StressTests/Directory.Build.props | 7 +- .../tests/TestUdts/Address/Address.csproj | 10 +- .../tests/TestUdts/Circle/Circle.csproj | 10 +- .../tests/TestUdts/Shapes/Shapes.csproj | 10 +- .../TestUdts/Utf8String/Utf8String.csproj | 10 +- .../Microsoft.Data.SqlClient.UnitTests.csproj | 14 +- .../TDS/TDS.EndPoint/TDS.EndPoint.csproj | 5 +- 15 files changed, 490 insertions(+), 103 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 45890dfdf4..dc237459ee 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -99,7 +99,7 @@ jobs: - name: Run manual build steps if: matrix.build-mode == 'manual' shell: bash - run: dotnet build src/Microsoft.Data.SqlClient.slnx + run: dotnet build build.proj -t:BuildAll - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 073f6afc68..5acf76cbd5 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -69,22 +69,31 @@ dotnet build -t: [optional_parameters] The command-line examples below will assume that `build.proj` is selected by default and will omit it from the `dotnet build` command. +If no target is specified, `build.proj` runs the `BuildAll` target by default, which builds all +projects, tests, samples, and tools for all supported OS combinations. To build only the driver +projects, specify `-t:BuildDriver` explicitly. + The following build targets can be used to build the following projects. All targets will implicitly build any other projects they depend on. -| `` | Description | -|-----------------------------|---------------------------------------------------------------------------------| -| `Build` | Builds all projects for all platforms | -| `BuildAbstractions` | Builds Microsoft.Data.SqlClient.Extensions.Abstractions | -| `BuildAkvProvider` | Builds Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider | -| `BuildAzure` | Builds Microsoft.Data.SqlClient.Extensions.Azure | -| `BuildLogging` | Builds Microsoft.Data.SqlClient.Internal.Logging | -| `BuildSqlClient` | Builds all variants of Microsoft.Data.SqlClient, for all platforms | -| `BuildSqlClientRef` | Builds the reference assemblies for Microsoft.Data.SqlClient | -| `BuildSqlClientUnsupported` | Builds the "unsupported platform" assemblies for Microsoft.Data.SqlClient | -| `BuildSqlClientUnix` | Builds the Unix-specific implementation binaries of Microsoft.Data.SqlClient | -| `BuildSqlClientWindows` | Builds the Windows-specific implementation binaries of Microsoft.Data.SqlClient | -| `BuildSqlServer` | Builds Microsoft.SqlServer.Server | +| `` | Description | +|-------------------------------|---------------------------------------------------------------------------------| +| `BuildAbstractions` | Builds Microsoft.Data.SqlClient.Extensions.Abstractions | +| `BuildAkvProvider` | Builds Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider | +| `BuildAll` | Builds all projects, tests, samples, and tools for all supported OS combinations (default target) | +| `BuildAzure` | Builds Microsoft.Data.SqlClient.Extensions.Azure | +| `BuildDriver` | Builds all driver projects for all platforms | +| `BuildLogging` | Builds Microsoft.Data.SqlClient.Internal.Logging | +| `BuildSamples` | Builds the sample projects under `doc/samples/` | +| `BuildSqlClient` | Builds all variants of Microsoft.Data.SqlClient, for all platforms | +| `BuildSqlClientNotSupported` | Builds the "unsupported platform" assemblies for Microsoft.Data.SqlClient | +| `BuildSqlClientRef` | Builds the reference assemblies for Microsoft.Data.SqlClient | +| `BuildSqlClientUnix` | Builds the Unix-specific implementation binaries of Microsoft.Data.SqlClient | +| `BuildSqlClientWindows` | Builds the Windows-specific implementation binaries of Microsoft.Data.SqlClient | +| `BuildSqlServer` | Builds Microsoft.SqlServer.Server | +| `BuildTests` | Builds all test projects for all supported OS combinations | +| `BuildTools` | Builds auxiliary tool/app projects | +| `Clean` | Removes build and test output directories | A selection of parameters for build targets in `build.proj` can be found below: @@ -107,10 +116,16 @@ placed in `artifacts/Microsoft.Data.SqlClient.ref/Project-/` #### Examples -Build all projects: +Build everything (all projects, tests, samples, and tools) using the default target: + +```bash +dotnet build +``` + +Build only the driver projects: ```bash -dotnet build -t:Build +dotnet build -t:BuildDriver ``` Build Microsoft.Data.SqlClient in Release configuration: @@ -139,6 +154,7 @@ dotnet build -t: [optional_parameters] | `` | Description | |----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| | `Test` | Runs all tests in the repository for all platforms supported by the host OS. _This will take a considerable amount of time and is not recommended_. | +| `TestAbstractions` | Runs all tests for Microsoft.Data.SqlClient.Extensions.Abstractions | | `TestAzure` | Runs all tests for Microsoft.Data.SqlClient.Extensions.Azure | | `TestSqlClient` | Runs all tests for Microsoft.Data.SqlClient. | | `TestSqlClientFunctional` | Runs the "functional" test project for Microsoft.Data.SqlClient. These are a mix of unit and integration tests against live servers. | diff --git a/build.proj b/build.proj index 4ff29a08df..f2ee21f77c 100644 --- a/build.proj +++ b/build.proj @@ -1,6 +1,5 @@ - - + @@ -246,7 +245,7 @@ + + + - + @@ -383,16 +388,18 @@ @@ -441,7 +448,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -471,7 +479,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -500,7 +509,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -531,7 +541,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -561,7 +572,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -607,7 +619,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -653,7 +666,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -698,7 +712,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -726,7 +741,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -765,7 +781,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -794,7 +811,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -843,7 +861,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -869,7 +888,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -908,7 +928,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -945,7 +966,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -971,7 +993,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -1013,7 +1036,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -1042,7 +1066,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -1064,7 +1089,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -1104,7 +1130,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -1126,7 +1153,8 @@ $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) - + + @@ -1140,4 +1168,343 @@ DestinationFolder="$(PackagesDir)" SkipUnchangedFiles="true" /> + + + + + + $(SqlClientSrcRoot)tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj + $(SqlClientSrcRoot)tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj + $(RepoRoot)doc/samples/Microsoft.Data.SqlClient.Samples.csproj + + + + + + + + + + "$(DotnetPath)dotnet" build "$(AbstractionsTestProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientUnitTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Unix + + + $(ReferenceTypeArgument) + $(TestSigningKeyPathArgument) + $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientUnitTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Windows_NT + + + $(ReferenceTypeArgument) + $(TestSigningKeyPathArgument) + $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientFunctionalTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Unix + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientFunctionalTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Windows_NT + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientManualTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Unix + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientManualTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Windows_NT + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + $(PackageVersionSqlServerArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientPerformanceTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Unix + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientPerformanceTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Windows_NT + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientStressTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Unix + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SqlClientStressTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Windows_NT + + + $(ReferenceTypeArgument) + $(PackageVersionAbstractionsArgument) + $(PackageVersionLoggingArgument) + $(PackageVersionSqlClientArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(AzureTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Unix + + + $(ReferenceTypeArgument) + $(PackageVersionSqlClientArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + "$(DotnetPath)dotnet" build "$(AzureTestProjectPath)" + -p:Configuration=$(Configuration) + -p:TargetOs=Windows_NT + + + $(ReferenceTypeArgument) + $(PackageVersionSqlClientArgument) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(SamplesProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + $(RepoRoot)doc/apps/AzureSqlConnector/AzureSqlConnector.csproj + $(RepoRoot)tools/PackageCompatibility/src/PackageCompatibility.csproj + + + + + + + + + + "$(DotnetPath)dotnet" build "$(AzureSqlConnectorProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(PackageCompatibilityProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj index 1c020c2ae4..5d6780414b 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -12,11 +12,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj index 67c5250748..de44a0c64b 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj +++ b/src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj @@ -25,11 +25,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj index bdbdeafe53..ca037a50bf 100644 --- a/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj +++ b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj @@ -8,11 +8,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 9098091ad8..c681d91dc7 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -10,11 +10,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index 4b0dfb6ddd..f3e4b61b45 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -10,11 +10,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props index 910f8d53a0..93c1f10d93 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props @@ -13,8 +13,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.csproj b/src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.csproj index db53171c6e..e19c8b40a5 100644 --- a/src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.csproj +++ b/src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.csproj @@ -8,11 +8,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Shapes.csproj b/src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Shapes.csproj index e115355bfe..d8c2d66253 100644 --- a/src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Shapes.csproj +++ b/src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Shapes.csproj @@ -8,11 +8,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.csproj b/src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.csproj index 02e0193f82..cc41b9fb47 100644 --- a/src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.csproj +++ b/src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.csproj @@ -8,11 +8,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index 12ea9c52d3..d3405ed113 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -11,11 +11,11 @@ net8.0;net9.0;net10.0 - - $(TargetFrameworks);net462 + + $(OS) + + + $(TargetFrameworks);net462 @@ -61,8 +61,8 @@ as explained above. --> - <_SqlClientRid Condition="'$(OS)' != 'Windows_NT'">unix - <_SqlClientRid Condition="'$(OS)' == 'Windows_NT'">win + <_SqlClientRid Condition="'$(TargetOs)' != 'Windows_NT'">unix + <_SqlClientRid Condition="'$(TargetOs)' == 'Windows_NT'">win <_SqlClientPackageTfm Condition="'$(TargetFramework)' == 'net8.0'">net8.0 <_SqlClientPackageTfm Condition="'$(TargetFramework)' == 'net462'">net462 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 c9dce97ddb..aaede5846b 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 @@ -3,6 +3,7 @@ Microsoft.SqlServer.TDS.EndPoint Microsoft.SqlServer.TDS.EndPoint netstandard2.0 + $(OS) @@ -48,13 +49,13 @@ - + - + From 8194f5fbb293ce7209a916efa3f606120f7f5421 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:04:01 -0300 Subject: [PATCH 167/298] Fix SPN using instance name instead of resolved port for Protocol.None and Protocol.Admin (#4180) --- .../SqlClient/ManagedSni/SniProxy.netcore.cs | 31 ++- .../ManualTests/DataCommon/DataTestUtility.cs | 45 +++- .../SQL/InstanceNameTest/InstanceNameTest.cs | 2 + .../SQL/KerberosTests/KerberosTest.cs | 204 ++++++++++++++++++ .../RetryLogic/SqlCommandReliabilityTest.cs | 2 +- .../SqlDataSourceEnumeratorTest.cs | 2 +- .../SniProxyGetSqlServerSPNsTest.cs | 160 ++++++++++++++ .../SniProxyGetSqlServerSPNsWindowsTests.cs | 84 ++++++++ 8 files changed, 514 insertions(+), 16 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsWindowsTests.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs index da6885062d..de40956784 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs @@ -116,7 +116,7 @@ internal static SniHandle CreateConnectionHandle( return sniHandle; } - private static ResolvedServerSpn GetSqlServerSPNs(DataSource dataSource, string serverSPN) + internal static ResolvedServerSpn GetSqlServerSPNs(DataSource dataSource, string serverSPN) { Debug.Assert(!string.IsNullOrWhiteSpace(dataSource.ServerName)); if (!string.IsNullOrWhiteSpace(serverSPN)) @@ -132,14 +132,29 @@ private static ResolvedServerSpn GetSqlServerSPNs(DataSource dataSource, string } else if (!string.IsNullOrWhiteSpace(dataSource.InstanceName)) { - postfix = dataSource.ResolvedProtocol == DataSource.Protocol.TCP ? dataSource.ResolvedPort.ToString() : dataSource.InstanceName; - } - - SqlClientEventSource.Log.TryTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerName {0}, InstanceName {1}, Port {2}, postfix {3}", dataSource?.ServerName, dataSource?.InstanceName, dataSource?.Port, postfix); + // Per SQL Server Kerberos/SPN guidance, TCP connections use + // MSSQLSvc/: while named pipes use + // MSSQLSvc/: for named instances. Both forms + // are valid — SQL Server registers both at startup — but the port + // form is canonical for TCP listeners and matches native SNI behavior. + // + // Protocol.None means no protocol was explicitly specified. In managed + // SNI, Protocol.None is treated as TCP (see CreateConnectionHandle), + // so the port-based SPN is appropriate for None/TCP/Admin. + // + // If SSRP resolution hasn't populated ResolvedPort yet (value <= 0), + // fall back to instance name since both SPN forms are valid. + // https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/register-a-service-principal-name-for-kerberos-connections?view=sql-server-ver17#named-instance + postfix = (dataSource.ResolvedProtocol == DataSource.Protocol.NP || dataSource.ResolvedPort <= 0) + ? dataSource.InstanceName + : dataSource.ResolvedPort.ToString(); + } + + SqlClientEventSource.Log.TryTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerName {0}, InstanceName {1}, Port {2}, ResolvedPort {3}, ResolvedProtocol {4}, postfix {5}", dataSource?.ServerName, dataSource?.InstanceName, dataSource?.Port, dataSource?.ResolvedPort, dataSource?.ResolvedProtocol, postfix); return GetSqlServerSPNs(hostName, postfix, dataSource.ResolvedProtocol); } - private static ResolvedServerSpn GetSqlServerSPNs(string hostNameOrAddress, string portOrInstanceName, DataSource.Protocol protocol) + internal static ResolvedServerSpn GetSqlServerSPNs(string hostNameOrAddress, string portOrInstanceName, DataSource.Protocol protocol) { Debug.Assert(!string.IsNullOrWhiteSpace(hostNameOrAddress)); IPHostEntry hostEntry = null; @@ -607,8 +622,8 @@ private bool InferNamedPipesInformation() // If the data source starts with "np:servername" if (!_dataSourceAfterTrimmingProtocol.Contains(PipeBeginning)) { - // Assuming that user did not change default NamedPipe name, if the datasource is in the format servername\instance, - // separate servername and instance and prepend instance with MSSQL$ and append default pipe path + // Assuming that user did not change default NamedPipe name, if the datasource is in the format servername\instance, + // separate servername and instance and prepend instance with MSSQL$ and append default pipe path // https://learn.microsoft.com/en-us/sql/tools/configuration-manager/named-pipes-properties?view=sql-server-ver16 if (_dataSourceAfterTrimmingProtocol.Contains(PathSeparator) && ResolvedProtocol == Protocol.NP) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index bd7f6eb262..c465235078 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -682,6 +682,22 @@ public static bool IsNotNamedInstance() return !AreConnStringsSetup() || !new SqlConnectionStringBuilder(TCPConnectionString).DataSource.Contains(@"\"); } + public static bool IsNamedInstanceSetup() + { + return IsTCPConnStringSetup() && + ParseDataSource(new SqlConnectionStringBuilder(TCPConnectionString).DataSource, + out _, out _, out string instanceName) && + !string.IsNullOrEmpty(instanceName); + } + + public static bool HasExplicitPortInTCPConnString() + { + return IsTCPConnStringSetup() && + ParseDataSource(new SqlConnectionStringBuilder(TCPConnectionString).DataSource, + out _, out int port, out _) && + port > 0; + } + public static bool IsLocalHost() { SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); @@ -723,15 +739,32 @@ public static bool IsSupportingDistributedTransactions() #endif } - public static bool IsUsingManagedSNI() => UseManagedSNIOnWindows; - - public static bool IsNotUsingManagedSNIOnWindows() => !UseManagedSNIOnWindows; + /// + /// Returns when the managed SNI implementation is active. + /// On .NET (non-Framework), managed SNI is always used on non-Windows platforms + /// (native SNI does not exist there) and is used on Windows when the + /// Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows AppContext + /// switch is enabled (which is what the product code checks at runtime). + /// On .NET Framework, native SNI is always used. + /// + public static bool IsUsingManagedSNI() => +#if NETFRAMEWORK + false; +#else + !OperatingSystem.IsWindows() || (AppContext.TryGetSwitch(ManagedNetworkingAppContextSwitch, out bool enabled) && enabled); +#endif + /// + /// Returns when the native SNI implementation is active. + /// Native SNI is only available on Windows: always on .NET Framework, and on .NET + /// only when the Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows + /// AppContext switch is not enabled. + /// public static bool IsUsingNativeSNI() => -#if !NETFRAMEWORK - IsNotUsingManagedSNIOnWindows(); -#else +#if NETFRAMEWORK true; +#else + OperatingSystem.IsWindows() && !(AppContext.TryGetSwitch(ManagedNetworkingAppContextSwitch, out bool enabled) && enabled); #endif // Synapse: UTF8 collations are not supported with Azure Synapse. // Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/40103791-utf-8-collations-should-be-supported-in-azure-syna 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 437e02c579..c51e8fd892 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Sockets; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Xunit; @@ -227,6 +228,7 @@ private static bool IsSPNPortNumberTestForNP() { return (IsInstanceNameValid(DataTestUtility.NPConnectionString) && DataTestUtility.IsUsingManagedSNI() + && RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && DataTestUtility.IsNotAzureServer() && DataTestUtility.IsNotAzureSynapse()); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/KerberosTests/KerberosTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/KerberosTests/KerberosTest.cs index e7d22907e0..5ff4aad8b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/KerberosTests/KerberosTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/KerberosTests/KerberosTest.cs @@ -8,9 +8,26 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { + /// + /// Integration tests for Kerberos authentication with protocol-specific SPN handling. + /// + /// These tests verify that the driver generates protocol-specific SPNs for Kerberos authentication + /// across different protocol types (TCP, None, Named Pipes, Admin). They complement unit tests + /// in SniProxyGetSqlServerSPNsTest by verifying end-to-end authentication behavior in a real + /// Kerberos domain environment. + /// + /// See: https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/register-a-service-principal-name-for-kerberos-connections + /// [Trait("Set", "3")] public class KerberosTests { + /// + /// Baseline Kerberos connectivity test verifying that the Kerberos authentication mechanism works + /// with the configured connection strings. + /// + /// This test runs on Unix platforms with Kerberos credentials and verifies that connections + /// authenticate using the KERBEROS auth_scheme (confirmed via sys.dm_exec_connections). + /// [PlatformSpecific(TestPlatforms.AnyUnix)] [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsKerberosTest))] [ClassData(typeof(ConnectionStringsProvider))] @@ -25,8 +42,195 @@ public void IsKerBerosSetupTestAsync(string connectionStr) Assert.True(reader.Read(), "Expected to receive one row data"); Assert.Equal("KERBEROS", reader.GetString(0)); } + + /// + /// Tests Kerberos authentication with Protocol.None (default when no protocol prefix specified). + /// + /// This regression test for GitHub issue #3566 verifies that named instances accessed via + /// Protocol.None (e.g. "Data Source=hostname\instancename") use the correct SPN format: + /// - If SSRP resolves a port: MSSQLSvc/hostname:port (not instance name) + /// - Kerberos should authenticate successfully with the correct SPN + /// + /// Environment: Requires a named instance running on a domain-joined server with an SSRP-resolvable port. + /// + [PlatformSpecific(TestPlatforms.Linux)] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsKerberosTest), nameof(DataTestUtility.IsNamedInstanceSetup))] + public void KerberosTest_ProtocolNone_NamedInstanceWithSsrpResolution() + { + string tcpConnStr = DataTestUtility.TCPConnectionString; + DataTestUtility.ParseDataSource(new SqlConnectionStringBuilder(tcpConnStr).DataSource, + out string hostname, out int port, out string instanceName); + + KerberosTicketManagemnt.Init(DataTestUtility.KerberosDomainUser, DataTestUtility.KerberosDomainPassword); + + // Build from the base connection string to preserve environment settings (Encrypt, + // TrustServerCertificate, timeouts, etc.), overriding only DataSource and IntegratedSecurity. + // SSRP resolution should occur and populate the port in the SPN. + SqlConnectionStringBuilder protocolNoneBuilder = new(tcpConnStr) + { + DataSource = $"{hostname}\\{instanceName}", + IntegratedSecurity = true + }; + + using SqlConnection conn = new(protocolNoneBuilder.ConnectionString); + conn.Open(); // Connection should succeed with Kerberos using the SSRP-resolved port in SPN + + // Verify authentication occurred with KERBEROS auth_scheme + using SqlCommand command = new("SELECT auth_scheme from sys.dm_exec_connections where session_id = @@spid", conn); + using SqlDataReader reader = command.ExecuteReader(); + Assert.True(reader.Read(), "Expected to receive one row data"); + Assert.Equal("KERBEROS", reader.GetString(0)); + } + + /// + /// Tests Kerberos authentication with explicit Protocol.TCP prefix on a named instance. + /// + /// Verifies that "tcp:hostname\instancename" uses the TCP-like SPN format with port: + /// MSSQLSvc/hostname:port (where port is from explicit connection string or SSRP resolution). + /// + /// Environment: Requires a named instance with an explicitly specified or SSRP-resolvable port. + /// + [PlatformSpecific(TestPlatforms.Linux)] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsKerberosTest), nameof(DataTestUtility.IsNamedInstanceSetup))] + public void KerberosTest_ProtocolTcp_NamedInstance() + { + string tcpConnStr = DataTestUtility.TCPConnectionString; + DataTestUtility.ParseDataSource(new SqlConnectionStringBuilder(tcpConnStr).DataSource, + out string hostname, out int port, out string instanceName); + + KerberosTicketManagemnt.Init(DataTestUtility.KerberosDomainUser, DataTestUtility.KerberosDomainPassword); + + // Build the tcp: data source. Include an explicit port only when the test connection string + // already has one; otherwise leave SSRP to resolve the port for the named instance. + // Do NOT fall back to port 1433: that disables SSRP and is unlikely to be correct for + // named instances, and produces an invalid "host\,1433" when instanceName is empty. + string newDataSource = port > 0 + ? $"tcp:{hostname}\\{instanceName},{port}" + : $"tcp:{hostname}\\{instanceName}"; + + // Preserve the base connection string settings (Encrypt, TrustServerCertificate, etc.) + SqlConnectionStringBuilder builder = new(tcpConnStr) + { + DataSource = newDataSource, + IntegratedSecurity = true + }; + + using SqlConnection conn = new(builder.ConnectionString); + conn.Open(); + + using SqlCommand command = new("SELECT auth_scheme from sys.dm_exec_connections where session_id = @@spid", conn); + using SqlDataReader reader = command.ExecuteReader(); + Assert.True(reader.Read(), "Expected to receive one row data"); + Assert.Equal("KERBEROS", reader.GetString(0)); + } + + /// + /// Tests Kerberos authentication with a custom ServerSPN override. + /// + /// Verifies that explicitly setting ServerSPN in the connection string bypasses auto-generation + /// and uses the provided SPN for Kerberos authentication. This is critical for environments where: + /// - Kerberos SPNs are registered with specific hostnames/ports + /// - SQL Server is behind a proxy or alias + /// - Multi-instance environments with non-standard naming + /// + /// Environment: Requires ability to specify a valid SPN for the target instance. + /// + [PlatformSpecific(TestPlatforms.Linux)] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsKerberosTest), nameof(DataTestUtility.HasExplicitPortInTCPConnString))] + public void KerberosTest_CustomServerSPN_BypassesAutoGeneration() + { + string tcpConnStr = DataTestUtility.TCPConnectionString; + DataTestUtility.ParseDataSource(new SqlConnectionStringBuilder(tcpConnStr).DataSource, + out string hostname, out int port, out string instanceName); + + KerberosTicketManagemnt.Init(DataTestUtility.KerberosDomainUser, DataTestUtility.KerberosDomainPassword); + + // Build the TCP-format SPN that matches what the driver would auto-generate. + // TCP Kerberos connections use MSSQLSvc/fqdn:port regardless of instance name. + string fqdn = DataTestUtility.GetMachineFQDN(hostname); + string customSpn = $"MSSQLSvc/{fqdn}:{port}"; + + SqlConnectionStringBuilder builder = new(tcpConnStr); + builder.IntegratedSecurity = true; + builder.ServerSPN = customSpn; + + using SqlConnection conn = new(builder.ConnectionString); + conn.Open(); + + using SqlCommand command = new("SELECT auth_scheme from sys.dm_exec_connections where session_id = @@spid", conn); + using SqlDataReader reader = command.ExecuteReader(); + Assert.True(reader.Read(), "Expected to receive one row data"); + Assert.Equal("KERBEROS", reader.GetString(0)); + } + + /// + /// Tests Kerberos authentication with Protocol.Admin (Dedicated Administrator Connection). + /// + /// Verifies that DAC connections (prefix "admin:") to named instances use the TCP-like SPN format: + /// MSSQLSvc/hostname:port (not instance name). DAC uses SSRP resolution similar to TCP. + /// + /// Environment: Requires DAC to be enabled on the target SQL Server instance and admin credentials. + /// Note: May be skipped if DAC is not enabled or not accessible via Kerberos. + /// + [PlatformSpecific(TestPlatforms.Linux)] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsKerberosTest), nameof(DataTestUtility.IsNamedInstanceSetup))] + public void KerberosTest_ProtocolAdmin_DedicatedAdminConnection() + { + string tcpConnStr = DataTestUtility.TCPConnectionString; + DataTestUtility.ParseDataSource(new SqlConnectionStringBuilder(tcpConnStr).DataSource, + out string hostname, out int port, out string instanceName); + + KerberosTicketManagemnt.Init(DataTestUtility.KerberosDomainUser, DataTestUtility.KerberosDomainPassword); + + // DAC can be unavailable in shared test environments. Skip this specific + // integration test when the target instance has remote admin connections disabled. + SqlConnectionStringBuilder preflightBuilder = new(tcpConnStr) + { + DataSource = $"{hostname}\\{instanceName}", + IntegratedSecurity = true + }; + + using (SqlConnection preflightConn = new(preflightBuilder.ConnectionString)) + { + preflightConn.Open(); + using SqlCommand preflightCommand = new("SELECT CAST(value_in_use AS bit) FROM sys.configurations WHERE name = 'remote admin connections'", preflightConn); + object preflightResult = preflightCommand.ExecuteScalar(); + if (preflightResult is not bool remoteAdminEnabled || !remoteAdminEnabled) + { + return; // Skip test; DAC is not enabled in this environment + } + } + + // Build the admin: data source without appending the regular TCP port. + // The DAC port is separate from the regular SQL Server port and must be + // discovered via SSRP (GetDacPortByInstanceName). Appending the regular + // TCP port would bypass DAC port resolution and connect to the wrong endpoint. + string newDataSource = $"admin:{hostname}\\{instanceName}"; + + SqlConnectionStringBuilder adminBuilder = new(tcpConnStr) + { + DataSource = newDataSource, + IntegratedSecurity = true + }; + + // Note: this test requires DAC to be enabled on the target instance + // (sp_configure 'remote admin connections', 1). + using SqlConnection conn = new(adminBuilder.ConnectionString); + conn.Open(); + + using SqlCommand command = new("SELECT auth_scheme from sys.dm_exec_connections where session_id = @@spid", conn); + using SqlDataReader reader = command.ExecuteReader(); + Assert.True(reader.Read(), "Expected to receive one row data"); + Assert.Equal("KERBEROS", reader.GetString(0)); + } } + /// + /// Provides connection strings from DataTestUtility for theory-based Kerberos tests. + /// + /// Each connection string is tested in a separate Kerberos context to ensure protocol-specific + /// SPN behavior works across all available SQL Server configurations in the test environment. + /// public class ConnectionStringsProvider : IEnumerable { public IEnumerator GetEnumerator() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs index 911b90ec5b..25e5675a09 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs @@ -370,7 +370,7 @@ public void DropDatabaseWithActiveConnection(string cnnString, SqlRetryLogicBase // In Managed SNI by Named pipe connection, SqlCommand doesn't respect timeout. "ActiveIssue 12167" // Synapse: Does not support WAITFOR DELAY. // Restricted to non azure: https://github.com/dotnet/SqlClient/issues/3821 - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotUsingManagedSNIOnWindows), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsUsingNativeSNI), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] [MemberData(nameof(UpdateLockedTable_Data), DisableDiscoveryEnumeration = true)] public void UpdateLockedTable(string cnnString, SqlRetryLogicBaseProvider provider) { 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 4369d75ab0..a89a728756 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs @@ -20,7 +20,7 @@ private static bool IsEnvironmentAvailable() ServiceController[] services = ServiceController.GetServices(Environment.MachineName); ServiceController service = services.FirstOrDefault(s => s.ServiceName == "SQLBrowser"); - return DataTestUtility.IsNotUsingManagedSNIOnWindows() && + return DataTestUtility.IsUsingNativeSNI() && service != null && service.Status == ServiceControllerStatus.Running; } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsTest.cs new file mode 100644 index 0000000000..9e80e24e89 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsTest.cs @@ -0,0 +1,160 @@ +// 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 Microsoft.Data.SqlClient.ManagedSni; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ManagedSni +{ + /// + /// Regression tests for SPN (Service Principal Name) selection logic in . + /// + /// These tests verify that the driver generates protocol-specific SPNs for Kerberos authentication: + /// - TCP-like protocols (TCP, None, Admin) use MSSQLSvc/hostname:port + /// - Named Pipes uses MSSQLSvc/hostname:instancename + /// - Custom ServerSPN overrides are always respected + /// + /// This addresses GitHub issue #3566: named instances connecting without a protocol prefix + /// (Protocol.None) should use the SSRP-resolved port in the SPN, not the instance name. + /// + /// See: https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/register-a-service-principal-name-for-kerberos-connections + /// + public class SniProxyGetSqlServerSPNsTest + { + /// + /// Verifies that Protocol.None (default when no prefix specified, e.g. "server\instance") + /// uses the SSRP-resolved port in the SPN, not the instance name. + /// + /// This is a regression test for GitHub issue #3566. On Linux with SSRP, a named instance + /// connection string like "Data Source=server\instance" requires the resolved TCP port + /// from SSRP to be used in the SPN for Kerberos authentication to succeed. + /// + [Fact] + public void GetSqlServerSPNs_ProtocolNone_WithResolvedPort_UsesPortNotInstanceName() + { + // Arrange: parse "localhost\instance" which sets Protocol.None and IsSsrpRequired. + // Using "localhost" instead of an arbitrary hostname avoids real DNS lookups + // that would make the test flaky in environments with restricted DNS resolution. + DataSource dataSource = DataSource.ParseServerName(@"localhost\instance"); + Assert.NotNull(dataSource); + Assert.Equal(DataSource.Protocol.None, dataSource.ResolvedProtocol); + Assert.Equal("instance", dataSource.InstanceName); + Assert.Equal(-1, dataSource.Port); // No explicit port in connection string + + // Simulate SSRP resolution setting the port (as CreateTcpHandle would do) + dataSource.ResolvedPort = 12345; + + // Act: generate SPN for this named instance with resolved port + ResolvedServerSpn spn = SniProxy.GetSqlServerSPNs(dataSource, serverSPN: string.Empty); + + // Assert: SPN should contain the resolved port, NOT the instance name + Assert.Contains(":12345", spn.Primary); + Assert.DoesNotContain("instance", spn.Primary, System.StringComparison.OrdinalIgnoreCase); + } + + /// + /// Verifies that Protocol.TCP (explicit "tcp:" prefix) uses the resolved port in the SPN. + /// + /// This was the original fix for GitHub issue #2187 and ensures TCP protocol behavior + /// is consistent across all platforms. + /// + [Fact] + public void GetSqlServerSPNs_ProtocolTcp_WithResolvedPort_UsesPort() + { + // Arrange: parse "tcp:localhost\instance" which sets Protocol.TCP. + // Using "localhost" avoids real DNS lookups that would make the test flaky. + DataSource dataSource = DataSource.ParseServerName(@"tcp:localhost\instance"); + Assert.NotNull(dataSource); + Assert.Equal(DataSource.Protocol.TCP, dataSource.ResolvedProtocol); + + // Simulate SSRP resolution setting the port + dataSource.ResolvedPort = 54321; + + // Act: generate SPN for this TCP named instance + ResolvedServerSpn spn = SniProxy.GetSqlServerSPNs(dataSource, serverSPN: string.Empty); + + // Assert: SPN should use the resolved port + Assert.Contains(":54321", spn.Primary); + Assert.DoesNotContain("instance", spn.Primary, System.StringComparison.OrdinalIgnoreCase); + } + + /// + /// Verifies that Protocol.NP (Named Pipes) uses the instance name in the SPN, + /// not a port number. + /// + /// Named Pipes protocol requires instance-name-based SPNs per SQL Server guidelines. + /// This test ensures NP behavior is preserved when the general protocol logic is updated. + /// + [Fact] + public void GetSqlServerSPNs_ProtocolNp_WithInstanceName_UsesInstanceName() + { + // Arrange & Act: test the lower-level overload directly with NP protocol. + // Named Pipes data sources go through a different parsing path that doesn't + // populate InstanceName in the same way, so we call the helper directly. + // Using "localhost" avoids real DNS lookups that would make the test flaky. + ResolvedServerSpn spn = SniProxy.GetSqlServerSPNs("localhost", "myinstance", DataSource.Protocol.NP); + + // Assert: SPN should use the instance name, not a port + Assert.Contains(":myinstance", spn.Primary); + Assert.Null(spn.Secondary); // NP does not generate a secondary SPN + } + + /// + /// Verifies that explicit ServerSPN overrides (via connection string) are used as-is, + /// bypassing all auto-generation logic. + /// + /// This is critical for Kerberos environments where custom SPNs may be required + /// (e.g., non-standard ports, aliased hostnames, or specific service accounts). + /// + [Fact] + public void GetSqlServerSPNs_CustomSpnProvided_UsesCustomSpn() + { + // Arrange: parse a named instance, but provide a custom SPN override. + // Using "localhost" avoids real DNS lookups that would make the test flaky. + DataSource dataSource = DataSource.ParseServerName(@"localhost\instance"); + Assert.NotNull(dataSource); + dataSource.ResolvedPort = 12345; + + string customSpn = "MSSQLSvc/myserver.domain.com:1433"; + + // Act: generate SPN with explicit override + ResolvedServerSpn spn = SniProxy.GetSqlServerSPNs(dataSource, serverSPN: customSpn); + + // Assert: custom SPN is used exactly as provided, without modification + Assert.Equal(customSpn, spn.Primary); + Assert.Null(spn.Secondary); + } + + /// + /// Verifies that Protocol.Admin (Dedicated Administrator Connection, DAC) + /// uses the resolved port in the SPN, not the instance name. + /// + /// DAC also uses SSRP resolution and should follow the same protocol-based logic + /// as Protocol.TCP and Protocol.None for SPN generation. + /// + [Fact] + public void GetSqlServerSPNs_ProtocolAdmin_WithResolvedPort_UsesPort() + { + // Arrange: parse "admin:localhost\instance" which sets Protocol.Admin. + // Using "localhost" avoids real DNS lookups that would make the test flaky. + DataSource dataSource = DataSource.ParseServerName(@"admin:localhost\instance"); + Assert.NotNull(dataSource); + Assert.Equal(DataSource.Protocol.Admin, dataSource.ResolvedProtocol); + + // Simulate SSRP resolution setting the port + dataSource.ResolvedPort = 11111; + + // Act: generate SPN for this DAC connection to a named instance + ResolvedServerSpn spn = SniProxy.GetSqlServerSPNs(dataSource, serverSPN: string.Empty); + + // Assert: SPN should use the resolved port, not the instance name + Assert.Contains(":11111", spn.Primary); + Assert.DoesNotContain("instance", spn.Primary, System.StringComparison.OrdinalIgnoreCase); + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsWindowsTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsWindowsTests.cs new file mode 100644 index 0000000000..c8c55ece62 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniProxyGetSqlServerSPNsWindowsTests.cs @@ -0,0 +1,84 @@ +// 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 Microsoft.Data.SqlClient.ManagedSni; +using Microsoft.Data.SqlClient.Tests.Common; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ManagedSni +{ + /// + /// Windows-focused unit tests for managed SNI SPN generation behavior. + /// + public sealed class SniProxyGetSqlServerSPNsWindowsTests + { + /// + /// Verifies Protocol.None uses the SSRP-resolved port in the generated SPN + /// for a named instance on Windows. + /// + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void GetSqlServerSPNs_ProtocolNone_WithResolvedPort_UsesPort_OnWindows() + { + DataSource dataSource = DataSource.ParseServerName(@"localhost\instance"); + Assert.NotNull(dataSource); + + // Mirror post-SSRP state by injecting a resolved TCP port. + dataSource.ResolvedPort = 12345; + + ResolvedServerSpn spn = SniProxy.GetSqlServerSPNs(dataSource, serverSPN: string.Empty); + + // Protocol.None should resolve to a port-based SPN for named instances. + Assert.Contains(":12345", spn.Primary); + Assert.DoesNotContain("instance", spn.Primary, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Verifies Protocol.TCP uses the resolved port in the generated SPN + /// for a named instance on Windows. + /// + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void GetSqlServerSPNs_ProtocolTcp_WithResolvedPort_UsesPort_OnWindows() + { + DataSource dataSource = DataSource.ParseServerName(@"tcp:localhost\instance"); + Assert.NotNull(dataSource); + + // Mirror post-SSRP state by injecting a resolved TCP port. + dataSource.ResolvedPort = 54321; + + ResolvedServerSpn spn = SniProxy.GetSqlServerSPNs(dataSource, serverSPN: string.Empty); + + // TCP protocol should use a port postfix and not the instance name. + Assert.Contains(":54321", spn.Primary); + Assert.DoesNotContain("instance", spn.Primary, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Verifies Protocol.Admin (DAC) uses the resolved port in the generated SPN + /// for a named instance on Windows. + /// + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void GetSqlServerSPNs_ProtocolAdmin_WithResolvedPort_UsesPort_OnWindows() + { + DataSource dataSource = DataSource.ParseServerName(@"admin:localhost\instance"); + Assert.NotNull(dataSource); + + // Mirror post-SSRP state by injecting a resolved DAC port. + dataSource.ResolvedPort = 11111; + + ResolvedServerSpn spn = SniProxy.GetSqlServerSPNs(dataSource, serverSPN: string.Empty); + + // Admin protocol follows TCP-like SPN formatting with a port postfix. + Assert.Contains(":11111", spn.Primary); + Assert.DoesNotContain("instance", spn.Primary, StringComparison.OrdinalIgnoreCase); + } + } +} + +#endif From 297d421eda084ade2c047a589145aadf8649abb1 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 27 Jun 2026 03:18:46 +1000 Subject: [PATCH 168/298] remove sqlserver 7 and 2000 support (#4015) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> --- .../Azure/test/AADConnectionTest.cs | 2 +- .../Connection/SqlConnectionInternal.cs | 13 +- .../Data/SqlClient/SqlConnectionOptions.cs | 22 +- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 215 ++++-------------- .../Data/SqlClient/SqlInternalTransaction.cs | 6 +- .../Microsoft/Data/SqlClient/SqlParameter.cs | 5 +- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 10 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 12 +- .../ManualTests/BulkCopy/CopyAllFromReader.cs | 4 +- .../ManualTests/BulkCopy/UnprivilegedLogin.cs | 7 +- .../SimulatedServerTests/ConnectionTests.cs | 16 +- .../tests/tools/TDS/TDS/TDSVersion.cs | 12 +- 12 files changed, 98 insertions(+), 226 deletions(-) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs index 5dd3ac336e..a8060354d4 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs @@ -97,7 +97,7 @@ public static void ActiveDirectoryDefaultMustPass() // ActiveIssue tests can be filtered out of test runs on the dotnet CLI // using the filter "category != failing". // - [ActiveIssue("https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/40107")] + [ActiveIssue("https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/45941")] [ConditionalFact( typeof(Config), nameof(Config.SupportsIntegratedSecurity), diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 7c3e177520..8ff3d92428 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -830,8 +830,8 @@ public override void EnlistTransaction(Transaction transaction) ValidateConnectionForExecute(null); // If a connection has a local transaction outstanding, and you try to enlist in a DTC - // transaction, SQL Server will roll back the local transaction and then enlist (7.0 and - // 2000). So, if the user tries to do this, throw. + // transaction, SQL Server will roll back the local transaction and then enlist. + // So, if the user tries to do this, throw. if (HasLocalTransaction) { throw ADP.LocalTransactionPresent(); @@ -843,12 +843,9 @@ public override void EnlistTransaction(Transaction transaction) return; } - // If a connection is already enlisted in a DTC transaction, and you try to enlist in - // another one, in 7.0 the existing DTC transaction would roll back and then the - // connection would enlist in the new one. In SQL 2000 & 2005, when you enlist in a DTC - // transaction while the connection is already enlisted in a DTC transaction, the - // connection simply switches enlistments. Regardless, simply enlist in the user - // specified distributed transaction. This behavior matches OLEDB and ODBC. + // If a connection is already enlisted in a DTC transaction and you try to enlist in + // another one, the connection simply switches enlistments. This behavior matches + // OLEDB and ODBC. Enlist(transaction); // @TODO: CER Exception Handling was removed here (see GH#3581) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs index 734ecc125f..a90c760ebb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs @@ -53,8 +53,6 @@ internal sealed partial class SqlConnectionOptions internal enum TypeSystem { - Latest = 2008, - SQLServer2000 = 2000, SQLServer2005 = 2005, SQLServer2008 = 2008, SQLServer2012 = 2012, @@ -62,8 +60,15 @@ internal enum TypeSystem internal static class TYPESYSTEMVERSION { + // "Latest" is accepted as a connection string value so that applications can + // explicitly opt in to (and the driver can default to) a stable, well-known type + // system rather than having type behavior shift as new SQL Server versions appear. + // + // NOTE: The name "Latest" is misleading and has nothing to do with which type system + // it maps to - it is NOT the newest type system available. It is simply a badly named + // sentinel value that the connection string accepts for historical/compatibility + // reasons. The actual type system it resolves to is decided where "Latest" is parsed. internal const string Latest = "Latest"; - internal const string SQL_Server_2000 = "SQL Server 2000"; internal const string SQL_Server_2005 = "SQL Server 2005"; internal const string SQL_Server_2008 = "SQL Server 2008"; internal const string SQL_Server_2012 = "SQL Server 2012"; @@ -459,11 +464,12 @@ internal SqlConnectionOptions(string connectionString) if (typeSystemVersionString.Equals(TYPESYSTEMVERSION.Latest, StringComparison.OrdinalIgnoreCase)) { - _typeSystemVersion = TypeSystem.Latest; - } - else if (typeSystemVersionString.Equals(TYPESYSTEMVERSION.SQL_Server_2000, StringComparison.OrdinalIgnoreCase)) - { - _typeSystemVersion = TypeSystem.SQLServer2000; + // "Latest" (and the default when no version is specified) intentionally maps to + // SQL Server 2008 rather than the newest defined type system. Advancing it would + // alter type mappings (e.g. the Microsoft.SqlServer.Types assembly version and how + // down-level types are surfaced) for existing applications that rely on the default, + // which would be a breaking change. Newer type systems must be requested explicitly. + _typeSystemVersion = TypeSystem.SQLServer2008; } else if (typeSystemVersionString.Equals(TYPESYSTEMVERSION.SQL_Server_2005, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 6b7b53688e..db21550aeb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -594,19 +594,11 @@ internal DataTable BuildSchemaTable() break; } } - else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && col.IsLargeUdt) + else if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005 && col.IsLargeUdt) { - if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005) - { - schemaRow[providerType] = SqlDbType.VarBinary; - } - else - { - // TypeSystem.SQLServer2000 - schemaRow[providerType] = SqlDbType.Image; - } + schemaRow[providerType] = SqlDbType.VarBinary; } - else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) + else { // TypeSystem.SQLServer2005 and above @@ -625,13 +617,6 @@ internal DataTable BuildSchemaTable() 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) { @@ -1208,19 +1193,11 @@ private string GetDataTypeNameInternal(_SqlMetaData metaData) { dataTypeName = MetaType.MetaNVarChar.TypeName; } - else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) + else if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) { - if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005) - { - dataTypeName = MetaType.MetaMaxVarBinary.TypeName; - } - else - { - // TypeSystem.SQLServer2000 - dataTypeName = MetaType.MetaImage.TypeName; - } + dataTypeName = MetaType.MetaMaxVarBinary.TypeName; } - else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) + else { // TypeSystem.SQLServer2005 and above @@ -1241,12 +1218,6 @@ private string GetDataTypeNameInternal(_SqlMetaData metaData) } } } - else - { - // TypeSystem.SQLServer2000 - - dataTypeName = GetVersionedMetaType(metaData.metaType).TypeName; - } return dataTypeName; } @@ -1297,19 +1268,11 @@ private Type GetFieldTypeInternal(_SqlMetaData metaData) // Return 2008 types as string fieldType = MetaType.MetaNVarChar.ClassType; } - else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) + else if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) { - if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005) - { - fieldType = MetaType.MetaMaxVarBinary.ClassType; - } - else - { - // TypeSystem.SQLServer2000 - fieldType = MetaType.MetaImage.ClassType; - } + fieldType = MetaType.MetaMaxVarBinary.ClassType; } - else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) + else { // TypeSystem.SQLServer2005 and above if (metaData.type == SqlDbType.Udt) @@ -1334,11 +1297,6 @@ private Type GetFieldTypeInternal(_SqlMetaData metaData) } } } - else - { - // TypeSystem.SQLServer2000 - fieldType = GetVersionedMetaType(metaData.metaType).ClassType; // Com+ type. - } return fieldType; } @@ -1429,19 +1387,11 @@ private Type GetProviderSpecificFieldTypeInternal(_SqlMetaData metaData) { providerSpecificFieldType = MetaType.MetaNVarChar.SqlType; } - else if (_typeSystem <= SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) + else if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) { - if (_typeSystem == SqlConnectionOptions.TypeSystem.SQLServer2005) - { - providerSpecificFieldType = MetaType.MetaMaxVarBinary.SqlType; - } - else - { - // TypeSystem.SQLServer2000 - providerSpecificFieldType = MetaType.MetaImage.SqlType; - } + providerSpecificFieldType = MetaType.MetaMaxVarBinary.SqlType; } - else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) + else { // TypeSystem.SQLServer2005 and above if (metaData.type == SqlDbType.Udt) @@ -1468,11 +1418,6 @@ private Type GetProviderSpecificFieldTypeInternal(_SqlMetaData metaData) } } } - else - { - // TypeSystem.SQLServer2000 - providerSpecificFieldType = GetVersionedMetaType(metaData.metaType).SqlType; // SqlType type. - } return providerSpecificFieldType; } @@ -2589,27 +2534,7 @@ virtual public SqlXml GetSqlXml(int i) ReadColumn(i); SqlXml sx = null; - if (_typeSystem != SqlConnectionOptions.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; - } + sx = _data[i].IsNull ? SqlXml.Null : _data[i].SqlCachedBuffer.ToSqlXml(); return sx; } @@ -2683,40 +2608,22 @@ private object GetSqlValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met { return data.SqlValue; } - else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) + else if (metaData.type == SqlDbType.Udt) { - // TypeSystem.SQLServer2005 and above - - if (metaData.type == SqlDbType.Udt) + SqlConnection connection = _connection; + if (connection != null) { - SqlConnection connection = _connection; - if (connection != null) - { - connection.CheckGetExtendedUDTInfo(metaData, true); - return connection.GetUdtValue(data.Value, metaData, false); - } - else - { - throw ADP.DataReaderClosed(); - } + connection.CheckGetExtendedUDTInfo(metaData, true); + return connection.GetUdtValue(data.Value, metaData, false); } else { - return data.SqlValue; + throw ADP.DataReaderClosed(); } } else { - // TypeSystem.SQLServer2000 - - if (metaData.type == SqlDbType.Xml) - { - return data.SqlString; - } - else - { - return data.SqlValue; - } + return data.SqlValue; } } @@ -2878,49 +2785,38 @@ private object GetValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaDa { return data.Value; } - else if (_typeSystem != SqlConnectionOptions.TypeSystem.SQLServer2000) + else if (metaData.type == SqlDbTypeExtensions.Vector) { - // TypeSystem.SQLServer2005 and above - - if (metaData.type == SqlDbTypeExtensions.Vector) + if (data.IsNull) { - if (data.IsNull) - { - return DBNull.Value; - } - else + return DBNull.Value; + } + else + { + switch (metaData.scale) { - switch (metaData.scale) - { - case (byte)MetaType.SqlVectorElementType.Float32: - return data.GetSqlVector(); - default: - throw SQL.VectorTypeNotSupported(metaData.scale.ToString()); - } + case (byte)MetaType.SqlVectorElementType.Float32: + return data.GetSqlVector(); + default: + throw SQL.VectorTypeNotSupported(metaData.scale.ToString()); } } - - if (metaData.type != SqlDbType.Udt) + } + else if (metaData.type == SqlDbType.Udt) + { + SqlConnection connection = _connection; + if (connection != null) { - return data.Value; + connection.CheckGetExtendedUDTInfo(metaData, true); + return connection.GetUdtValue(data.Value, metaData, true); } else { - SqlConnection connection = _connection; - if (connection != null) - { - connection.CheckGetExtendedUDTInfo(metaData, true); - return connection.GetUdtValue(data.Value, metaData, true); - } - else - { - throw ADP.DataReaderClosed(); - } + throw ADP.DataReaderClosed(); } } else { - // TypeSystem.SQLServer2000 return data.Value; } } @@ -3237,39 +3133,6 @@ override public int GetValues(object[] values) } } - private MetaType GetVersionedMetaType(MetaType actualMetaType) - { - Debug.Assert(_typeSystem == SqlConnectionOptions.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) { 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 ee6557e55b..69474315cd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs @@ -211,10 +211,8 @@ internal void CloseFromConnection() { if (processFinallyBlock) { - // Always ensure we're zombied; 2005 will send an EnvChange that - // will cause the zombie, but only if we actually go to the wire; - // 7.0 and 2000 won't send the env change, so we have to handle - // them ourselves. + // Always ensure we're zombied; the server will send an EnvChange + // that will cause the zombie, but only if we actually go to the wire. Zombie(); } } 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 e030fb11f5..f9e342d4c6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -2129,9 +2129,8 @@ internal MetaType ValidateTypeLengths() } else { - // Notes: - // Elevation from (n)(var)char (4001+) to (n)text succeeds without failure only with 2005 and greater. - // it fails in sql server 2000 + // Non-NChar types are measured in bytes, so compare the size in characters + // against the actual size in bytes and keep the larger of the two. maxSizeInBytes = (sizeInCharacters > actualSizeInBytes) ? sizeInCharacters : actualSizeInBytes; } 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 9b71afc350..46f447d582 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -325,19 +325,13 @@ public enum ActiveDirectoryWorkflow : byte Client sends: 0x70000000 -> 7.0 - 0x71000000 -> 2000 RTM - 0x71000001 -> 2000 SP1 0x72xx0002 -> 2005 RTM Server responds: - 0x07000000 -> 7.0 // Notice server response format is different for bwd compat - 0x07010000 -> 2000 RTM // Notice server response format is different for bwd compat - 0x71000001 -> 2000 SP1 0x72xx0002 -> 2005 RTM */ // Majors: - // 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; @@ -459,11 +453,9 @@ public enum ActiveDirectoryWorkflow : byte public const int MAX_NUMERIC_PRECISION = 0x26; // 38 is max numeric precision; public const byte UNKNOWN_PRECISION_SCALE = 0xff; // -1 is value for unknown precision or scale - // The following datatypes are specific to 2000 (version 8) and later. + // The following datatypes are specific to 2005 (version 9) or later public const int SQLINT8 = 0x7f; public const int SQLVARIANT = 0x62; - - // The following datatypes are specific to 2005 (version 9) or later public const int SQLXMLTYPE = 0xf1; public const int XMLUNICODEBOM = 0xfeff; public static readonly byte[] XMLUNICODEBOMBYTES = { 0xff, 0xfe }; 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 c3f96b077a..9f37d30015 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -640,8 +640,6 @@ bool withFailover isTlsFirst, serverCertificateFilename); - // Don't need to check for 7.0 failure, since we've already consumed - // one pre-login packet and know we are connecting to 2000. if (status == PreLoginHandshakeStatus.InstanceFailure) { SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake unsuccessful. Login failure"); @@ -2344,7 +2342,6 @@ internal void WriteDouble(double v, TdsParserStateObject stateObj) internal void PrepareResetConnection(bool preserveTransaction) { - // Set flag to reset connection upon next use - only for use on 2000! _fResetConnection = true; _fPreserveTransaction = preserveTransaction; } @@ -4337,9 +4334,6 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out uint increment = (a.tdsVersion >> 16) & 0xff; // Server responds: - // 0x07000000 -> 7.0 // Notice server response format is different for bwd compat - // 0x07010000 -> 2000 RTM // Notice server response format is different for bwd compat - // 0x71000001 -> 2000 SP1 // 0x72xx0002 -> 2005 RTM // information provided by S. Ashwin switch (majorMinor) @@ -4769,8 +4763,8 @@ internal TdsOperationStatus TryProcessReturnValue(int length, rec.metaType = MetaType.GetSqlDataType(tdsType, userType, tdsLen); rec.type = rec.metaType.SqlDbType; - // always use the nullable type for parameters if 2000 or later - // 7.0 sometimes sends fixed length return values + // always use the nullable type for parameters if 2005 or later + // older servers sometimes send fixed length return values rec.tdsType = rec.metaType.NullableType; rec.IsNullable = true; if (tdsLen == TdsEnums.SQL_USHORTVARMAXLEN) @@ -10178,7 +10172,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout { if (rpcext.ProcID != 0) { - // Perf optimization for 2000 and later, + // Perf optimization for 2005 and later, Debug.Assert(rpcext.ProcID < 255, "rpcExec:ProcID can't be larger than 255"); WriteShort(0xffff, stateObj); WriteShort((short)(rpcext.ProcID), stateObj); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs index 17a613294b..fd6b06bafb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs @@ -45,10 +45,10 @@ public void Test() using (DbDataReader reader = srcCmd.ExecuteReader()) { IDictionary stats; - long expectedIduCount = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 2 : 0; + long expectedIduCount = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 2 : 1; long expectedSelectCount = DataTestUtility.IsAzureSynapse ? 4 : 13; long expectedSelectRows = DataTestUtility.IsAzureSynapse ? 4 : 15; - long expectedTransactions = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 2 : 0; + long expectedTransactions = DataTestUtility.IsAzureSynapse || DataTestUtility.IsAtLeastSQL2017() ? 2 : 1; using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { bulkcopy.DestinationTableName = dstTable; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs index 08915e30ab..7fd5b36237 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs @@ -31,6 +31,11 @@ public sealed class UnprivilegedLogin : IDisposable DataTestUtility.AreConnStringsSetup() && DataTestUtility.IsNotAzureServer() && DataTestUtility.CanCreateLogins && DataTestUtility.CanUseSqlAuthentication; + // Graph tables (AS NODE / AS EDGE) were introduced in SQL Server 2017, so tests which rely on + // them must be skipped against older servers. + public static bool CanRunGraphTests => + CanRunTests && DataTestUtility.IsAtLeastSQL2017(); + public UnprivilegedLogin() { // xUnit will instantiate the class before evaluating the test condition - make sure that we don't @@ -134,7 +139,7 @@ public void BulkCopyWithoutMetadataPermission_Succeeds() Assert.Equal(BulkCopyRowCount, resultantRowCount); } - [ConditionalFact(nameof(CanRunTests))] + [ConditionalFact(nameof(CanRunGraphTests))] public void BulkCopyWithoutMetadataPermission_FailsWhenUsingAliases() { AssertEnvironmentCreated(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index 57e8d17632..ef058c881a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -57,7 +57,7 @@ public void IntegratedAuthConnectionTest() /// /// Runs a test where TDS Server doesn't send encryption info during pre-login response. - /// The driver is expected to fail when that happens, and terminate the connection during pre-login phase + /// The driver is expected to fail when that happens, and terminate the connection during pre-login phase /// when client enables encryption using Encrypt=true or uses default encryption setting. /// [Fact] @@ -476,7 +476,7 @@ public void ConnectionTimeoutTest(int timeout) }.ConnectionString; using SqlConnection connection = new(connStr); - // Dispose the server to force connection timeout + // Dispose the server to force connection timeout server.Dispose(); // Measure the actual time it took to timeout and compare it with configured timeout @@ -520,7 +520,7 @@ public async Task ConnectionTimeoutTestAsync(int timeout) }.ConnectionString; using SqlConnection connection = new(connStr); - // Dispose the server to force connection timeout + // Dispose the server to force connection timeout server.Dispose(); // Measure the actual time it took to timeout and compare it with configured timeout @@ -711,11 +711,17 @@ public void ConnectionTestPermittedVersion(int major, int minor, int build) Assert.Equal(simulatedServerVersion, returnedServerVersion); } + // Verifies that the client refuses to connect to a SQL Server whose TDS + // protocol version predates the minimum supported version (2005). This is + // genuine client-side coverage: the simulated server emits a real legacy + // LOGINACK (TDS 7.0 for SQL Server 7.0, TDS 7.1 for SQL Server 2000), and + // the driver rejects the negotiated version in TryProcessLoginAck via + // SQL.InvalidTDSVersion(), which surfaces as an InvalidOperationException. [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) + public void ConnectionRefusesUnsupportedServerTdsVersion(int major, int minor, int build) { Version simulatedServerVersion = new(major, minor, build); using TdsServer server = new( @@ -731,6 +737,8 @@ public void ConnectionTestDeniedVersion(int major, int minor, int build) }.ConnectionString; using SqlConnection conn = new(connStr); + // The driver receives a LOGINACK advertising a pre-2005 TDS version and + // refuses it, throwing InvalidOperationException (SQL.InvalidTDSVersion). Assert.Throws(() => conn.Open()); } 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 d17d879ed4..bee5e0084c 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSVersion.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSVersion.cs @@ -58,10 +58,16 @@ public static Version GetTDSVersion(Version buildVersion) } else if (buildVersion.Major == 8) { + // SQL Server 2000. This version is no longer supported by the + // client; the simulated server can still emit it so that the + // client's rejection of unsupported versions can be exercised. return SqlServer2000; } else if (buildVersion.Major == 7) { + // SQL Server 7.0. This version is no longer supported by the + // client; the simulated server can still emit it so that the + // client's rejection of unsupported versions can be exercised. return SqlServer7_0; } else @@ -97,7 +103,11 @@ public static Version Resolve(Version tdsServer, Version tdsClient) /// public static bool IsSupported(Version tdsVersion) { - return tdsVersion >= SqlServer7_0 && tdsVersion <= SqlServer2012; + // The simulated server can speak any TDS version up to 2012, + // including the legacy 7.0/2000 versions. Emitting those legacy + // versions lets tests verify that the client rejects servers whose + // TDS version is no longer supported. + return tdsVersion <= SqlServer2012; } } } From c7ee148cb9fa3cbc14c47ef781b03f5d31f7e6ae Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 26 Jun 2026 16:57:21 -0300 Subject: [PATCH 169/298] Harden SqlConnection state transitions with CAS guards (#4267) --- .../src/Microsoft/Data/Common/AdapterUtil.cs | 4 + .../Microsoft/Data/SqlClient/SqlConnection.cs | 64 +++- .../SqlConnectionConcurrentOpenTests.cs | 6 +- .../SqlConnectionStateTransitionTests.cs | 289 ++++++++++++++++++ 4 files changed, 356 insertions(+), 7 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStateTransitionTests.cs 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 568592072d..16b2689730 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -1106,6 +1106,10 @@ internal enum ConnectionError GetConnectionReturnsNull, ConnectionOptionsMissing, CouldNotSwitchToClosedPreviouslyOpenedState, + InvalidSourceStateForSetInnerConnectionTo, + FailedToCommitSetInnerConnectionTo, + InvalidSourceStateForSetInnerConnectionEvent, + FailedToCommitSetInnerConnectionEvent, } internal static Exception InternalConnectionError(ConnectionError internalError) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 7b236afc2b..171bff0109 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -2236,7 +2236,16 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec return result; } - private bool TryOpenInner(TaskCompletionSource retry) + /// + /// Completes the inner open/replace operation and initializes parser state for the active inner connection. + /// + /// Retry continuation used by async open paths. + /// when open initialization completed synchronously; otherwise . + /// + /// The inner connection is snapshotted after the open call so downstream parser access uses a single observed + /// instance and does not rely on a second racy read of . + /// + internal bool TryOpenInner(TaskCompletionSource retry) { // Create a single TimeoutTimer that represents the overall connection-open budget for this // attempt. The timer is threaded through every layer (inner connection state transitions, @@ -2408,11 +2417,32 @@ internal void RemoveWeakReference(object value) // ClosedBusy->Closed (never opened) // Connecting->Closed (exception during open, return to previous closed state) + /// + /// Finalizes a non-evented transition from a transitional inner state to a stable closed state. + /// + /// The target inner connection state object. + /// + /// This path is restricted to transition sources that intentionally serialize external callers while state is + /// being updated. A compare-and-swap prevents stale writers from overwriting a newer inner connection state. + /// internal void SetInnerConnectionTo(DbConnectionInternal to) { Debug.Assert(_innerConnection != null, "null InnerConnection"); Debug.Assert(to != null, "to null InnerConnection"); - _innerConnection = to; + + DbConnectionInternal originalInnerConnection = _innerConnection; + if (originalInnerConnection != DbConnectionClosedBusy.SingletonInstance && + originalInnerConnection != DbConnectionClosedConnecting.SingletonInstance) + { + // SetInnerConnectionTo is only valid while leaving a known transitional state. + throw ADP.InternalConnectionError(ADP.ConnectionError.InvalidSourceStateForSetInnerConnectionTo); + } + + if (originalInnerConnection != Interlocked.CompareExchange(ref _innerConnection, to, originalInnerConnection)) + { + // Reject stale writers when another thread already advanced the state machine. + throw ADP.InternalConnectionError(ADP.ConnectionError.FailedToCommitSetInnerConnectionTo); + } } // Closed->Connecting: prevent set_ConnectionString during Open @@ -2430,14 +2460,37 @@ internal bool SetInnerConnectionFrom(DbConnectionInternal to, DbConnectionIntern // OpenBusy->Closed (previously opened) // Connecting->Open + /// + /// Finalizes an evented state transition from a transitional inner state to a stable target state. + /// + /// The target inner connection state object. + /// + /// This path is intentionally restricted to the expected transition sources ( + /// and ). A compare-and-swap enforces that the transition commits only + /// if no concurrent writer has already changed . + /// internal void SetInnerConnectionEvent(DbConnectionInternal to) { Debug.Assert(_innerConnection != null, "null InnerConnection"); Debug.Assert(to != null, "to null InnerConnection"); - ConnectionState originalState = _innerConnection.State & ConnectionState.Open; + DbConnectionInternal originalInnerConnection = _innerConnection; + if (originalInnerConnection != DbConnectionOpenBusy.SingletonInstance && + originalInnerConnection != DbConnectionClosedConnecting.SingletonInstance) + { + // Evented transitions are valid only from the two expected transitional sources. + throw ADP.InternalConnectionError(ADP.ConnectionError.InvalidSourceStateForSetInnerConnectionEvent); + } + + ConnectionState originalState = originalInnerConnection.State & ConnectionState.Open; ConnectionState currentState = to.State & ConnectionState.Open; + if (originalInnerConnection != Interlocked.CompareExchange(ref _innerConnection, to, originalInnerConnection)) + { + // Ensure event and close-count side effects are tied to a committed transition only. + throw ADP.InternalConnectionError(ADP.ConnectionError.FailedToCommitSetInnerConnectionEvent); + } + if ((originalState != currentState) && (ConnectionState.Closed == currentState)) { unchecked @@ -2446,8 +2499,6 @@ internal void SetInnerConnectionEvent(DbConnectionInternal to) } } - _innerConnection = to; - if (ConnectionState.Closed == originalState && ConnectionState.Open == currentState) { OnStateChange(DbConnectionInternal.StateChangeOpen); @@ -2574,9 +2625,10 @@ private void ConnectionString_Set(ConnectionPoolKey key) flag = SetInnerConnectionFrom(DbConnectionClosedBusy.SingletonInstance, connectionInternal); if (flag) { + // Publish option/pool metadata before releasing the busy state back to a stable closed state. _userConnectionOptions = connectionOptions; _poolGroup = poolGroup; - _innerConnection = DbConnectionClosedNeverOpened.SingletonInstance; + SetInnerConnectionTo(DbConnectionClosedNeverOpened.SingletonInstance); } } if (!flag) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs index c38a30d317..8d151e61cd 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs @@ -36,7 +36,11 @@ private static DbConnectionInternal GetConnectingSingleton() private static void ForceInnerConnection(SqlConnection connection, DbConnectionInternal innerConnectionValue) { - connection.SetInnerConnectionTo(innerConnectionValue); + // Use SetInnerConnectionFrom to transition from the current state via CAS. + // SetInnerConnectionTo requires the connection to already be in a transitional state. + DbConnectionInternal current = connection.InnerConnection; + bool swapped = connection.SetInnerConnectionFrom(innerConnectionValue, current); + Assert.True(swapped, $"CAS failed: expected current state {current.GetType().Name} but it changed concurrently."); } private static bool InvokeTryOpenInner(SqlConnection connection, TaskCompletionSource retry) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStateTransitionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStateTransitionTests.cs new file mode 100644 index 0000000000..b1decd2be0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStateTransitionTests.cs @@ -0,0 +1,289 @@ +// 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.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.ProviderBase; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests; + +/// +/// Regression tests for connection state transitions that can be affected by concurrent state updates. +/// +public class SqlConnectionStateTransitionTests +{ + /// + /// Verifies TryOpenInner does not surface an invalid cast when the inner state changes after TryOpenConnection. + /// + [Fact] + public void TryOpenInner_WhenInnerConnectionChangesAfterTryOpen_ThrowsInvalidOperation() + { + using SqlConnection connection = new(); + connection.ForceNewConnection = false; + bool initialized = connection.SetInnerConnectionFrom(new TestRaceDbConnectionInternal(), DbConnectionClosedNeverOpened.SingletonInstance); + Assert.True(initialized); + + InvalidOperationException exception = Assert.Throws(() => connection.TryOpenInner(null)); + Assert.NotNull(exception); + Assert.Same(DbConnectionClosedPreviouslyOpened.SingletonInstance, connection.InnerConnection); + } + + /// + /// Verifies TryOpenInner does not surface an invalid cast when the inner state changes after TryReplaceConnection. + /// + [Fact] + public void TryOpenInner_WhenInnerConnectionChangesAfterTryReplace_ThrowsInvalidOperation() + { + using SqlConnection connection = new(); + connection.ForceNewConnection = true; + bool initialized = connection.SetInnerConnectionFrom(new TestRaceDbConnectionInternal(), DbConnectionClosedNeverOpened.SingletonInstance); + Assert.True(initialized); + + InvalidOperationException exception = Assert.Throws(() => connection.TryOpenInner(null)); + Assert.NotNull(exception); + Assert.Same(DbConnectionClosedPreviouslyOpened.SingletonInstance, connection.InnerConnection); + } + + /// + /// Verifies evented state transitions reject non-transitional source states. + /// + [Fact] + public void SetInnerConnectionEvent_WhenInnerConnectionIsNotTransitionState_ThrowsInvalidOperation() + { + using SqlConnection connection = new(); + bool initialized = connection.SetInnerConnectionFrom(DbConnectionClosedPreviouslyOpened.SingletonInstance, DbConnectionClosedNeverOpened.SingletonInstance); + Assert.True(initialized); + + int initialCloseCount = connection.CloseCount; + int eventCount = 0; + connection.StateChange += (_, _) => eventCount++; + + InvalidOperationException exception = Assert.Throws( + () => connection.SetInnerConnectionEvent(new TestOpenDbConnectionInternal())); + Assert.NotNull(exception); + Assert.Equal(initialCloseCount, connection.CloseCount); + Assert.Equal(0, eventCount); + } + + /// + /// Verifies direct transition-to-state writes reject non-transitional source states. + /// + [Fact] + public void SetInnerConnectionTo_WhenInnerConnectionIsNotTransitionState_ThrowsInvalidOperation() + { + using SqlConnection connection = new(); + bool initialized = connection.SetInnerConnectionFrom(DbConnectionClosedPreviouslyOpened.SingletonInstance, DbConnectionClosedNeverOpened.SingletonInstance); + Assert.True(initialized); + + InvalidOperationException exception = Assert.Throws( + () => connection.SetInnerConnectionTo(DbConnectionClosedNeverOpened.SingletonInstance)); + Assert.NotNull(exception); + } + + /// + /// Verifies Connecting-to-Open event transitions raise a single open state-change notification. + /// + [Fact] + public void SetInnerConnectionEvent_FromConnectingToOpen_RaisesOpenStateChange() + { + using SqlConnection connection = new(); + bool enteredConnecting = connection.SetInnerConnectionFrom(DbConnectionClosedConnecting.SingletonInstance, DbConnectionClosedNeverOpened.SingletonInstance); + Assert.True(enteredConnecting); + + int initialCloseCount = connection.CloseCount; + int eventCount = 0; + StateChangeEventArgs? stateChange = null; + connection.StateChange += (_, e) => { eventCount++; stateChange = e; }; + + connection.SetInnerConnectionEvent(new TestOpenDbConnectionInternal()); + + Assert.Equal(1, eventCount); + Assert.NotNull(stateChange); + Assert.Equal(ConnectionState.Closed, stateChange!.OriginalState); + Assert.Equal(ConnectionState.Open, stateChange.CurrentState); + Assert.Equal(initialCloseCount, connection.CloseCount); + } + + /// + /// Verifies OpenBusy-to-Closed event transitions raise a single closed state-change notification and increment close count. + /// + [Fact] + public void SetInnerConnectionEvent_FromOpenBusyToClosed_RaisesClosedStateChangeAndIncrementsCloseCount() + { + using SqlConnection connection = new(); + TestOpenDbConnectionInternal openConnection = new(); + + bool enteredOpen = connection.SetInnerConnectionFrom(openConnection, DbConnectionClosedNeverOpened.SingletonInstance); + Assert.True(enteredOpen); + bool enteredOpenBusy = connection.SetInnerConnectionFrom(DbConnectionOpenBusy.SingletonInstance, openConnection); + Assert.True(enteredOpenBusy); + + int initialCloseCount = connection.CloseCount; + int eventCount = 0; + StateChangeEventArgs? stateChange = null; + connection.StateChange += (_, e) => { eventCount++; stateChange = e; }; + + connection.SetInnerConnectionEvent(DbConnectionClosedPreviouslyOpened.SingletonInstance); + + Assert.Equal(1, eventCount); + Assert.NotNull(stateChange); + Assert.Equal(ConnectionState.Open, stateChange!.OriginalState); + Assert.Equal(ConnectionState.Closed, stateChange.CurrentState); + Assert.Equal(initialCloseCount + 1, connection.CloseCount); + } + + /// + /// A test inner connection that forces a post-open state change to simulate race-sensitive transition windows. + /// + private sealed class TestRaceDbConnectionInternal : DbConnectionInternal + { + /// + /// Initializes a closed test connection that permits connection-string updates. + /// + internal TestRaceDbConnectionInternal() : base(ConnectionState.Closed, true, true) + { + } + + /// + /// Gets a placeholder server version for abstract member satisfaction. + /// + public override string ServerVersion => "0"; + + /// + /// Not supported in this test double. + /// + /// Requested isolation level. + /// Never returns; always throws. + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) => throw new NotSupportedException(); + + /// + /// No-op for this test double. + /// + /// Transaction to enlist. + public override void EnlistTransaction(Transaction transaction) + { + } + + /// + /// No-op reset for this test double. + /// + internal override void ResetConnection() + { + } + + /// + /// No-op activation for this test double. + /// + /// Transaction supplied by the caller. + protected override void Activate(Transaction transaction) + { + } + + /// + /// No-op deactivation for this test double. + /// + protected override void Deactivate() + { + } + + /// + /// Simulates a race by replacing the inner connection with a closed singleton after open succeeds. + /// + /// Owning outer connection. + /// Factory used to mutate inner state. + /// Retry continuation. + /// Timeout timer (unused in test). + /// Always . + internal override bool TryOpenConnection( + DbConnection outerConnection, + SqlConnectionFactory connectionFactory, + TaskCompletionSource retry, + TimeoutTimer timeout) + { + // Mirror the real open path by committing through a valid transitional source state first. + connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedConnecting.SingletonInstance, this); + connectionFactory.SetInnerConnectionTo(outerConnection, DbConnectionClosedPreviouslyOpened.SingletonInstance); + return true; + } + + /// + /// Simulates a race by replacing the inner connection with a closed singleton after replace succeeds. + /// + /// Owning outer connection. + /// Factory used to mutate inner state. + /// Retry continuation. + /// Timeout timer (unused in test). + /// Always . + internal override bool TryReplaceConnection( + DbConnection outerConnection, + SqlConnectionFactory connectionFactory, + TaskCompletionSource retry, + TimeoutTimer timeout) + { + // Mirror the real replace path by committing through a valid transitional source state first. + connectionFactory.SetInnerConnectionFrom(outerConnection, DbConnectionClosedBusy.SingletonInstance, this); + connectionFactory.SetInnerConnectionTo(outerConnection, DbConnectionClosedPreviouslyOpened.SingletonInstance); + return true; + } + } + + /// + /// A minimal open-state inner connection used as a non-transitional target in transition validation tests. + /// + private sealed class TestOpenDbConnectionInternal : DbConnectionInternal + { + /// + /// Initializes an open test connection instance. + /// + internal TestOpenDbConnectionInternal() : base(ConnectionState.Open, true, false) + { + } + + /// + /// Gets a placeholder server version for abstract member satisfaction. + /// + public override string ServerVersion => "0"; + + /// + /// Not supported in this test double. + /// + /// Requested isolation level. + /// Never returns; always throws. + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) => throw new NotSupportedException(); + + /// + /// No-op for this test double. + /// + /// Transaction to enlist. + public override void EnlistTransaction(Transaction transaction) + { + } + + /// + /// No-op reset for this test double. + /// + internal override void ResetConnection() + { + } + + /// + /// No-op activation for this test double. + /// + /// Transaction supplied by the caller. + protected override void Activate(Transaction transaction) + { + } + + /// + /// No-op deactivation for this test double. + /// + protected override void Deactivate() + { + } + } +} From c30bbbcc284fecc3219af30a6ddcff1f4aa08bc6 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Mon, 29 Jun 2026 17:56:43 -0500 Subject: [PATCH 170/298] Sort modifiers (#4412) --- .../Handles/SafeLibraryHandle.netcore.cs | 4 +- .../src/Microsoft/Data/Common/AdapterUtil.cs | 14 +- .../IpAddressPreferenceUtilities.cs | 4 +- .../Microsoft/Data/Common/NameValuePair.cs | 6 +- .../ProviderBase/DbReferenceCollection.cs | 6 +- .../DbConnectionPoolAuthenticationContext.cs | 2 +- ...bConnectionPoolAuthenticationContextKey.cs | 2 +- .../ConnectionPool/DbConnectionPoolGroup.cs | 2 +- .../SqlConfigurableRetryFactory.cs | 8 +- .../Data/SqlClient/Server/SmiMetaData.cs | 6 +- .../Data/SqlClient/SqlCachedBuffer.cs | 6 +- .../Microsoft/Data/SqlClient/SqlConnection.cs | 8 +- .../Data/SqlClient/SqlConnectionOptions.cs | 4 +- .../Data/SqlClient/SqlDataAdapter.cs | 12 +- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 132 +++++++++--------- .../Microsoft/Data/SqlClient/SqlException.cs | 2 +- .../Data/SqlClient/SqlInfoMessageEvent.cs | 2 +- .../Data/SqlClient/SqlInternalTransaction.cs | 4 +- .../Data/SqlClient/SqlQueryMetadataCache.cs | 2 +- .../Data/SqlClient/SqlReferenceCollection.cs | 4 +- .../Data/SqlClient/SqlRowUpdatedEvent.cs | 2 +- .../Data/SqlClient/SqlRowUpdatingEvent.cs | 4 +- .../Data/SqlClient/SqlSequentialTextReader.cs | 8 +- .../src/Microsoft/Data/SqlClient/SqlStream.cs | 12 +- .../Data/SqlClient/SqlSymmetricKeyCache.cs | 2 +- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 12 +- .../SqlClient/TdsParserSafeHandles.windows.cs | 6 +- .../Data/SqlClient/TdsParserStateObject.cs | 2 +- .../Data/SqlClient/TdsParserStaticMethods.cs | 14 +- 29 files changed, 146 insertions(+), 146 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Handles/SafeLibraryHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Handles/SafeLibraryHandle.netcore.cs index 64bc92d6d4..54512985f1 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Handles/SafeLibraryHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Handles/SafeLibraryHandle.netcore.cs @@ -8,11 +8,11 @@ namespace Interop.Windows.Handles { - sealed internal class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid + internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid { internal SafeLibraryHandle() : base(true) { } - override protected bool ReleaseHandle() + protected override bool ReleaseHandle() { return Kernel32.Kernel32.FreeLibrary(handle); } 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 16b2689730..2eb9429675 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -105,7 +105,7 @@ internal static InvalidUdtException CreateInvalidUdtException(Type udtType, stri #endregion - static private void TraceException(string trace, Exception e) + private static void TraceException(string trace, Exception e) { Debug.Assert(e != null, "TraceException: null Exception"); if (e is not null) @@ -1030,7 +1030,7 @@ internal static NotImplementedException MethodNotImplemented([CallerMemberName] #region Stream internal static Exception StreamClosed([CallerMemberName] string method = "") => InvalidOperation(StringsHelper.GetString(Strings.ADP_StreamClosed, method)); - static internal Exception InvalidSeekOrigin(string parameterName) => ArgumentOutOfRange(StringsHelper.GetString(Strings.ADP_InvalidSeekOrigin), parameterName); + internal static Exception InvalidSeekOrigin(string parameterName) => ArgumentOutOfRange(StringsHelper.GetString(Strings.ADP_InvalidSeekOrigin), parameterName); internal static IOException ErrorReadingFromStream(Exception internalException) => IO(StringsHelper.GetString(Strings.SqlMisc_StreamErrorMessage), internalException); #endregion @@ -1157,7 +1157,7 @@ internal static Exception NonSeqByteAccess(long badIndex, long currIndex, string #region IDbCommand // IDbCommand.CommandType - static internal ArgumentOutOfRangeException InvalidCommandType(CommandType value) + internal static ArgumentOutOfRangeException InvalidCommandType(CommandType value) { #if DEBUG switch (value) @@ -1442,16 +1442,16 @@ internal static ArgumentException InvalidMixedArgumentOfSecureCredentialAndInteg internal static InvalidOperationException InvalidMixedUsageOfAccessTokenAndIntegratedSecurity() => InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidMixedUsageOfAccessTokenAndIntegratedSecurity)); - static internal InvalidOperationException InvalidMixedUsageOfAccessTokenAndUserIDPassword() + internal static InvalidOperationException InvalidMixedUsageOfAccessTokenAndUserIDPassword() => InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidMixedUsageOfAccessTokenAndUserIDPassword)); - static internal InvalidOperationException InvalidMixedUsageOfAccessTokenAndAuthentication() + internal static InvalidOperationException InvalidMixedUsageOfAccessTokenAndAuthentication() => InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidMixedUsageOfAccessTokenAndAuthentication)); - static internal Exception InvalidMixedUsageOfCredentialAndAccessToken() + internal static Exception InvalidMixedUsageOfCredentialAndAccessToken() => InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidMixedUsageOfCredentialAndAccessToken)); - static internal Exception InvalidMixedUsageOfAccessTokenAndTokenCallback() + internal static Exception InvalidMixedUsageOfAccessTokenAndTokenCallback() => InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidMixedUsageOfAccessTokenAndTokenCallback)); internal static Exception InvalidMixedUsageOfAccessTokenCallbackAndAuthentication() 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 index 747ae157e4..cbd07658f9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/IpAddressPreferenceUtilities.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/IpAddressPreferenceUtilities.cs @@ -13,7 +13,7 @@ internal static class IpAddressPreferenceUtilities /// /// IP Address Preference. /// - private readonly static Dictionary s_preferenceNames = new(StringComparer.InvariantCultureIgnoreCase); + private static readonly Dictionary s_preferenceNames = new(StringComparer.InvariantCultureIgnoreCase); static IpAddressPreferenceUtilities() { @@ -27,7 +27,7 @@ static IpAddressPreferenceUtilities() /// 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. + /// 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) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/NameValuePair.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/NameValuePair.cs index e3db46466a..7505780bb4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/NameValuePair.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/NameValuePair.cs @@ -11,10 +11,10 @@ namespace Microsoft.Data.Common [Serializable] internal sealed class NameValuePair { - readonly private string _name; - readonly private string _value; + private readonly string _name; + private readonly string _value; [OptionalField(VersionAdded = 2)] - readonly private int _length; + private readonly int _length; private NameValuePair _next; internal NameValuePair(string name, string value, int length) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbReferenceCollection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbReferenceCollection.cs index 843f600b59..26db3b7ea7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbReferenceCollection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbReferenceCollection.cs @@ -81,7 +81,7 @@ protected DbReferenceCollection() _lastItemIndex = 0; } - abstract public void Add(object value, int refInfo); + public abstract void Add(object value, int refInfo); protected void AddItem(object value, int refInfo) { @@ -220,9 +220,9 @@ public void Deactivate() } } - abstract protected void NotifyItem(int refInfo, object value); + protected abstract void NotifyItem(int refInfo, object value); - abstract public void Remove(object value); + public abstract void Remove(object value); protected void RemoveItem(object value) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContext.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContext.cs index 2b487b5fb7..db70c57eaa 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContext.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContext.cs @@ -13,7 +13,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// Represents the context of an authentication attempt when using the new active directory based authentication mechanisms. /// All data members, except_isUpdateInProgressCounter, should be immutable. /// - sealed internal class DbConnectionPoolAuthenticationContext + internal sealed class DbConnectionPoolAuthenticationContext { /// /// The value expected in _isUpdateInProgress if a thread has taken a lock on this context, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContextKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContextKey.cs index e12648b3d0..f1cc66601e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContextKey.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContextKey.cs @@ -11,7 +11,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// Represents the key of dbConnectionPoolAuthenticationContext. /// All data members should be immutable and so, hashCode is pre-computed. /// - sealed internal class DbConnectionPoolAuthenticationContextKey + internal sealed class DbConnectionPoolAuthenticationContextKey { /// /// Security Token Service Authority. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 8f62787e30..a7e0d1c48b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -30,7 +30,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool // and once no pools remain, change state from Active->Idle->Disabled // Once Disabled, factory can remove its reference to the pool entry - sealed internal class DbConnectionPoolGroup + internal sealed class DbConnectionPoolGroup { private readonly SqlConnectionOptions _connectionOptions; private readonly ConnectionPoolKey _poolKey; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryFactory.cs index 0bcebdad9a..fae822ac25 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryFactory.cs @@ -37,7 +37,7 @@ public sealed class SqlRetryLogicOption /// public sealed class SqlConfigurableRetryFactory { - private readonly static object s_syncObject = new(); + private static readonly object s_syncObject = new(); /// Default known transient error numbers. // We use a HashSet internally for O(1) lookup performance in the hot path. The public API exposes a ReadOnlyCollection. @@ -50,7 +50,7 @@ private static readonly HashSet s_defaultTransientErrors 1222, // Lock request time out period exceeded. 4060, // Cannot open database "%.*ls" requested by the login. The login failed. 4221, // Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. The replica is not available for login because row versions are missing for transactions that were in-flight when the replica was recycled. The issue can be resolved by rolling back or committing the active transactions on the primary replica. Occurrences of this condition can be minimized by avoiding long write transactions on the primary. - 10060, // An error has occurred while establishing a connection to the server. When connecting to SQL Server, this failure may be caused by the fact that under the default settings SQL Server does not allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.) (Microsoft SQL Server, Error: 10060) + 10060, // An error has occurred while establishing a connection to the server. When connecting to SQL Server, this failure may be caused by the fact that under the default settings SQL Server does not allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.) (Microsoft SQL Server, Error: 10060) 10928, // Resource ID: %d. The %s limit for the database is %d and has been reached. For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. 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. For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again later. 40143, // The service has encountered an error processing your request. Please try again. @@ -74,7 +74,7 @@ public static SqlRetryLogicBaseProvider CreateExponentialRetryProvider(SqlRetryL retryLogicOption != null ? new SqlExponentialIntervalEnumerator(retryLogicOption.DeltaTime, retryLogicOption.MaxTimeInterval, retryLogicOption.MinTimeInterval) : null); /// - public static SqlRetryLogicBaseProvider CreateIncrementalRetryProvider(SqlRetryLogicOption retryLogicOption) + public static SqlRetryLogicBaseProvider CreateIncrementalRetryProvider(SqlRetryLogicOption retryLogicOption) => InternalCreateRetryProvider(retryLogicOption, retryLogicOption != null ? new SqlIncrementalIntervalEnumerator(retryLogicOption.DeltaTime, retryLogicOption.MaxTimeInterval, retryLogicOption.MinTimeInterval) : null); @@ -109,7 +109,7 @@ public static SqlRetryLogicBaseProvider CreateNoneRetryProvider() /// /// Verifies the provider which is not null and doesn't include SqlNoneIntervalEnumerator enumerator object. /// - internal static bool IsRetriable(SqlRetryLogicBaseProvider provider) + internal static bool IsRetriable(SqlRetryLogicBaseProvider provider) => provider is not null && (provider.RetryLogic is null || provider.RetryLogic.RetryIntervalEnumerator is not SqlNoneIntervalEnumerator); /// Return true if the exception is a transient fault. 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 220471394c..f2d430f2e2 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 @@ -112,7 +112,7 @@ internal class SmiMetaData internal static readonly SmiMetaData DefaultDateTimeOffset = new SmiMetaData(SqlDbType.DateTimeOffset, 10, 0, 7, SqlCompareOptions.None); // SqlDbType.DateTimeOffset // No default for generic UDT - internal static SmiMetaData DefaultNVarChar => + internal static SmiMetaData DefaultNVarChar => new SmiMetaData( DefaultNVarChar_NoCollation.SqlDbType, DefaultNVarChar_NoCollation.MaxLength, @@ -530,7 +530,7 @@ private void SetDefaultsForType(SqlDbType dbType) _extendedProperties = smdDflt._extendedProperties; // This is ok due to immutability } - virtual internal string TraceString(int indent) + internal virtual string TraceString(int indent) { string indentStr = new string(' ', indent); string fields = string.Empty; @@ -847,7 +847,7 @@ internal override string TraceString(int indent) // SmiStorageMetaData // - // This class represents the addition of storage-level attributes to the hierarchy (i.e. attributes from + // This class represents the addition of storage-level attributes to the hierarchy (i.e. attributes from // underlying table, source variables, or whatever). // // Most values use Null (either IsNullable == true or CLR null) to indicate "Not specified" state. Selection diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs index f0f7edf099..5810db6d5a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs @@ -51,7 +51,7 @@ internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser cachedBytes = null; } } - + if (cachedBytes == null) { cachedBytes = new List(); @@ -128,7 +128,7 @@ internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser private static void AddByteOrderMark(byte[] byteArr, List cachedBytes) { - // Need to find out if we should add byte order mark or not. + // Need to find out if we should add byte order mark or not. // We need to add this if we are getting ntext xml, not if we are getting binary xml // Binary Xml always begins with the bytes 0xDF and 0xFF // If we aren't getting these, then we are getting Unicode xml @@ -144,7 +144,7 @@ internal Stream ToStream() return new SqlCachedStream(this); } - override public string ToString() + public override string ToString() { if (IsNull) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index 171bff0109..bf99482d41 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -1240,14 +1240,14 @@ protected override void OnStateChange(StateChangeEventArgs stateChange) // PUBLIC METHODS // /// - new public SqlTransaction BeginTransaction() + public new SqlTransaction BeginTransaction() { // this is just a delegate. The actual method tracks executiontime return BeginTransaction(IsolationLevel.Unspecified, null); } /// - new public SqlTransaction BeginTransaction(IsolationLevel iso) + public new SqlTransaction BeginTransaction(IsolationLevel iso) { // this is just a delegate. The actual method tracks executiontime return BeginTransaction(iso, null); @@ -1265,7 +1265,7 @@ public SqlTransaction BeginTransaction(string transactionName) /// [SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")] - override protected DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) { using (SqlClientEventScope.Create("SqlConnection.BeginDbTransaction | API | Object Id {0}, Isolation Level {1}", ObjectID, (int)isolationLevel)) { @@ -1469,7 +1469,7 @@ public override void Close() } /// - new public SqlCommand CreateCommand() + public new SqlCommand CreateCommand() { return new SqlCommand(null, this); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs index a90c760ebb..42c562a4d1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionOptions.cs @@ -984,7 +984,7 @@ internal SqlConnectionEncryptOption ConvertValueToSqlConnectionEncrypt() } } - static internal Dictionary NetlibMapping() + internal static Dictionary NetlibMapping() { const int NetLibCount = 8; @@ -1007,7 +1007,7 @@ static internal Dictionary NetlibMapping() } return hash; } - static internal bool ValidProtocol(string protocol) + internal static bool ValidProtocol(string protocol) { return protocol switch { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs index 3b07ab0706..14d976803b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs @@ -68,7 +68,7 @@ private SqlDataAdapter(SqlDataAdapter from) : base(from) [DefaultValue(null)] [ResCategory(nameof(Strings.DataCategory_Update))] [ResDescription(nameof(Strings.DbDataAdapter_DeleteCommand))] - new public SqlCommand DeleteCommand + public new SqlCommand DeleteCommand { get { return _deleteCommand; } set { _deleteCommand = value; } @@ -85,7 +85,7 @@ IDbCommand IDbDataAdapter.DeleteCommand [DefaultValue(null)] [ResCategory(nameof(Strings.DataCategory_Update))] [ResDescription(nameof(Strings.DbDataAdapter_InsertCommand))] - new public SqlCommand InsertCommand + public new SqlCommand InsertCommand { get { return _insertCommand; } set { _insertCommand = value; } @@ -102,7 +102,7 @@ IDbCommand IDbDataAdapter.InsertCommand [DefaultValue(null)] [ResCategory(nameof(Strings.DataCategory_Fill))] [ResDescription(nameof(Strings.DbDataAdapter_SelectCommand))] - new public SqlCommand SelectCommand + public new SqlCommand SelectCommand { get { return _selectCommand; } set { _selectCommand = value; } @@ -119,7 +119,7 @@ IDbCommand IDbDataAdapter.SelectCommand [DefaultValue(null)] [ResCategory(nameof(Strings.DataCategory_Update))] [ResDescription(nameof(Strings.DbDataAdapter_UpdateCommand))] - new public SqlCommand UpdateCommand + public new SqlCommand UpdateCommand { get { return _updateCommand; } set { _updateCommand = value; } @@ -285,7 +285,7 @@ public event SqlRowUpdatingEventHandler RowUpdating } /// - override protected void OnRowUpdated(RowUpdatedEventArgs value) + protected override void OnRowUpdated(RowUpdatedEventArgs value) { SqlRowUpdatedEventHandler handler = (SqlRowUpdatedEventHandler)Events[s_eventRowUpdated]; if (handler != null && value is SqlRowUpdatedEventArgs args) @@ -296,7 +296,7 @@ override protected void OnRowUpdated(RowUpdatedEventArgs value) } /// - override protected void OnRowUpdating(RowUpdatingEventArgs value) + protected override void OnRowUpdating(RowUpdatingEventArgs value) { SqlRowUpdatingEventHandler handler = (SqlRowUpdatingEventHandler)Events[s_eventRowUpdating]; if (handler != null && value is SqlRowUpdatingEventArgs args) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index db21550aeb..14c8a51b4f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -159,7 +159,7 @@ protected SqlConnection Connection public SensitivityClassification SensitivityClassification { get; internal set; } /// - override public int Depth + public override int Depth { get { @@ -174,7 +174,7 @@ override public int Depth /// // fields/attributes collection - override public int FieldCount + public override int FieldCount { get { @@ -197,7 +197,7 @@ override public int FieldCount } /// - override public bool HasRows + public override bool HasRows { get { @@ -215,7 +215,7 @@ override public bool HasRows } /// - override public bool IsClosed + public override bool IsClosed { get { @@ -363,7 +363,7 @@ internal virtual SmiExtendedMetaData[] GetInternalSmiMetaData() } /// - override public int RecordsAffected + public override int RecordsAffected { get { @@ -406,7 +406,7 @@ internal MultiPartTableName[] TableNames } /// - override public int VisibleFieldCount + public override int VisibleFieldCount { get { @@ -425,7 +425,7 @@ override public int VisibleFieldCount /// // this operator - override public object this[int i] + public override object this[int i] { get { @@ -434,7 +434,7 @@ override public object this[int i] } /// - override public object this[string name] + public override object this[string name] { get { @@ -1093,7 +1093,7 @@ private TdsOperationStatus TryCloseInternal(bool closeReader) } } - virtual internal void CloseReaderFromConnection() + internal virtual void CloseReaderFromConnection() { var parser = _parser; Debug.Assert(parser == null || parser.State != TdsParserState.OpenNotLoggedIn, "Reader on a connection that is not logged in"); @@ -1169,7 +1169,7 @@ private TdsOperationStatus TryConsumeMetaData() } /// - override public string GetDataTypeName(int i) + public override string GetDataTypeName(int i) { SqlStatistics statistics = null; try @@ -1222,7 +1222,7 @@ private string GetDataTypeNameInternal(_SqlMetaData metaData) return dataTypeName; } - virtual internal SqlBuffer.StorageType GetVariantInternalStorageType(int i) + internal virtual 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?"); @@ -1231,7 +1231,7 @@ virtual internal SqlBuffer.StorageType GetVariantInternalStorageType(int i) } /// - override public IEnumerator GetEnumerator() + public override IEnumerator GetEnumerator() { return new DbEnumerator(this, IsCommandBehavior(CommandBehavior.CloseConnection)); } @@ -1240,7 +1240,7 @@ override public IEnumerator GetEnumerator() #if !NETFRAMEWORK [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] #endif - override public Type GetFieldType(int i) + public override Type GetFieldType(int i) { SqlStatistics statistics = null; try @@ -1314,7 +1314,7 @@ private static Type GetVectorFieldType(byte vectorElementType) }; } - virtual internal int GetLocaleId(int i) + internal virtual int GetLocaleId(int i) { _SqlMetaData sqlMetaData = MetaData[i]; int lcid; @@ -1348,7 +1348,7 @@ virtual internal int GetLocaleId(int i) } /// - override public string GetName(int i) + public override string GetName(int i) { CheckMetaDataIsReady(columnIndex: i); @@ -1360,7 +1360,7 @@ override public string GetName(int i) #if NET [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] #endif - override public Type GetProviderSpecificFieldType(int i) + public override Type GetProviderSpecificFieldType(int i) { SqlStatistics statistics = null; try @@ -1424,7 +1424,7 @@ private Type GetProviderSpecificFieldTypeInternal(_SqlMetaData metaData) /// // named field access - override public int GetOrdinal(string name) + public override int GetOrdinal(string name) { SqlStatistics statistics = null; try @@ -1444,13 +1444,13 @@ override public int GetOrdinal(string name) } /// - override public object GetProviderSpecificValue(int i) + public override object GetProviderSpecificValue(int i) { return GetSqlValue(i); } /// - override public int GetProviderSpecificValues(object[] values) + public override int GetProviderSpecificValues(object[] values) { return GetSqlValues(values); } @@ -1482,14 +1482,14 @@ public override DataTable GetSchemaTable() } /// - override public bool GetBoolean(int i) + public override bool GetBoolean(int i) { ReadColumn(i); return _data[i].Boolean; } /// - virtual public XmlReader GetXmlReader(int i) + public virtual 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 @@ -1529,7 +1529,7 @@ virtual public XmlReader GetXmlReader(int i) } /// - override public Stream GetStream(int i) + public override Stream GetStream(int i) { CheckDataIsReady(columnIndex: i); @@ -1577,14 +1577,14 @@ override public Stream GetStream(int i) } /// - override public byte GetByte(int i) + public override byte GetByte(int i) { ReadColumn(i); return _data[i].Byte; } /// - override public long GetBytes(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) + public override long GetBytes(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) { SqlStatistics statistics = null; long cbBytes = 0; @@ -1613,7 +1613,7 @@ override public long GetBytes(int i, long dataIndex, byte[] buffer, int bufferIn } // Used (indirectly) by SqlCommand.CompleteXmlReader - virtual internal long GetBytesInternal(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) + internal virtual long GetBytesInternal(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) { if (_currentTask != null) { @@ -1953,7 +1953,7 @@ internal TdsOperationStatus TryGetBytesInternalSequential(int i, byte[] buffer, } /// - override public TextReader GetTextReader(int i) + public override TextReader GetTextReader(int i) { CheckDataIsReady(columnIndex: i); @@ -2027,13 +2027,13 @@ override public TextReader GetTextReader(int i) /// [EditorBrowsable(EditorBrowsableState.Never)] - override public char GetChar(int i) + public override char GetChar(int i) { throw ADP.NotSupported(); } /// - override public long GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) + public override long GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) { SqlStatistics statistics = null; @@ -2336,7 +2336,7 @@ IDataReader IDataRecord.GetData(int i) #endif /// - override public DateTime GetDateTime(int i) + public override DateTime GetDateTime(int i) { ReadColumn(i); @@ -2358,77 +2358,77 @@ override public DateTime GetDateTime(int i) } /// - override public decimal GetDecimal(int i) + public override decimal GetDecimal(int i) { ReadColumn(i); return _data[i].Decimal; } /// - override public double GetDouble(int i) + public override double GetDouble(int i) { ReadColumn(i); return _data[i].Double; } /// - override public float GetFloat(int i) + public override float GetFloat(int i) { ReadColumn(i); return _data[i].Single; } /// - override public Guid GetGuid(int i) + public override Guid GetGuid(int i) { ReadColumn(i); return _data[i].Guid; } /// - override public short GetInt16(int i) + public override short GetInt16(int i) { ReadColumn(i); return _data[i].Int16; } /// - override public int GetInt32(int i) + public override int GetInt32(int i) { ReadColumn(i); return _data[i].Int32; } /// - override public long GetInt64(int i) + public override long GetInt64(int i) { ReadColumn(i); return _data[i].Int64; } /// - virtual public SqlBoolean GetSqlBoolean(int i) + public virtual SqlBoolean GetSqlBoolean(int i) { ReadColumn(i); return _data[i].SqlBoolean; } /// - virtual public SqlBinary GetSqlBinary(int i) + public virtual SqlBinary GetSqlBinary(int i) { ReadColumn(i, setTimeout: true, allowPartiallyReadColumn: true); return _data[i].SqlBinary; } /// - virtual public SqlByte GetSqlByte(int i) + public virtual SqlByte GetSqlByte(int i) { ReadColumn(i); return _data[i].SqlByte; } /// - virtual public SqlBytes GetSqlBytes(int i) + public virtual SqlBytes GetSqlBytes(int i) { ReadColumn(i); SqlBinary data = _data[i].SqlBinary; @@ -2436,7 +2436,7 @@ virtual public SqlBytes GetSqlBytes(int i) } /// - virtual public SqlChars GetSqlChars(int i) + public virtual SqlChars GetSqlChars(int i) { ReadColumn(i); SqlString data; @@ -2453,70 +2453,70 @@ virtual public SqlChars GetSqlChars(int i) } /// - virtual public SqlDateTime GetSqlDateTime(int i) + public virtual SqlDateTime GetSqlDateTime(int i) { ReadColumn(i); return _data[i].SqlDateTime; } /// - virtual public SqlDecimal GetSqlDecimal(int i) + public virtual SqlDecimal GetSqlDecimal(int i) { ReadColumn(i); return _data[i].SqlDecimal; } /// - virtual public SqlGuid GetSqlGuid(int i) + public virtual SqlGuid GetSqlGuid(int i) { ReadColumn(i); return _data[i].SqlGuid; } /// - virtual public SqlDouble GetSqlDouble(int i) + public virtual SqlDouble GetSqlDouble(int i) { ReadColumn(i); return _data[i].SqlDouble; } /// - virtual public SqlInt16 GetSqlInt16(int i) + public virtual SqlInt16 GetSqlInt16(int i) { ReadColumn(i); return _data[i].SqlInt16; } /// - virtual public SqlInt32 GetSqlInt32(int i) + public virtual SqlInt32 GetSqlInt32(int i) { ReadColumn(i); return _data[i].SqlInt32; } /// - virtual public SqlInt64 GetSqlInt64(int i) + public virtual SqlInt64 GetSqlInt64(int i) { ReadColumn(i); return _data[i].SqlInt64; } /// - virtual public SqlMoney GetSqlMoney(int i) + public virtual SqlMoney GetSqlMoney(int i) { ReadColumn(i); return _data[i].SqlMoney; } /// - virtual public SqlSingle GetSqlSingle(int i) + public virtual SqlSingle GetSqlSingle(int i) { ReadColumn(i); return _data[i].SqlSingle; } /// - virtual public SqlString GetSqlString(int i) + public virtual SqlString GetSqlString(int i) { ReadColumn(i); @@ -2529,7 +2529,7 @@ virtual public SqlString GetSqlString(int i) } /// - virtual public SqlXml GetSqlXml(int i) + public virtual SqlXml GetSqlXml(int i) { ReadColumn(i); SqlXml sx = null; @@ -2540,7 +2540,7 @@ virtual public SqlXml GetSqlXml(int i) } /// - virtual public SqlJson GetSqlJson(int i) + public virtual SqlJson GetSqlJson(int i) { ReadColumn(i); SqlJson json = _data[i].IsNull ? SqlJson.Null : _data[i].SqlJson; @@ -2548,7 +2548,7 @@ virtual public SqlJson GetSqlJson(int i) } /// - virtual public SqlVector GetSqlVector(int i) where T : unmanaged + public virtual SqlVector GetSqlVector(int i) where T : unmanaged { if (typeof(T) != typeof(float)) { @@ -2560,7 +2560,7 @@ virtual public SqlVector GetSqlVector(int i) where T : unmanaged } /// - virtual public object GetSqlValue(int i) + public virtual object GetSqlValue(int i) { SqlStatistics statistics = null; try @@ -2628,7 +2628,7 @@ private object GetSqlValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met } /// - virtual public int GetSqlValues(object[] values) + public virtual int GetSqlValues(object[] values) { SqlStatistics statistics = null; try @@ -2657,7 +2657,7 @@ virtual public int GetSqlValues(object[] values) } /// - override public string GetString(int i) + public override string GetString(int i) { ReadColumn(i); // Convert 2008 value to string if type system knob is 2005 or earlier @@ -2670,7 +2670,7 @@ override public string GetString(int i) } /// - override public T GetFieldValue(int i) + public override T GetFieldValue(int i) { SqlStatistics statistics = null; try @@ -2687,7 +2687,7 @@ override public T GetFieldValue(int i) } /// - override public object GetValue(int i) + public override object GetValue(int i) { SqlStatistics statistics = null; try @@ -2704,7 +2704,7 @@ override public object GetValue(int i) } /// - virtual public TimeSpan GetTimeSpan(int i) + public virtual TimeSpan GetTimeSpan(int i) { ReadColumn(i); @@ -2726,7 +2726,7 @@ virtual public TimeSpan GetTimeSpan(int i) } /// - virtual public DateTimeOffset GetDateTimeOffset(int i) + public virtual DateTimeOffset GetDateTimeOffset(int i) { ReadColumn(i); @@ -3069,7 +3069,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met } /// - override public int GetValues(object[] values) + public override int GetValues(object[] values) { SqlStatistics statistics = null; bool sequentialAccess = IsCommandBehavior(CommandBehavior.SequentialAccess); @@ -3316,7 +3316,7 @@ private bool IsRowToken(byte token) } /// - override public bool IsDBNull(int i) + public override bool IsDBNull(int i) { #if NETFRAMEWORK if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && ((_sharedState._nextColumnHeaderToRead > i + 1) || (_lastColumnWithDataChunkRead > i))) @@ -3349,7 +3349,7 @@ protected internal bool IsCommandBehavior(CommandBehavior condition) } /// - override public bool NextResult() + public override bool NextResult() { if (_currentTask != null) { @@ -4994,7 +4994,7 @@ private static Task ReadAsyncExecute(Task task, object state) } /// - override public Task IsDBNullAsync(int i, CancellationToken cancellationToken) + public override Task IsDBNullAsync(int i, CancellationToken cancellationToken) { try { @@ -5129,7 +5129,7 @@ private static Task IsDBNullAsyncExecute(Task task, object state) } /// - override public Task GetFieldValueAsync(int i, CancellationToken cancellationToken) + public override Task GetFieldValueAsync(int i, CancellationToken cancellationToken) { try { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs index d06a02413f..c1ef429204 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs @@ -114,7 +114,7 @@ public override void GetObjectData(SerializationInfo si, StreamingContext contex public byte State => Errors.Count > 0 ? Errors[0].State : default; /// - override public string Source => DbConnectionStringDefaults.ApplicationName; + public override string Source => DbConnectionStringDefaults.ApplicationName; #if NET diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInfoMessageEvent.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInfoMessageEvent.cs index 5392419a05..745cddc67b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInfoMessageEvent.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInfoMessageEvent.cs @@ -29,6 +29,6 @@ internal SqlInfoMessageEventArgs(SqlException exception) /// // MDAC 68482 - override public string ToString() => Message; + public override string ToString() => Message; } } 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 69474315cd..96ccc8a8af 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs @@ -30,7 +30,7 @@ internal enum TransactionType Context = 5, // only valid in proc. } - sealed internal class SqlInternalTransaction + internal sealed class SqlInternalTransaction { internal const long NullTransactionId = 0; @@ -456,7 +456,7 @@ private void ZombieParent() _parent = null; } - internal string TraceString() => string.Format(/*IFormatProvider*/ null, + internal string TraceString() => string.Format(/*IFormatProvider*/ null, "(ObjId={0}, tranId={1}, state={2}, type={3}, open={4}, disp={5}", ObjectID, _transactionId, _transactionState, _transactionType, _openResultCount, _disposing); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs index 3fb1ec1db6..40618faf03 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs @@ -17,7 +17,7 @@ namespace Microsoft.Data.SqlClient /// /// Implements a cache of query parameter metadata that is used to avoid the extra roundtrip to the server for every execution of the same query. /// - sealed internal class SqlQueryMetadataCache + internal sealed class SqlQueryMetadataCache { private const int CacheSize = 2000; // Cache size in number of entries. private const int CacheTrimThreshold = 300; // Threshold above the cache size when we start trimming. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlReferenceCollection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlReferenceCollection.cs index b0b5917551..a88485b034 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlReferenceCollection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlReferenceCollection.cs @@ -9,7 +9,7 @@ namespace Microsoft.Data.SqlClient { - sealed internal class SqlReferenceCollection : DbReferenceCollection + internal sealed class SqlReferenceCollection : DbReferenceCollection { private sealed class FindLiveReaderContext { @@ -30,7 +30,7 @@ private sealed class FindLiveReaderContext internal const int CommandTag = 2; internal const int BulkCopyTag = 3; - private readonly static Func s_hasOpenReaderFunc = HasOpenReaderPredicate; + private static readonly Func s_hasOpenReaderFunc = HasOpenReaderPredicate; private static FindLiveReaderContext s_cachedFindLiveReaderContext; public override void Add(object value, int tag) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlRowUpdatedEvent.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlRowUpdatedEvent.cs index a09f06ad18..a9ba58d609 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlRowUpdatedEvent.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlRowUpdatedEvent.cs @@ -17,7 +17,7 @@ public SqlRowUpdatedEventArgs(DataRow row, IDbCommand command, StatementType sta } /// - new public SqlCommand Command + public new SqlCommand Command { get { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlRowUpdatingEvent.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlRowUpdatingEvent.cs index d0d3b58050..cff099953f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlRowUpdatingEvent.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlRowUpdatingEvent.cs @@ -17,14 +17,14 @@ public SqlRowUpdatingEventArgs(DataRow row, IDbCommand command, StatementType st } /// - new public SqlCommand Command + public new SqlCommand Command { get { return (base.Command as SqlCommand); } set { base.Command = value; } } /// - override protected IDbCommand BaseCommand + protected override IDbCommand BaseCommand { get { return base.BaseCommand; } set { base.BaseCommand = (value as SqlCommand); } 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 b5a00cde4e..4547743f2d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs @@ -13,7 +13,7 @@ namespace Microsoft.Data.SqlClient { - sealed internal class SqlSequentialTextReader : System.IO.TextReader + internal sealed class SqlSequentialTextReader : System.IO.TextReader { private SqlDataReader _reader; // The SqlDataReader that we are reading data from private readonly int _columnIndex; // The index of out column in the table @@ -417,7 +417,7 @@ private int DecodeBytesToChars(byte[] inBuffer, int inBufferCount, char[] outBuf { _leftOverByteBufferUsed = inBufferCount - bytesUsed; _leftOverBytes = ArrayPool.Shared.Rent(_leftOverByteBufferUsed); - + Buffer.BlockCopy(inBuffer, bytesUsed, _leftOverBytes, 0, _leftOverByteBufferUsed); } else @@ -494,7 +494,7 @@ internal static void ValidateReadParameters(char[] buffer, int index, int count) } } - sealed internal class SqlUnicodeEncoding : UnicodeEncoding + internal sealed class SqlUnicodeEncoding : UnicodeEncoding { private static readonly SqlUnicodeEncoding s_singletonEncoding = new(); @@ -517,7 +517,7 @@ public static Encoding SqlUnicodeEncodingInstance get { return s_singletonEncoding; } } - sealed private class SqlUnicodeDecoder : Decoder + private sealed class SqlUnicodeDecoder : Decoder { public override int GetCharCount(byte[] bytes, int index, int count) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStream.cs index 653faf7213..bd9f75f4a7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStream.cs @@ -13,7 +13,7 @@ namespace Microsoft.Data.SqlClient { - sealed internal class SqlStream : Stream + internal sealed class SqlStream : Stream { private SqlDataReader _reader; // reader we will stream off private readonly int _columnOrdinal; @@ -93,7 +93,7 @@ public override int Read(byte[] buffer, int offset, int count) throw ADP.ArgumentOutOfRange(nameof(count)); } - // Need to find out if we should add byte order mark or not. + // Need to find out if we should add byte order mark or not. // We need to add this if we are getting ntext xml, not if we are getting binary xml // Binary Xml always begins with the bytes 0xDF and 0xFF // If we aren't getting these, then we are getting unicode xml @@ -265,9 +265,9 @@ internal XmlReader ToXmlReader(bool async = false) // XmlTextReader does not read all the bytes off the network buffers, so we have to cache it here in the random access // case. This causes double buffering and is a perf hit, but this is not the high perf way for accessing this type of data. - // In the case of sequential access, we do not have to do any buffering since the XmlTextReader we return can become + // In the case of sequential access, we do not have to do any buffering since the XmlTextReader we return can become // invalid as soon as we move off the current column. - sealed internal class SqlCachedStream : Stream + internal sealed class SqlCachedStream : Stream { private int _currentPosition; // Position within the current array byte private int _currentArrayIndex; // Index into the _cachedBytes List @@ -475,7 +475,7 @@ private long TotalLength } } - sealed internal class SqlStreamingXml + internal sealed class SqlStreamingXml { private static readonly XmlWriterSettings s_writerSettings = new() { CloseOutput = true, ConformanceLevel = ConformanceLevel.Fragment }; @@ -580,7 +580,7 @@ public long GetChars(long dataIndex, char[] buffer, int bufferIndex, int length) return (long)cnt; } - // This method duplicates the work of XmlWriter.WriteNode except that it reads one element at a time + // This method duplicates the work of XmlWriter.WriteNode except that it reads one element at a time // instead of reading the entire node like XmlWriter. private void WriteXmlElement() { 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 d88833abed..6618288b32 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs @@ -13,7 +13,7 @@ namespace Microsoft.Data.SqlClient /// /// Implements a cache of Symmetric Keys (once they are decrypted).Useful for rapidly decrypting multiple data values. /// - sealed internal class SqlSymmetricKeyCache + internal sealed class SqlSymmetricKeyCache { private readonly MemoryCache _cache; private static readonly SqlSymmetricKeyCache _singletonInstance = new(); 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 678697d221..e80387063b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -85,16 +85,16 @@ internal static Exception InvalidSSPIPacketSize() { return ADP.Argument(StringsHelper.GetString(Strings.SQL_InvalidSSPIPacketSize)); } - static internal Exception NullEmptyTransactionName() + internal static Exception NullEmptyTransactionName() { return ADP.Argument(StringsHelper.GetString(Strings.SQL_NullEmptyTransactionName)); } - static internal Exception UserInstanceFailoverNotCompatible() + internal static Exception UserInstanceFailoverNotCompatible() { return ADP.Argument(StringsHelper.GetString(Strings.SQL_UserInstanceFailoverNotCompatible)); } - static internal Exception CredentialsNotProvided(SqlAuthenticationMethod auth) + internal static Exception CredentialsNotProvided(SqlAuthenticationMethod auth) { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_CredentialsNotProvided, DbConnectionStringUtilities.AuthenticationTypeToString(auth))); } @@ -636,7 +636,7 @@ internal static Exception SqlDependencyNoMatchingServerDatabaseStart() // // SQL.SqlDelegatedTransaction // - static internal Exception CannotCompleteDelegatedTransactionWithOpenResults( + internal static Exception CannotCompleteDelegatedTransactionWithOpenResults( SqlConnectionInternal internalConnection, bool marsOn) { @@ -1860,7 +1860,7 @@ internal static Exception EmptyProviderName() // Merged Provider // - static internal Exception ContextConnectionIsUnsupported() + internal static Exception ContextConnectionIsUnsupported() { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ContextConnectionIsUnsupported)); } @@ -1909,7 +1909,7 @@ internal static Exception SocketDidNotThrow() } - sealed internal class SQLMessage + internal sealed class SQLMessage { private SQLMessage() { /* prevent utility class from being instantiated*/ } 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 ed55a76fbb..8242dd997f 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 @@ -74,7 +74,7 @@ public bool ClientOSEncryptionSupport public override bool IsInvalid => (IntPtr.Zero == base.handle); - override protected bool ReleaseHandle() + protected override bool ReleaseHandle() { if (base.handle != IntPtr.Zero) { @@ -224,7 +224,7 @@ public override bool IsInvalid } } - override protected bool ReleaseHandle() + protected override bool ReleaseHandle() { // NOTE: The SafeHandle class guarantees this will be called exactly once. IntPtr ptr = base.handle; @@ -267,7 +267,7 @@ public override bool IsInvalid } } - override protected bool ReleaseHandle() + protected override bool ReleaseHandle() { // NOTE: The SafeHandle class guarantees this will be called exactly once. IntPtr ptr = base.handle; 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 921fb3f30b..b3b6becbdf 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -25,7 +25,7 @@ namespace Microsoft.Data.SqlClient { - sealed internal class LastIOTimer + internal sealed class LastIOTimer { internal long _value; } 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 a384095254..fec2779f82 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -17,7 +17,7 @@ private TdsParserStaticMethods() { /* prevent utility class from being insantiat // Static methods // - static internal void AliasRegistryLookup(ref string host, ref string protocol) + internal static void AliasRegistryLookup(ref string host, ref string protocol) { if (!string.IsNullOrEmpty(host)) { @@ -86,12 +86,12 @@ static internal void AliasRegistryLookup(ref string host, ref string protocol) // Obfuscate password to be sent to SQL Server // Blurb from the TDS spec at https://msdn.microsoft.com/en-us/library/dd304523.aspx - // "Before submitting a password from the client to the server, for every byte in the password buffer - // starting with the position pointed to by IbPassword, the client SHOULD first swap the four high bits - // with the four low bits and then do a bit-XOR with 0xA5 (10100101). After reading a submitted password, - // for every byte in the password buffer starting with the position pointed to by IbPassword, the server SHOULD + // "Before submitting a password from the client to the server, for every byte in the password buffer + // starting with the position pointed to by IbPassword, the client SHOULD first swap the four high bits + // with the four low bits and then do a bit-XOR with 0xA5 (10100101). After reading a submitted password, + // for every byte in the password buffer starting with the position pointed to by IbPassword, the server SHOULD // first do a bit-XOR with 0xA5 (10100101) and then swap the four high bits with the four low bits." - // The password exchange during Login phase happens over a secure channel i.e. SSL/TLS + // The password exchange during Login phase happens over a secure channel i.e. SSL/TLS // Note: The same logic is used in SNIPacketSetData (SniManagedWrapper) to encrypt passwords stored in SecureString // If this logic changed, SNIPacketSetData needs to be changed as well internal static byte[] ObfuscatePassword(string password) @@ -164,7 +164,7 @@ internal static int GetCurrentThreadIdForTdsLoginOnly() private static byte[] s_nicAddress = null; - static internal byte[] GetNetworkPhysicalAddressForTdsLoginOnly() + internal static byte[] GetNetworkPhysicalAddressForTdsLoginOnly() { if (s_nicAddress != null) { From 3c522bccbe3491e465761aac0e6c169786e1546c Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Tue, 30 Jun 2026 11:55:50 -0700 Subject: [PATCH 171/298] Docs | Fix server certificate docs (#4408) --- .../Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index 98daa768b5..4014bd1450 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -1330,10 +1330,7 @@ Database = AdventureWorks [!NOTE] -> This property only applies when using Integrated Security mode, otherwise it is ignored. +This property corresponds to the "Server Certificate" and "ServerCertificate" keys within the connection string. ]]> From c97dbce0faabab962f97c71de43c245f696d680c Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 2 Jul 2026 07:31:52 -0300 Subject: [PATCH 172/298] Consolidate OS detection to OsConstants (#4255) * Consolidate OS detection to OsConstants - Create new OsConstants class with IsWindows, IsLinux, IsMacOS, IsFreeBSD, and IsUnix fields - Add comprehensive XML documentation and explain why static constructor is preferred over [ModuleInitializer] (CA2255 rule) - Replace all RuntimeInformation.IsOSPlatform() calls with OsConstants fields in: - TdsParserStaticMethods.cs - UserAgent.cs - AdapterUtil.cs (now forwards to OsConstants) - Replace Environment.OSVersion.Platform checks with OsConstants in: - SqlColumnEncryptionCertificateStoreProvider.cs (3 instances) - SqlUtil.cs (3 instances) - Replace OperatingSystem static methods with OsConstants in: - LocalAppContextSwitches.cs (uses IsUnix for clearer semantics) - TdsParser.cs - Replace all ADP.IsWindows usage with OsConstants in: - SqlFileStream.cs - SqlColumnEncryptionCngProvider.cs (4 instances) - SqlColumnEncryptionCspProvider.cs (4 instances) - Remove unused AdapterUtil.IsWindows property Benefits: - Single point of control for OS detection - Improved JIT optimization through static readonly cached flags - Clearer semantics with IsUnix flag for Unix-like platforms - Consistent API surface across the codebase * Address Copilot OS guard feedback - Remove OsConstants.IsUnix to avoid non-Windows ambiguity - Update Windows-only guards in SqlFileStream, CNG, CSP, and switch logic - Keep explicit OS flags (Windows/Linux/macOS/FreeBSD) * Expose OsConstants flags as intrinsic properties for IL trimming Replace the static readonly fields and static constructor in OsConstants with properties that directly return RuntimeInformation.IsOSPlatform(...). The IL trimmer cannot analyze a static constructor, so the cached-field design left OS-specific code (including Windows-only native dependencies) in apps published for other platforms. Returning the intrinsic directly lets the trimmer fold each guard to a constant and drop the dead branch. Also document why the type exists (brevity and encapsulation), the inline-guard requirement for trimming, and reconcile the stale remark about JIT branch elision. Addresses PR #4255 review feedback from @edwardneal. * Use OperatingSystem.Is*() on .NET for JIT-intrinsic OS checks On modern .NET, OperatingSystem.IsWindows()/IsLinux()/IsMacOS()/IsFreeBSD() are JIT intrinsics that get constant-folded (and are recognized by the IL trimmer), so switch the OsConstants properties to them under #if NET. .NET Framework keeps RuntimeInformation.IsOSPlatform(...) since the OperatingSystem platform-check helpers do not exist there. This addresses the automated reviewer feedback that RuntimeInformation .IsOSPlatform(...) misses the JIT constant-folding optimization on net8/net9, while preserving the trimming correctness from the prior change. Addresses PR #4255 review feedback. --- .../src/Microsoft/Data/Common/AdapterUtil.cs | 18 +-- .../Data/SqlClient/LocalAppContextSwitches.cs | 2 +- .../Microsoft/Data/SqlClient/OsConstants.cs | 107 ++++++++++++++++++ ...olumnEncryptionCertificateStoreProvider.cs | 7 +- .../SqlColumnEncryptionCngProvider.cs | 8 +- .../SqlColumnEncryptionCspProvider.cs | 8 +- .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 6 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- .../Data/SqlClient/TdsParserStaticMethods.cs | 3 +- .../src/Microsoft/Data/SqlClient/UserAgent.cs | 13 +-- .../Microsoft/Data/SqlTypes/SqlFileStream.cs | 2 +- 11 files changed, 132 insertions(+), 44 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs 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 2eb9429675..e8ec7c9e0e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -65,22 +65,6 @@ internal static partial class ADP /// internal const int MaxBufferAccessTokenExpiry = 600; - /// - /// This member returns true if the current OS platform is Windows. - /// - /// - /// This is a const on .NET Framework, and a property on .NET Core, because of differing API availability and JIT requirements. - /// .NET Framework will perform basic dead branch elimination when a const value is encountered, while .NET Core can trim Windows-specific - /// code when published to non-Windows platforms. - /// .NET Core's trimming is very limited though, so this must be used inline within methods to throw PlatformNotSupportedException, - /// rather than in a throw helper. - /// - #if NETFRAMEWORK - public const bool IsWindows = true; - #else - public static bool IsWindows => OperatingSystem.IsWindows(); - #endif - #region UDT #if NETFRAMEWORK @@ -441,7 +425,7 @@ internal static ArgumentOutOfRangeException InvalidCommandBehavior(CommandBehavi internal static object LocalMachineRegistryValue(string subkey, string queryvalue) { #if NET - if (!IsWindows) + if (!OsConstants.IsWindows) { // No registry in non-Windows environments return null; 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 d951bb5d30..85f8c87937 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -635,7 +635,7 @@ public static bool UseManagedNetworking return s_useManagedNetworking == SwitchValue.True; } - if (!OperatingSystem.IsWindows()) + if (!OsConstants.IsWindows) { s_useManagedNetworking = SwitchValue.True; return true; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs new file mode 100644 index 0000000000..b496141693 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/OsConstants.cs @@ -0,0 +1,107 @@ +// 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; + +namespace Microsoft.Data.SqlClient; + +/// +/// Provides platform detection flags for OS-specific code paths. +/// +/// +/// This type exists to keep OS detection terse and in one place: callers write +/// OsConstants.IsWindows instead of repeating RuntimeInformation.IsOSPlatform(...) +/// throughout the codebase. Centralizing it also gives us a single seam to adjust if a specific +/// OS needs special handling (for example, a different detection mechanism, a finer-grained +/// flag, or an override for testing) without touching every call site. +/// +/// +/// Each flag is exposed as a property that directly returns an OS check. On .NET, that check is +/// OperatingSystem.Is*(), which the JIT treats as an intrinsic and constant-folds (and which +/// the IL trimmer also recognizes). On .NET Framework — where the +/// platform-check helpers do not exist — the check falls back to +/// . Either way, returning the check directly is +/// deliberate: caching the results in static fields (for example, via a static constructor) would +/// defeat both the JIT folding and the trimmer — the trimmer cannot analyze a static constructor, +/// so it would be unable to prove which branches are dead and would keep OS-specific code (including +/// Windows-only native dependencies) in apps published for other platforms. +/// +/// +/// +/// IL trimming note: when a downstream app is published for a specific OS (a RID-specific publish), +/// the IL trimmer substitutes the underlying / +/// OperatingSystem.Is* checks with constant true/false for the target OS and +/// then removes the dead branch (along with everything it transitively references, such as +/// Windows-only native entry points). This is what lets a single cross-platform assembly trim +/// away the OS-specific code that does not apply to the published target. +/// +/// +/// For this to work, each guard must be used inline in the same method as the OS-specific +/// code it gates. The trimmer's constant folding is shallow: it reasons within the method that +/// contains the guard, so a guard hidden behind a helper traps the constant inside that helper and +/// leaves the protected code reachable (and therefore not trimmed). +/// +/// +/// Do — guard inline, so the trimmer can drop the branch and its dependencies: +/// +/// if (OsConstants.IsWindows) +/// { +/// WindowsOnlyNativeCall(); // becomes `if (false) { ... }` off-Windows, then removed +/// } +/// +/// +/// +/// Don't — hide the guard in a throw helper; the trimmer cannot prove the call below is dead: +/// +/// static void ThrowIfNotWindows() +/// { +/// if (!OsConstants.IsWindows) throw new PlatformNotSupportedException(); +/// } +/// // caller: +/// ThrowIfNotWindows(); +/// WindowsOnlyNativeCall(); // still reachable to the trimmer; kept even off-Windows +/// +/// +/// +internal static class OsConstants +{ + /// + /// Gets a value indicating whether the runtime is executing on Windows. + /// + #if NET + internal static bool IsWindows => OperatingSystem.IsWindows(); + #else + internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + #endif + + /// + /// Gets a value indicating whether the runtime is executing on Linux. + /// + #if NET + internal static bool IsLinux => OperatingSystem.IsLinux(); + #else + internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + #endif + + /// + /// Gets a value indicating whether the runtime is executing on macOS. + /// + #if NET + internal static bool IsMacOS => OperatingSystem.IsMacOS(); + #else + internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + #endif + + #if NET + /// + /// Gets a value indicating whether the runtime is executing on FreeBSD. + /// + /// + /// FreeBSD support is only available in .NET 5+ and later. This property will be + /// false on .NET Framework or if the runtime does not support FreeBSD detection. + /// + internal static bool IsFreeBSD => OperatingSystem.IsFreeBSD(); + #endif +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs index b02dd24af1..f0438cd162 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs @@ -57,7 +57,7 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe /// Gets a string array containing valid certificate locations. /// private static string[] ValidCertificateLocations => - Environment.OSVersion.Platform == PlatformID.Win32NT + OsConstants.IsWindows ? [CertLocationLocalMachine, CertLocationCurrentUser] : [CertLocationCurrentUser]; @@ -158,14 +158,13 @@ private static RSA GetCertificatePrivateKeyByPath(string keyPath, bool isSystemO } // Extract the store location where the cert is stored - if (storeLocationSpan.IsEmpty - && Environment.OSVersion.Platform == PlatformID.Win32NT) + if (storeLocationSpan.IsEmpty && OsConstants.IsWindows) { // Default to Local Machine on Windows. Non-Windows platforms only support CurrentUser storeLocation = StoreLocation.LocalMachine; } else if (storeLocationSpan.Equals(CertLocationLocalMachine.AsSpan(), StringComparison.OrdinalIgnoreCase) - && Environment.OSVersion.Platform == PlatformID.Win32NT) + && OsConstants.IsWindows) { storeLocation = StoreLocation.LocalMachine; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs index fc65808cf1..4d0d79b750 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.cs @@ -149,7 +149,7 @@ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out /// public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(); } @@ -180,7 +180,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(); } @@ -211,7 +211,7 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) { - throw ADP.IsWindows + throw OsConstants.IsWindows ? new NotSupportedException() : new PlatformNotSupportedException(); } @@ -219,7 +219,7 @@ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool a /// public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature) { - throw ADP.IsWindows + throw OsConstants.IsWindows ? new NotSupportedException() : new PlatformNotSupportedException(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs index 73ee585dfa..dc956d9c71 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.cs @@ -151,7 +151,7 @@ private static int GetProviderType(string providerName, string keyPath, bool isS /// public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(); } @@ -182,7 +182,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(); } @@ -213,7 +213,7 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? /// public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations) { - throw ADP.IsWindows + throw OsConstants.IsWindows ? new NotSupportedException() : new PlatformNotSupportedException(); } @@ -221,7 +221,7 @@ public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool a /// public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature) { - throw ADP.IsWindows + throw OsConstants.IsWindows ? new NotSupportedException() : new PlatformNotSupportedException(); } 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 e80387063b..c9388a42f1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -1141,7 +1141,7 @@ internal static Exception LargeCertificatePathLength(int actualLength, int maxLe internal static Exception NullCertificatePath(string[] validLocations, bool isSystemOp) { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (OsConstants.IsWindows) { Debug.Assert(validLocations.Length == 2); if (isSystemOp) @@ -1193,7 +1193,7 @@ internal static Exception NullCngKeyPath(bool isSystemOp) internal static Exception InvalidCertificatePath(string actualCertificatePath, string[] validLocations, bool isSystemOp) { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (OsConstants.IsWindows) { Debug.Assert(validLocations.Length == 2); if (isSystemOp) @@ -1329,7 +1329,7 @@ internal static Exception InvalidCngKey(string masterKeyPath, string cngProvider internal static Exception InvalidCertificateLocation(string certificateLocation, string certificatePath, string[] validLocations, bool isSystemOp) { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (OsConstants.IsWindows) { Debug.Assert(validLocations.Length == 2); if (isSystemOp) 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 9f37d30015..d53bf6f827 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -977,7 +977,7 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ // Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete // before calling SNISecGenClientContext). #if NET - if (OperatingSystem.IsWindows()) + if (OsConstants.IsWindows) #endif { error = _physicalStateObj.WaitForSSLHandShakeToComplete(out protocol); 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 fec2779f82..63ba1945cc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; -using System.Runtime.InteropServices; using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient @@ -173,7 +172,7 @@ internal static byte[] GetNetworkPhysicalAddressForTdsLoginOnly() byte[] nicAddress = null; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OsConstants.IsWindows) { // NIC address is stored in NetworkAddress key. However, if NetworkAddressLocal key // has a value that is not zero, then we cannot use the NetworkAddress key and must diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs index a8fcf7ca88..63cd220d6c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs @@ -120,25 +120,24 @@ static UserAgent() // specific values. // string osType = Unknown; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OsConstants.IsWindows) { osType = Windows; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + else if (OsConstants.IsLinux) { osType = Linux; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + else if (OsConstants.IsMacOS) { osType = macOS; } - // The FreeBSD platform doesn't exist in .NET Framework at all. - #if NET - else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) +#if NET + else if (OsConstants.IsFreeBSD) { osType = FreeBSD; } - #endif +#endif // Build it! Value = Build( diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs index 4c48890fc4..6059705910 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.cs @@ -102,7 +102,7 @@ public SqlFileStream( FileOptions options, long allocationSize) { - if (!ADP.IsWindows) + if (!OsConstants.IsWindows) { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } From 36d2b9d864a7651e240ad52cbd68eec7b9e8eeec Mon Sep 17 00:00:00 2001 From: priyankatiwari08 Date: Thu, 2 Jul 2026 16:30:36 +0530 Subject: [PATCH 173/298] Implement pool shutdown for ChannelDbConnectionPool and harden WaitHandleDbConnectionPool shutdown (#4302) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement pool shutdown for ChannelDbConnectionPool and harden WaitHandleDbConnectionPool shutdown (AB#44828) ChannelDbConnectionPool: - Shutdown() now transitions State to ShuttingDown (FR-001), completes the idle channel writer to unblock async waiters (FR-002, FR-007), and drains and destroys buffered idle connections (FR-003). Implementation is idempotent via Interlocked.CompareExchange (FR-006). - Startup() emits a trace and is a structural counterpart to Shutdown. - ReturnInternalConnection no longer asserts on TryWrite; if the channel was completed by a concurrent shutdown, the returning connection is destroyed (FR-004 race-window guard). - IdleConnectionChannel.Complete() exposes channel completion to the pool. WaitHandleDbConnectionPool: - Shutdown() is now idempotent (FR-006), disposes both _cleanupTimer and _errorTimer (FR-005), wakes threads parked in WaitHandle.WaitAny by releasing the pool semaphore (FR-007), and drains _stackNew/_stackOld (FR-003). - CleanupCallback and ErrorCallback short-circuit when State == ShuttingDown so an in-flight callback racing with Shutdown does not re-arm work. - The private TryGetConnection wait loop re-checks State after WaitHandle.WaitAny and bails out, so waiters cannot spin against a drained pool. Tests: - ChannelDbConnectionPoolShutdownTest (7 tests) covers state transition, drain, return-after-shutdown, idempotency, async waiter unblock, post-shutdown return, and Startup-after-Shutdown. - WaitHandleDbConnectionPoolShutdownTest (9 tests) covers state transition, cleanup/error timer disposal, drain, idempotency, callback no-op after shutdown, sync TryGetConnection short-circuit, and sync waiter unblock. Verified by running targeted suite: 340 tests passing across net8.0, net9.0, net10.0, and net462. Spec: specs/004-pool-shutdown. *  Address PR #4302 review feedback and drop spec FR-00x labels Code review fixes (WaitHandleDbConnectionPool): - TryGetConnection shutdown short-circuit now releases the PoolSemaphore slot when WaitAny was woken by SEMAPHORE_HANDLE (or WAIT_ABANDONED+SEMAPHORE_HANDLE), preventing a semaphore-slot leak that would starve future waiters. - Shutdown() releases Volatile.Read(ref _waitCount) slots instead of MaxPoolSize, fixing ArgumentOutOfRangeException when MaxPoolSize == 0 (unlimited pool) and ensuring every parked waiter is woken under high contention. Kept SemaphoreFullException guard. Test fix: - Shutdown_UnblocksSyncWaiter replaces Thread.Sleep(200) with a bounded poll on the private _waitCount field (via the existing reflection helper) so the test is deterministic on slow CI agents. Documentation cleanup: - Removed inline 'FR-00x' spec-traceability labels from comments and test section banners across both pool implementations and both shutdown test files. Explanatory text is preserved; the spec coverage will live in the PR description instead of the source. * Address PR #4302 review: avoid reflection in WaitHandleDbConnectionPool shutdown tests Replace reflection-based access of WaitHandleDbConnectionPool internals from the shutdown unit tests with direct field/method access. The pool's _waitCount, _errorTimer, _cleanupTimer, CleanupCallback, and ErrorCallback are now declared internal so the UnitTests assembly (covered by InternalsVisibleTo) can reach them without GetField/Invoke. The GetPrivateField helper is removed. Per @mdaigle review feedback on dotnet/SqlClient#4302. * Address PR #4302 review: reuse Clear() from Shutdown() in both pools Both pool implementations now delegate the connection drain in Shutdown() to the existing Clear() routine instead of duplicating the drain inline. ChannelDbConnectionPool.Shutdown(): keeps the CompareExchange election, State transition and _idleChannel.Complete() call, then calls Clear() (which bumps _clearGeneration and best-effort drains the idle channel). A final unbounded drain loop is retained as a mop-up because Clear() may short-circuit when another Clear() is concurrently in flight; the final loop is safe because the channel is already completed and no new writes can succeed. WaitHandleDbConnectionPool.Shutdown(): keeps the trace, idempotent State check, timer disposal and waiter wake-up, then replaces the inline _stackNew / _stackOld drain loops with a single Clear() call. Clear() additionally dooms every entry in _objectList, decrements the ExitFreeConnection metric per drained connection, and runs ReclaimEmancipatedObjects() — strictly more cleanup than the previous inline drain, with no behaviour change for normal Running-state operations. Per @mdaigle review feedback on dotnet/SqlClient#4302. * Fix PR #4302 CI: pass TimeoutTimer to ChannelDbConnectionPool.TryGetConnection in shutdown tests PR 4295 (merged to main) added a required TimeoutTimer parameter to ChannelDbConnectionPool.TryGetConnection. The shutdown tests added in this PR still call the pre-4295 3-arg overload, which compiles locally against PoolShutdown's older base but fails in the CI pipeline because the pipeline merges this PR's head into the post-4295 main. Pass TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)) at all 5 call sites to match the new signature. * Pass TimeoutTimer to WaitHandleDbConnectionPool.TryGetConnection in shutdown tests The TryGetConnection method on main now requires a TimeoutTimer argument. Update all 5 call sites in WaitHandleDbConnectionPoolShutdownTest.cs to pass TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)) so the test project compiles against the current 4-arg signature. * Address PR #4302 Copilot review: drop unused usings and use Volatile.Read for _waitCount - WaitHandleDbConnectionPoolShutdownTest.cs: remove unused 'using System.Threading.Tasks;' and read pool._waitCount via Volatile.Read in both the poll-loop condition and the assertion so the test no longer relies on plain int reads observing another thread's Interlocked.Increment.\n- ChannelDbConnectionPoolShutdownTest.cs: remove unused 'using System.Data.Common;'. * Address PR #4302 Copilot review: ChannelDbConnectionPool.TryGetConnection short-circuits when State != Running Mirror WaitHandleDbConnectionPool.TryGetConnection: when the pool has been shut down (or is not yet Running), return (true, null) immediately instead of entering the channel-reader path. The previous behavior could reserve a connection slot via _connectionSlots.Add, open a fresh physical connection, hand it to the caller, and then immediately destroy it on return because ReturnInternalConnection checks State == ShuttingDown. The short-circuit avoids the unnecessary open/close round-trip for both sync and async paths. Adds two regression tests. * Address PR #4302 Copilot review: make WaitHandleDbConnectionPool.Startup() a no-op when ShuttingDown Guard Startup() against being called after Shutdown(). Without this, Startup() would create a fresh _cleanupTimer and queue a PoolCreateRequest against a pool that will never accept connections back, leaking the timer and scheduling background work that immediately short-circuits on State == ShuttingDown. Adds Startup_AfterShutdown_DoesNotResurrectPool regression test. * Address PR #4302 Copilot review: drop unused 'using System.Diagnostics;' from ChannelDbConnectionPool Commit 6431209a replaced the only Debug.Assert in ChannelDbConnectionPool.cs with a race-window-guarded RemoveConnection call but left the 'using System.Diagnostics;' directive in place. Drop the now-unused directive so CS8019 cannot surface where it is promoted to an error. * Address PR review: harden shutdown drain, async-path short-circuit, style flips, channel Complete tests - ChannelDbConnectionPool.Shutdown: wrap Pruner.Dispose, Clear, and per-connection RemoveConnection in try/catch so a single failure cannot leave the pool half-shut-down. - WaitHandleDbConnectionPool.TryGetConnection: re-check State after inner TryGetConnection on the async path and short-circuit with (true, null) to avoid enqueueing PendingGetConnection work against a non-Running pool. - WaitHandleDbConnectionPool: flip three positive-form shutdown guards (CleanupCallback, ErrorCallback, Startup) to 'State is not Running' for consistency with the rest of the file and to ease the planned bool-state migration. DeactivateObject's stasis-or-destroy branch and Shutdown's idempotency check intentionally left in positive form. - IdleConnectionChannelTest: add 6 tests covering Complete() idempotency, post-complete TryWrite/TryRead behavior, ChannelClosedException after drain, and pending ReadAsync waiter faulting on Complete (FR-007). * Pass idleTimeout to DbConnectionPoolGroupOptions in new shutdown tests Merge of main brought in the 7-arg DbConnectionPoolGroupOptions constructor from #4295 (Add configurable idle connection timeout). Update the two new shutdown test fixtures to pass idleTimeout: 0 so the build succeeds against the merged base. * Add async-overload shutdown short-circuit test (outer guard path) Covers Copilot review feedback: the async TryGetConnection overload (taskCompletionSource != null) called after Shutdown must take the (true, null) short-circuit at the outer State guard, leave the TCS untouched, and not enqueue a PendingGetConnection. The post-inner race-window backstop is intentionally not tested here because the async caller does not park inside the inner TryGetConnection (waitForMultipleObjectsTimeout is 0); the window cannot be triggered deterministically without instrumenting production code. * Fix stale Startup comment in ChannelDbConnectionPool The previous comment claimed pruning was not implemented, but PR #4295 introduced PoolPruner which is constructed eagerly in the ctor (when MinPoolSize < MaxPoolSize) and arms/disarms its timer via UpdateTimer() calls from OpenNewInternalConnection and RemoveConnection. Update the comment so future maintainers see Startup correctly described as a no-op symmetrical counterpart of Shutdown, while accurately documenting that pruning's background timer is owned by PoolPruner and managed by the connection lifecycle paths, not by Startup. * Use 'is not Running' consistently in WaitHandleDbConnectionPool Two new shutdown-state checks I added (async-path short-circuit and post-WaitAny re-check) used 'State != Running' while the rest of the file consistently uses 'State is not Running'. Align them, plus the adjacent comments, with the prevailing style. Trace strings are left as '!=' since they are human-readable log output. * Compensate CreationSemaphore in shutdown fast-path; fix Complete() comment WaitHandleDbConnectionPool: when the shutdown fast-path after WaitAny returns early, the outer finally that normally releases CreationSemaphore on the CREATION_HANDLE path is bypassed. Mirror the existing PoolSemaphore compensation: on waitResult == CREATION_HANDLE (and the WAIT_ABANDONED variant), Release(1) on CreationSemaphore inside a try/catch SemaphoreFullException so accounting stays balanced. ChannelDbConnectionPool: the comment on _idleChannel.Complete() referenced InvalidOperationException, but IdleConnectionChannel.Complete wraps ChannelWriter.TryComplete which is idempotent and never throws. Rewrite the comment to describe TryComplete's actual semantics so future maintainers aren't misled about why this call is safe. * Revert CreationSemaphore compensation in shutdown fast-path (fixes CI) Commit c5a55441 added a CreationSemaphore.Release(1) in the shutdown fast-path based on the premise that the early 'return false' bypassed the outer finally that normally releases CreationSemaphore on CREATION_HANDLE. That premise was wrong: C# try/finally executes the finally on return, so the outer finally at line ~1196 already handled the release. My extra Release therefore double-released, tripping SemaphoreFullException (CreationSemaphore max=1) whenever the fast-path was taken with waitResult == CREATION_HANDLE. CI's WaitHandleDbConnectionPoolShutdownTest.Shutdown_UnblocksSyncWaiter hit this reliably in Debug config. Remove the CreationSemaphore else-if branch and rewrite the comment to explain why compensation is intentionally absent (outer finally handles it). Keep the PoolSemaphore compensation - that path has no outer finally, so the manual Release is genuinely required. Verified locally: Shutdown_UnblocksSyncWaiter and 24 other Shutdown-related tests pass on net9.0; 20/20 stress iterations of the previously-failing test pass. --- .../ConnectionPool/ChannelDbConnectionPool.cs | 111 +++++++- .../ConnectionPool/IdleConnectionChannel.cs | 11 +- .../WaitHandleDbConnectionPool.cs | 117 +++++++- .../ChannelDbConnectionPoolShutdownTest.cs | 232 +++++++++++++++ .../IdleConnectionChannelTest.cs | 86 ++++++ .../WaitHandleDbConnectionPoolShutdownTest.cs | 265 ++++++++++++++++++ 6 files changed, 806 insertions(+), 16 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolShutdownTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs 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 index 9169e9717e..f5d758ebb7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Data.Common; -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Channels; @@ -92,6 +91,13 @@ internal sealed class ChannelDbConnectionPool : IDbConnectionPool, IDisposable /// Must be updated using operations to ensure thread safety. /// private volatile int _isClearing; + + /// + /// Tracks whether has already initiated the shutdown sequence so that + /// repeated calls are observed as no-ops. Updated atomically via + /// . + /// + private int _shutdownInitiated; #endregion /// @@ -276,16 +282,91 @@ public void ReturnInternalConnection(DbConnectionInternal connection, DbConnecti } else { - var written = _idleChannel.TryWrite(connection); - Debug.Assert(written, "Failed to write returning connection to the idle channel."); + if (!_idleChannel.TryWrite(connection)) + { + // The channel has been completed (pool is shutting down). Race window + // between the State check above and TryWrite: destroy instead of pooling. + RemoveConnection(connection); + } } } /// public void Shutdown() { + // idempotent. Compare-and-exchange ensures only one caller performs shutdown work. + if (Interlocked.CompareExchange(ref _shutdownInitiated, 1, 0) != 0) + { + return; + } + + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}", Id); + + // Transition to ShuttingDown. After this point, ReturnInternalConnection + // routes returning connections to RemoveConnection. State = ShuttingDown; - Pruner?.Dispose(); + + // Each cleanup step is independent and best-effort. A failure in one step must not + // prevent later steps from running, otherwise the pool can be left half-shut-down + // (e.g. timer disposed but channel never completed -> waiters stuck forever). + + // Stop the idle-pruning timer before draining so a tick cannot race with + // the final drain below. PoolPruner.Dispose is idempotent and non-throwing + // in normal use; the catch is defense-in-depth. + try + { + Pruner?.Dispose(); + } + catch (Exception ex) + { + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Pruner.Dispose threw, continuing shutdown: {1}", Id, ex); + } + + // Complete the channel writer so: + // - no further idle connections can be enqueued (TryWrite returns false), and + // - in-flight / future async waiters on ReadAsync fault with ChannelClosedException. + // IdleConnectionChannel.Complete wraps ChannelWriter.TryComplete and is idempotent + // (a second call returns false rather than throwing), so this is safe even if the + // shutdown sequence is ever refactored to invoke this step more than once. + _idleChannel.Complete(); + + // Reuse Clear() for the drain. Clear bumps _clearGeneration so any active + // checked-out connection fails IsLiveConnection on return and is removed, and it + // drains the idle channel up to its captured IdleCount. + try + { + Clear(); + } + catch (Exception ex) + { + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Clear threw, continuing shutdown: {1}", Id, ex); + } + + // Clear() may short-circuit if another caller is already draining. Because the + // channel is now completed, no new items can be enqueued, so it is safe to do a + // final unbounded drain to mop up anything Clear() may have skipped. + while (_idleChannel.TryRead(out DbConnectionInternal? connection)) + { + if (connection is null) + { + // null sentinels are wake-up signals only; nothing to destroy. + continue; + } + + // Isolate per-connection failure: one bad Dispose must not strand the rest. + try + { + RemoveConnection(connection); + } + catch (Exception ex) + { + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, RemoveConnection threw during drain, continuing: {1}", Id, ex); + } + } } /// @@ -296,7 +377,14 @@ public void Shutdown() /// public void Startup() { - // No-op for now, warmup will be implemented later. + // Startup is currently a no-op for this pool: State is set to Running in the + // constructor, and PoolPruner (when present, i.e. MinPoolSize < MaxPoolSize) is + // also constructed eagerly there; its timer arms/disarms via UpdateTimer() calls + // from OpenNewInternalConnection and RemoveConnection as the pool grows/shrinks. + // This method exists as the symmetrical counterpart of Shutdown and as a hook + // for future warmup behavior. + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}", Id); } /// @@ -312,6 +400,19 @@ public bool TryGetConnection( TimeoutTimer timeout, out DbConnectionInternal? connection) { + // Short-circuit when the pool is not Running (i.e., shut down or never started). + // Returning (true, null) matches WaitHandleDbConnectionPool.TryGetConnection and tells + // the caller "completed; no connection available" without entering the channel path, + // which would otherwise reserve a slot, attempt to open a fresh physical connection, + // and then immediately destroy it on return because State == ShuttingDown. + if (State is not Running) + { + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, State != Running.", Id); + connection = null; + return true; + } + // If taskCompletionSource is null, we are in a sync context. if (taskCompletionSource is null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs index 00748ab184..348ad33d9e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IdleConnectionChannel.cs @@ -26,10 +26,19 @@ internal IdleConnectionChannel() { var channel = Channel.CreateUnbounded(); _reader = channel.Reader; - //TODO: the channel should be completed on pool shutdown _writer = channel.Writer; } + /// + /// Marks the channel writer as complete. After completion, + /// returns for any future writes, and any in-flight or future + /// waiters will fault with + /// once the channel is drained. Used by the connection pool to signal shutdown. + /// + /// if this call completed the channel; otherwise + /// (channel was already completed). + internal bool Complete() => _writer.TryComplete(); + /// /// The number of non-null connections currently in the channel. /// diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index d2c3b57323..2aac0a6ce8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -191,16 +191,16 @@ public void Dispose() private readonly WaitCallback _poolCreateRequest; - private int _waitCount; + internal int _waitCount; private readonly PoolWaitHandles _waitHandles; private Exception _resError; private volatile bool _errorOccurred; private int _errorWait; - private Timer _errorTimer; + internal Timer _errorTimer; - private Timer _cleanupTimer; + internal Timer _cleanupTimer; private readonly TransactedConnectionPool _transactedConnectionPool; @@ -346,8 +346,16 @@ public bool IsRunning public TransactedConnectionPool TransactedConnectionPool => _transactedConnectionPool; - private void CleanupCallback(object state) + internal void CleanupCallback(object state) { + // If the pool is not Running, skip work. Shutdown disposes the timer, but + // a callback may already be in-flight when Shutdown runs; this guard ensures it does + // not perform pruning or re-arm pool create requests. + if (State is not Running) + { + return; + } + // Called when the cleanup-timer ticks over. // This is the automatic pruning method. Every period, we will @@ -792,8 +800,15 @@ private void DestroyObject(DbConnectionInternal obj) } } - private void ErrorCallback(object state) + internal void ErrorCallback(object state) { + // Skip work if the pool is not Running. The shutdown path disposes the + // timer; this guard handles the in-flight-callback race. + if (State is not Running) + { + return; + } + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Resetting Error handling.", Id); _errorOccurred = false; _waitHandles.ErrorEvent.Reset(); @@ -953,6 +968,20 @@ public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource {0}, Pool not Running after inner TryGetConnection; short-circuit async path.", Id); + connection = null; + return true; + } + long dueTime; if (LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait) { @@ -1019,6 +1048,34 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj { waitResult = WaitHandle.WaitAny(_waitHandles.GetHandles(allowCreate), unchecked((int)waitForMultipleObjectsTimeout)); + // After waking, observe shutdown state and bail out so waiters + // do not spin against a drained pool. If WaitAny consumed a + // PoolSemaphore slot, release it back so the accounting stays + // balanced; otherwise the slot would leak and other waiters + // (or callers that arrive after Shutdown completes its own + // Release loop) would starve. CreationSemaphore does NOT need + // compensation here because the outer finally below already + // releases it whenever waitResult == CREATION_HANDLE, and + // that finally runs even on this early return. + if (State is not Running) + { + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Pool is shutting down; abandoning wait.", Id); + if (waitResult == SEMAPHORE_HANDLE || waitResult == WAIT_ABANDONED + SEMAPHORE_HANDLE) + { + try + { + _waitHandles.PoolSemaphore.Release(1); + } + catch (SemaphoreFullException) + { + // Pool semaphore was already saturated by Shutdown's bulk release; safe to ignore. + } + } + Interlocked.Decrement(ref _waitCount); + connection = null; + return false; + } + // From the WaitAny docs: "If more than one object became signaled during // the call, this is the array index of the signaled object with the // smallest index value of all the signaled objects." This is important @@ -1565,6 +1622,16 @@ private bool ReclaimEmancipatedObjects() public void Startup() { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", Id, _cleanupWait); + + // Do not resurrect a shut-down pool. Without this guard a Startup() call after + // Shutdown() would create a fresh _cleanupTimer and queue a PoolCreateRequest + // against a pool that will never accept connections back, leaking the timer + // and scheduling background work that immediately short-circuits. + if (State is not Running) + { + return; + } + _cleanupTimer = CreateCleanupTimer(); if (NeedToReplenish) @@ -1576,15 +1643,45 @@ public void Startup() public void Shutdown() { SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); + + // Idempotent: subsequent calls observe ShuttingDown and bail. + if (State == ShuttingDown) + { + return; + } State = ShuttingDown; - // deactivate timer callbacks - Timer t = _cleanupTimer; - _cleanupTimer = null; - if (t != null) + // Dispose all background timers so they no longer schedule new work. + // Note that any timer callback already in flight may still observe State == ShuttingDown + // and short-circuit (see CleanupCallback / ErrorCallback). + Timer cleanup = Interlocked.Exchange(ref _cleanupTimer, null); + cleanup?.Dispose(); + Timer error = Interlocked.Exchange(ref _errorTimer, null); + error?.Dispose(); + + // Wake any threads parked in WaitHandle.WaitAny by releasing as many semaphore + // slots as there are recorded waiters. Using _waitCount (rather than MaxPoolSize) + // avoids ArgumentOutOfRangeException when MaxPoolSize == 0 (unlimited) and ensures + // we wake every parked waiter even when _waitCount exceeds MaxPoolSize. Waiters + // observe State is not Running after wake-up and bail. + int waitersToWake = Volatile.Read(ref _waitCount); + if (waitersToWake > 0) { - t.Dispose(); + try + { + _waitHandles.PoolSemaphore.Release(waitersToWake); + } + catch (SemaphoreFullException) + { + // Semaphore already saturated; nothing to do. + } } + + // Reuse Clear() to doom every connection (including active checked-out ones), drain + // both idle stacks, and reclaim emancipated objects. Active connections destroy + // themselves on return either via the doom flag or via DeactivateObject's + // State == ShuttingDown branch. + Clear(); } // TransactionEnded merely provides the plumbing for DbConnectionInternal to access the transacted pool diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolShutdownTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolShutdownTest.cs new file mode 100644 index 0000000000..7246a932ca --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolShutdownTest.cs @@ -0,0 +1,232 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool +{ + /// + /// Deterministic tests for shutdown behavior. + /// + public class ChannelDbConnectionPoolShutdownTest + { + private static ChannelDbConnectionPool ConstructPool(int maxPoolSize = 5) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: maxPoolSize, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0); + + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + poolGroupOptions); + + return new ChannelDbConnectionPool( + new ChannelDbConnectionPoolTest.SuccessfulSqlConnectionFactory(), + dbConnectionPoolGroup, + DbConnectionPoolIdentity.NoIdentity, + new DbConnectionPoolProviderInfo()); + } + + // State transitions to ShuttingDown on Shutdown. + [Fact] + public void Shutdown_TransitionsState_ToShuttingDown() + { + var pool = ConstructPool(); + Assert.True(pool.IsRunning); + Assert.Equal(DbConnectionPoolState.Running, pool.State); + + pool.Shutdown(); + + Assert.False(pool.IsRunning); + Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); + } + + // Drains buffered idle connections. + [Fact] + public void Shutdown_DrainsIdleConnections() + { + var pool = ConstructPool(); + + // Vend and return three connections so they sit idle in the channel. + var owners = new List(); + var conns = new List(); + for (int i = 0; i < 3; i++) + { + var owner = new SqlConnection(); + owners.Add(owner); + Assert.True(pool.TryGetConnection(owner, taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? c)); + Assert.NotNull(c); + conns.Add(c!); + } + for (int i = 0; i < conns.Count; i++) + { + pool.ReturnInternalConnection(conns[i], owners[i]); + } + Assert.Equal(3, pool.IdleCount); + + pool.Shutdown(); + + Assert.Equal(0, pool.IdleCount); + Assert.Equal(0, pool.Count); + } + + // Returned connection while shutting down is destroyed, not pooled. + [Fact] + public void Shutdown_ReturnedConnection_IsDestroyedNotPooled() + { + var pool = ConstructPool(); + var owner = new SqlConnection(); + Assert.True(pool.TryGetConnection(owner, taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? conn)); + Assert.NotNull(conn); + Assert.Equal(1, pool.Count); + Assert.Equal(0, pool.IdleCount); + + pool.Shutdown(); + + // Connection is still checked out; return it now. + pool.ReturnInternalConnection(conn!, owner); + + Assert.Equal(0, pool.IdleCount); + Assert.Equal(0, pool.Count); + } + + // Shutdown is idempotent. + [Fact] + public void Shutdown_IsIdempotent() + { + var pool = ConstructPool(); + pool.Shutdown(); + // Second call must not throw and must leave state intact. + pool.Shutdown(); + pool.Shutdown(); + Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); + } + + // Async waiter is unblocked when the pool shuts down. + [Fact] + public async Task Shutdown_UnblocksAsyncWaiter() + { + var pool = ConstructPool(maxPoolSize: 1); + + // Saturate the pool. + Assert.True(pool.TryGetConnection(new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? blocking)); + Assert.NotNull(blocking); + + // Park an async waiter. + var tcs = new TaskCompletionSource(); + bool completed = pool.TryGetConnection(new SqlConnection(), tcs, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? waiter); + Assert.False(completed); + Assert.Null(waiter); + Assert.False(tcs.Task.IsCompleted); + + // Shut down the pool. + pool.Shutdown(); + + // Waiter must complete (faulted or with a null connection) within a bounded window. + var winner = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromSeconds(5))); + Assert.Same(tcs.Task, winner); + // Either an exception was set (channel closed) or the result is null - both are acceptable + // shutdown signals. What matters is the waiter does NOT block forever. + if (tcs.Task.IsFaulted) + { + // Expected path: ChannelClosedException or a wrapped exception. + Assert.NotNull(tcs.Task.Exception); + } + else + { + // Permitted: completed with null result. + Assert.Null(tcs.Task.Result); + } + } + + // Sync get fails fast after shutdown. + // The factory-level retry guard checks IsRunning, but the pool itself must not vend + // new connections after Shutdown. We verify by exhausting the pool first then + // checking that returned connections are destroyed (Count goes back to 0). + [Fact] + public void Shutdown_AfterShutdown_NewReturnsAreDestroyed() + { + var pool = ConstructPool(); + var owner = new SqlConnection(); + Assert.True(pool.TryGetConnection(owner, taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? c)); + Assert.NotNull(c); + + pool.Shutdown(); + pool.ReturnInternalConnection(c!, owner); + + Assert.False(pool.IsRunning); + Assert.Equal(0, pool.Count); + Assert.Equal(0, pool.IdleCount); + } + + // Sanity: Startup is currently a no-op for this pool but must not throw or change + // shutdown state if invoked after Shutdown. + [Fact] + public void Startup_AfterShutdown_DoesNotResurrectPool() + { + var pool = ConstructPool(); + pool.Shutdown(); + + pool.Startup(); + + Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); + Assert.False(pool.IsRunning); + } + + // After Shutdown, a synchronous TryGetConnection must short-circuit and return + // (true, null) rather than entering the channel path and opening a fresh physical + // connection that would be immediately destroyed on return. + [Fact] + public void TryGetConnection_AfterShutdown_Sync_ShortCircuits() + { + var pool = ConstructPool(); + pool.Shutdown(); + + int countBefore = pool.Count; + bool completed = pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(5)), + out DbConnectionInternal? conn); + + Assert.True(completed, "Sync TryGetConnection on a shut-down pool should return true to signal completion."); + Assert.Null(conn); + Assert.Equal(countBefore, pool.Count); + } + + // Same contract for the async (TaskCompletionSource) path: a TryGetConnection call + // issued after Shutdown must short-circuit without opening a new connection. + [Fact] + public void TryGetConnection_AfterShutdown_Async_ShortCircuits() + { + var pool = ConstructPool(); + pool.Shutdown(); + + int countBefore = pool.Count; + var tcs = new TaskCompletionSource(); + bool completed = pool.TryGetConnection( + new SqlConnection(), + tcs, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(5)), + out DbConnectionInternal? conn); + + // Match WaitHandleDbConnectionPool: short-circuit returns (true, null) regardless of TCS. + Assert.True(completed); + Assert.Null(conn); + Assert.Equal(countBefore, pool.Count); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs index 56aa9c234a..faa4073a7d 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs @@ -5,6 +5,7 @@ using System; using System.Data.Common; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.ProviderBase; @@ -160,6 +161,91 @@ await Assert.ThrowsAnyAsync( #endregion + #region Complete + + [Fact] + public void Complete_FirstCall_ReturnsTrue() + { + var channel = new IdleConnectionChannel(); + + Assert.True(channel.Complete()); + } + + [Fact] + public void Complete_SecondCall_ReturnsFalse() + { + var channel = new IdleConnectionChannel(); + Assert.True(channel.Complete()); + + // Idempotent: a second Complete is a safe no-op and must not throw. + Assert.False(channel.Complete()); + } + + [Fact] + public void TryWrite_AfterComplete_ReturnsFalseAndDoesNotIncrementCount() + { + var channel = new IdleConnectionChannel(); + channel.Complete(); + + Assert.False(channel.TryWrite(new StubDbConnectionInternal())); + Assert.False(channel.TryWrite(null)); + Assert.Equal(0, channel.Count); + } + + [Fact] + public void TryRead_AfterComplete_DrainsBufferedItems() + { + var channel = new IdleConnectionChannel(); + channel.TryWrite(new StubDbConnectionInternal()); + channel.TryWrite(new StubDbConnectionInternal()); + Assert.Equal(2, channel.Count); + + channel.Complete(); + + // Completion only stops new writes; already-buffered items remain readable. + Assert.True(channel.TryRead(out var first)); + Assert.NotNull(first); + Assert.True(channel.TryRead(out var second)); + Assert.NotNull(second); + Assert.Equal(0, channel.Count); + + // Once drained, further reads return false. + Assert.False(channel.TryRead(out _)); + } + + [Fact] + public async Task ReadAsync_AfterCompleteAndDrain_ThrowsChannelClosedException() + { + var channel = new IdleConnectionChannel(); + channel.TryWrite(new StubDbConnectionInternal()); + channel.Complete(); + + // Buffered item is still readable. + var buffered = await channel.ReadAsync(CancellationToken.None); + Assert.NotNull(buffered); + + // After the channel is drained, ReadAsync faults with ChannelClosedException. + await Assert.ThrowsAsync( + () => channel.ReadAsync(CancellationToken.None).AsTask()); + } + + [Fact] + public async Task ReadAsync_PendingWaiter_FaultsOnComplete() + { + // FR-007: a caller already parked in ReadAsync when shutdown completes the + // channel must be unblocked (not wait until its connection timeout). + var channel = new IdleConnectionChannel(); + + var readTask = channel.ReadAsync(CancellationToken.None); + Assert.False(readTask.IsCompleted); + + channel.Complete(); + + await Assert.ThrowsAsync(() => readTask.AsTask()); + } + + #endregion + #region Mixed operations [Fact] diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs new file mode 100644 index 0000000000..52bf9ccb29 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs @@ -0,0 +1,265 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool +{ + /// + /// Deterministic tests for shutdown behavior. + /// + public class WaitHandleDbConnectionPoolShutdownTest + { + private static WaitHandleDbConnectionPool CreatePool(int maxPoolSize = 5) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: maxPoolSize, + creationTimeout: 15000, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0); + + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + poolGroupOptions); + + var pool = new WaitHandleDbConnectionPool( + new WaitHandleDbConnectionPoolTransactionTest.MockSqlConnectionFactory(), + dbConnectionPoolGroup, + DbConnectionPoolIdentity.NoIdentity, + new DbConnectionPoolProviderInfo()); + pool.Startup(); + return pool; + } + + // State transitions to ShuttingDown on Shutdown. + [Fact] + public void Shutdown_TransitionsState_ToShuttingDown() + { + var pool = CreatePool(); + Assert.True(pool.IsRunning); + + pool.Shutdown(); + + Assert.False(pool.IsRunning); + Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); + } + + // Cleanup timer is disposed. + [Fact] + public void Shutdown_DisposesCleanupTimer() + { + var pool = CreatePool(); + Assert.NotNull(pool._cleanupTimer); + + pool.Shutdown(); + + Assert.Null(pool._cleanupTimer); + } + + // Error timer is disposed when present. + [Fact] + public void Shutdown_DisposesErrorTimer_WhenPresent() + { + var pool = CreatePool(); + // Inject a real Timer into _errorTimer to mimic an error-state pool. + pool._errorTimer = new Timer(_ => { }, null, Timeout.Infinite, Timeout.Infinite); + + pool.Shutdown(); + + Assert.Null(pool._errorTimer); + } + + // Drains idle stacks. + [Fact] + public void Shutdown_DrainsIdleStacks() + { + var pool = CreatePool(); + + // Vend a few connections then return them so they sit in _stackNew. + var owner1 = new SqlConnection(); + var owner2 = new SqlConnection(); + pool.TryGetConnection(owner1, taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? c1); + pool.TryGetConnection(owner2, taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? c2); + Assert.NotNull(c1); + Assert.NotNull(c2); + pool.ReturnInternalConnection(c1!, owner1); + pool.ReturnInternalConnection(c2!, owner2); + + Assert.Equal(2, pool.IdleCount); + Assert.Equal(2, pool.Count); + + pool.Shutdown(); + + Assert.Equal(0, pool.IdleCount); + Assert.Equal(0, pool.Count); + } + + // Shutdown is idempotent. + [Fact] + public void Shutdown_IsIdempotent() + { + var pool = CreatePool(); + pool.Shutdown(); + pool.Shutdown(); + pool.Shutdown(); + Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); + } + + // Cleanup callback after shutdown is a no-op. + [Fact] + public void CleanupCallback_AfterShutdown_IsNoOp() + { + var pool = CreatePool(); + pool.Shutdown(); + + // Invoke the callback directly. Must not throw and must not re-arm any pool + // create requests. + var ex = Record.Exception(() => pool.CleanupCallback(state: null)); + Assert.Null(ex); + Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); + } + + // Error callback after shutdown is a no-op. + [Fact] + public void ErrorCallback_AfterShutdown_IsNoOp() + { + var pool = CreatePool(); + pool.Shutdown(); + + var ex = Record.Exception(() => pool.ErrorCallback(state: null)); + Assert.Null(ex); + } + + // Sync caller arriving after shutdown gets a null connection (factory will + // see this and return up the retry chain). The pool's TryGetConnection short-circuits + // on State != Running. + [Fact] + public void TryGetConnection_AfterShutdown_ReturnsNullWithoutBlocking() + { + var pool = CreatePool(); + pool.Shutdown(); + + bool completed = pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? conn); + + // TryGetConnection returns true with a null connection when State != Running. + Assert.True(completed); + Assert.Null(conn); + } + + // Async overload (taskCompletionSource != null) called after Shutdown must take the + // (true, null) short-circuit instead of enqueueing a PendingGetConnection and spinning + // up a WaitForPendingOpen background loop. The TCS must be left untouched so the caller + // surfaces a deterministic shutdown signal rather than an eventual PooledOpenTimeout. + [Fact] + public void TryGetConnection_Async_AfterShutdown_ShortCircuits_NoPendingOpenScheduled() + { + var pool = CreatePool(); + pool.Shutdown(); + + var tcs = new TaskCompletionSource(); + bool completed = pool.TryGetConnection( + new SqlConnection(), + tcs, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? conn); + + Assert.True(completed); + Assert.Null(conn); + Assert.False(tcs.Task.IsCompleted); + Assert.Equal(0, Volatile.Read(ref pool._waitCount)); + } + + // Shutdown wakes up a thread parked in WaitHandle.WaitAny. + [Fact] + public void Shutdown_UnblocksSyncWaiter() + { + var pool = CreatePool(maxPoolSize: 1); + + // Saturate the pool. + var owner = new SqlConnection(); + Assert.True(pool.TryGetConnection(owner, taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? blocking)); + Assert.NotNull(blocking); + + // Park a sync waiter on a worker thread with a long creation timeout. + DbConnectionInternal? waiterResult = null; + bool waiterCompleted = false; + Exception? waiterEx = null; + + var t = new Thread(() => + { + try + { + waiterCompleted = pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out waiterResult); + } + catch (Exception ex) + { + waiterEx = ex; + } + }) + { IsBackground = true }; + t.Start(); + + // Wait deterministically until the worker has incremented _waitCount, which + // happens immediately before it enters WaitHandle.WaitAny. Polling avoids the + // CI-flakiness of a fixed Thread.Sleep on slow agents. Volatile.Read ensures + // we see the worker's Interlocked.Increment without depending on CPU memory + // ordering of plain int reads. + var deadline = DateTime.UtcNow.AddSeconds(5); + while (DateTime.UtcNow < deadline && Volatile.Read(ref pool._waitCount) < 1) + { + Thread.Yield(); + } + Assert.True(Volatile.Read(ref pool._waitCount) >= 1, "Waiter did not park within 5s."); + Assert.True(t.IsAlive, "Waiter should be parked, but thread already exited."); + + pool.Shutdown(); + + Assert.True(t.Join(TimeSpan.FromSeconds(5)), "Waiter did not unblock within 5s of Shutdown."); + // Acceptable outcomes: either returned false/null (timed out / abandoned) or + // returned true/null (state-check short-circuit). Either way, it must NOT block + // forever, and it must NOT vend a real connection from a shut-down pool. + Assert.Null(waiterResult); + Assert.Null(waiterEx); + // Suppress unused warning - presence of waiterCompleted just documents the contract. + _ = waiterCompleted; + } + + // Startup() must be a no-op when the pool has already been shut down. Without the + // guard it would create a fresh _cleanupTimer and queue a PoolCreateRequest against + // a pool that will never accept connections back. + [Fact] + public void Startup_AfterShutdown_DoesNotResurrectPool() + { + var pool = CreatePool(); + pool.Shutdown(); + Assert.Null(pool._cleanupTimer); + Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); + + pool.Startup(); + + Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); + Assert.False(pool.IsRunning); + // No new cleanup timer must have been scheduled against a shut-down pool. + Assert.Null(pool._cleanupTimer); + } + } +} From 1ed86465959c6803dea5354c538ca22b2448f5fd Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 2 Jul 2026 10:20:48 -0700 Subject: [PATCH 174/298] Refactor ForceNewConnection (#4415) * Remove unused method. * Remove instance-level ForceNewConnection property. Replace with explicit method parameters. * Fix initialization. Add doc comments. * Remove unnecessary internal API surface. * Expose param. * Address broken test and copilot comments. * Name arguments. Address copilot comments. --- .../Data/ProviderBase/DbConnectionClosed.cs | 2 +- .../Data/ProviderBase/DbConnectionInternal.cs | 14 ++-- .../Connection/SqlConnectionInternal.cs | 2 +- .../WaitHandleDbConnectionPool.cs | 1 - .../Microsoft/Data/SqlClient/SqlConnection.cs | 66 ++++++++++++------- .../Data/SqlClient/SqlConnectionFactory.cs | 4 +- .../SqlConnectionConcurrentOpenTests.cs | 18 +---- .../SqlConnectionStateTransitionTests.cs | 6 +- 8 files changed, 57 insertions(+), 56 deletions(-) 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 f5ae12f5a1..37782dddf3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -64,7 +64,7 @@ internal override bool TryOpenConnection( SqlConnectionFactory connectionFactory, TaskCompletionSource retry, TimeoutTimer timeout) => - TryOpenConnectionInternal(outerConnection, connectionFactory, retry, timeout); + TryOpenConnectionInternal(outerConnection, connectionFactory, retry, forceNewConnection: false, timeout); /// internal override void ResetConnection() => throw ADP.ClosedConnectionError(); 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 539f79ff47..907e491949 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -735,11 +735,6 @@ internal void PostPop(DbConnection newOwner) } } - internal virtual void PrepareForReplaceConnection() - { - // By default, there is no preparation required - } - /// /// Stamps with the current UTC time. The pool calls this from its /// return-to-pool path only when it intends the idle-timeout machinery to act on the value; @@ -930,6 +925,7 @@ protected bool TryOpenConnectionInternal( DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource retry, + bool forceNewConnection, TimeoutTimer timeout) { // ?->Connecting: prevent set_ConnectionString during Open @@ -939,7 +935,13 @@ protected bool TryOpenConnectionInternal( try { connectionFactory.PermissionDemand(outerConnection); - if (!connectionFactory.TryGetConnection(outerConnection, retry, this, timeout, out openConnection)) + if (!connectionFactory.TryGetConnection( + outerConnection, + retry, + this, + timeout, + forceNewConnection, + out openConnection)) { return false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 8ff3d92428..a70ab35360 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -1969,7 +1969,7 @@ internal override bool TryReplaceConnection( TaskCompletionSource retry, TimeoutTimer timeout) { - return TryOpenConnectionInternal(outerConnection, connectionFactory, retry, timeout); + return TryOpenConnectionInternal(outerConnection, connectionFactory, retry, forceNewConnection: true, timeout); } internal void ValidateConnectionForExecute(SqlCommand command) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 2aac0a6ce8..e83fdcf7fa 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -1260,7 +1260,6 @@ public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConne { SqlClientDiagnostics.Metrics.SoftConnectRequest(); PrepareConnection(owningObject, newConnection, oldConnection.EnlistedTransaction); - oldConnection.PrepareForReplaceConnection(); oldConnection.DeactivateConnection(); oldConnection.Dispose(); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs index bf99482d41..d63571bd55 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -1223,8 +1223,6 @@ internal int ReconnectCount get => _reconnectCount; } - internal bool ForceNewConnection { get; set; } - #if NET /// protected override void OnStateChange(StateChangeEventArgs stateChange) @@ -1597,8 +1595,8 @@ public override void EnlistTransaction(System.Transactions.Transaction transacti public override void Open() => Open(SqlConnectionOverrides.None); - private bool TryOpenWithRetry(TaskCompletionSource retry, SqlConnectionOverrides overrides) - => RetryLogicProvider.Execute(this, () => TryOpen(retry, overrides)); + private bool TryOpenWithRetry(TaskCompletionSource retry, bool forceNewConnection, SqlConnectionOverrides overrides) + => RetryLogicProvider.Execute(this, () => TryOpen(retry, forceNewConnection, overrides)); /// public void Open(SqlConnectionOverrides overrides) @@ -1618,7 +1616,7 @@ public void Open(SqlConnectionOverrides overrides) { statistics = SqlStatistics.StartTimer(Statistics); - if (!(IsProviderRetriable ? TryOpenWithRetry(null, overrides) : TryOpen(null, overrides))) + if (!(IsProviderRetriable ? TryOpenWithRetry(null, false, overrides) : TryOpen(null, false, overrides))) { throw ADP.InternalError(ADP.InternalErrorCode.SynchronousConnectReturnedPending); } @@ -1667,6 +1665,12 @@ private async Task ReconnectAsync(int timeout) { commandTimeoutExpiration = ADP.TimerCurrent() + ADP.TimerFromSeconds(timeout); } + + // Calling Close will request a cancellation on this token source, cancelling the reconnection. + // Ideally, however, the reconnect should be directly awaited, making it impossible to have a + // concurrent call to Close. + // TODO: Note that we do not respect the command timeout in this context. Instead, the reconnect gets + // a full connect timeout. Investigate if this should be changed. _reconnectionCancellationSource = cts; CancellationToken ctoken = cts.Token; int retryCount = _connectRetryCount; // take a snapshot: could be changed by modifying the connection string @@ -1684,8 +1688,15 @@ private async Task ReconnectAsync(int timeout) #endif try { - ForceNewConnection = true; - await OpenAsync(ctoken).ConfigureAwait(false); + if (IsProviderRetriable) + { + await InternalOpenWithRetryAsync(SqlConnectionOverrides.None, forceNewConnection: true, ctoken).ConfigureAwait(false); + } + else + { + await InternalOpenAsync(SqlConnectionOverrides.None, forceNewConnection: true, ctoken).ConfigureAwait(false); + } + // On success, increment the reconnect count - we don't really care if it rolls over since it is approx. _reconnectCount = unchecked(_reconnectCount + 1); #if DEBUG @@ -1697,7 +1708,6 @@ private async Task ReconnectAsync(int timeout) #if NETFRAMEWORK _impersonateIdentity = null; #endif - ForceNewConnection = false; } SqlClientEventSource.Log.TryTraceEvent("SqlConnection.ReconnectAsync | Info | Reconnection succeeded. Client Connection Id {0} -> {1}", _originalConnectionId, ClientConnectionId); @@ -1907,13 +1917,13 @@ public override Task OpenAsync(CancellationToken cancellationToken) /// public Task OpenAsync(SqlConnectionOverrides overrides, CancellationToken cancellationToken) => IsProviderRetriable ? - InternalOpenWithRetryAsync(overrides, cancellationToken) : - InternalOpenAsync(overrides, cancellationToken); + InternalOpenWithRetryAsync(overrides, forceNewConnection: false, cancellationToken) : + InternalOpenAsync(overrides, forceNewConnection: false, cancellationToken); - private Task InternalOpenWithRetryAsync(SqlConnectionOverrides overrides, CancellationToken cancellationToken) - => RetryLogicProvider.ExecuteAsync(this, () => InternalOpenAsync(overrides, cancellationToken), cancellationToken); + private Task InternalOpenWithRetryAsync(SqlConnectionOverrides overrides, bool forceNewConnection, CancellationToken cancellationToken) + => RetryLogicProvider.ExecuteAsync(this, () => InternalOpenAsync(overrides, forceNewConnection, cancellationToken), cancellationToken); - private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationToken cancellationToken) + private Task InternalOpenAsync(SqlConnectionOverrides overrides, bool forceNewConnection, CancellationToken cancellationToken) { long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent("SqlConnection.InternalOpenAsync | API | Object Id {0}", ObjectID); SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlConnection.InternalOpenAsync | API | Correlation | Object Id {0}, Activity Id {1}", ObjectID, ActivityCorrelator.Current); @@ -1955,7 +1965,7 @@ private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationTok try { - completed = TryOpen(completion, overrides); + completed = TryOpen(completion, forceNewConnection, overrides); } catch (Exception e) { @@ -1975,7 +1985,7 @@ private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationTok { registration = cancellationToken.Register(s_openAsyncCancel, completion); } - OpenAsyncRetry retry = new OpenAsyncRetry(this, completion, result, overrides, registration); + OpenAsyncRetry retry = new OpenAsyncRetry(this, completion, result, overrides, registration, forceNewConnection); _currentCompletion = new Tuple, Task>(completion, result.Task); completion.Task.ContinueWith(retry.Retry, TaskScheduler.Default); return result.Task; @@ -2088,14 +2098,16 @@ private class OpenAsyncRetry private TaskCompletionSource _result; private SqlConnectionOverrides _overrides; private CancellationTokenRegistration _registration; + private bool _forceNewConnection; - public OpenAsyncRetry(SqlConnection parent, TaskCompletionSource retry, TaskCompletionSource result, SqlConnectionOverrides overrides, CancellationTokenRegistration registration) + public OpenAsyncRetry(SqlConnection parent, TaskCompletionSource retry, TaskCompletionSource result, SqlConnectionOverrides overrides, CancellationTokenRegistration registration, bool forceNewConnection) { _parent = parent; _retry = retry; _result = result; _overrides = overrides; _registration = registration; + _forceNewConnection = forceNewConnection; SqlClientEventSource.Log.TryTraceEvent("SqlConnection.OpenAsyncRetry | Info | Object Id {0}", _parent?.ObjectID); } @@ -2130,7 +2142,7 @@ internal void Retry(Task retryTask) // protect continuation from races with close and cancel lock (_parent.InnerConnection) { - result = _parent.TryOpen(_retry, _overrides); + result = _parent.TryOpen(_retry, _forceNewConnection, _overrides); } if (result) { @@ -2176,7 +2188,7 @@ private void PrepareStatisticsForNewConnection() } } - private bool TryOpen(TaskCompletionSource retry, SqlConnectionOverrides overrides = SqlConnectionOverrides.None) + private bool TryOpen(TaskCompletionSource retry, bool forceNewConnection, SqlConnectionOverrides overrides = SqlConnectionOverrides.None) { bool result = false; @@ -2206,13 +2218,13 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec { if (_impersonateIdentity.User == identity.User) { - result = TryOpenInner(retry); + result = TryOpenInner(retry, forceNewConnection); } else { using (WindowsImpersonationContext context = _impersonateIdentity.Impersonate()) { - result = TryOpenInner(retry); + result = TryOpenInner(retry, forceNewConnection); } } } @@ -2227,10 +2239,10 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec { _lastIdentity = null; } - result = TryOpenInner(retry); + result = TryOpenInner(retry, forceNewConnection); } #else - result = TryOpenInner(retry); + result = TryOpenInner(retry, forceNewConnection); #endif return result; @@ -2240,12 +2252,18 @@ private bool TryOpen(TaskCompletionSource retry, SqlConnec /// Completes the inner open/replace operation and initializes parser state for the active inner connection. /// /// Retry continuation used by async open paths. + /// Provide true to forcibly overwrite the existing connection. Provide false if connecting for the first time. /// when open initialization completed synchronously; otherwise . /// /// The inner connection is snapshotted after the open call so downstream parser access uses a single observed /// instance and does not rely on a second racy read of . + /// + /// forceNewConnection may only be true when the connection is already open (or was open) and needs to be replaced. If the connection has never + /// been opened, passing true will result in an exception. It may only be false when the connection has never been opened or is + /// currently disconnected. If the connection is currently open, passing false will result in an exception. See SqlConnection state + /// transitions and subclasses for more details. /// - internal bool TryOpenInner(TaskCompletionSource retry) + internal bool TryOpenInner(TaskCompletionSource retry, bool forceNewConnection) { // Create a single TimeoutTimer that represents the overall connection-open budget for this // attempt. The timer is threaded through every layer (inner connection state transitions, @@ -2257,7 +2275,7 @@ internal bool TryOpenInner(TaskCompletionSource retry) TimeoutTimer timeout = TimeoutTimer.StartNew( TimeSpan.FromSeconds(ConnectionOptions?.ConnectTimeout ?? ADP.DefaultConnectionTimeout)); - if (ForceNewConnection) + if (forceNewConnection) { if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry, timeout)) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 1f90eac202..889a9c014c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -318,6 +318,7 @@ internal bool TryGetConnection( TaskCompletionSource retry, DbConnectionInternal oldConnection, TimeoutTimer timeout, + bool forceNewConnection, out DbConnectionInternal connection) { Debug.Assert(owningConnection is not null, "null owningConnection?"); @@ -419,7 +420,7 @@ internal bool TryGetConnection( } else { - if (((SqlConnection)owningConnection).ForceNewConnection) + if (forceNewConnection) { Debug.Assert(oldConnection is not DbConnectionClosed, "Force new connection, but there is no old connection"); @@ -777,7 +778,6 @@ private Task CreateReplaceConnectionContinuation( if (oldConnection?.State == ConnectionState.Open) { - oldConnection.PrepareForReplaceConnection(); oldConnection.Dispose(); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs index 8d151e61cd..17ac619a3e 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionConcurrentOpenTests.cs @@ -4,7 +4,6 @@ using System; using System.Data; -using System.Reflection; using System.Threading.Tasks; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.Connection; @@ -26,9 +25,6 @@ namespace Microsoft.Data.SqlClient.UnitTests.Microsoft.Data.SqlClient /// public class SqlConnectionConcurrentOpenTests { - private static readonly MethodInfo s_tryOpenInner = typeof(SqlConnection) - .GetMethod("TryOpenInner", BindingFlags.Instance | BindingFlags.NonPublic)!; - private static DbConnectionInternal GetConnectingSingleton() { return DbConnectionClosedConnecting.SingletonInstance; @@ -43,18 +39,6 @@ private static void ForceInnerConnection(SqlConnection connection, DbConnectionI Assert.True(swapped, $"CAS failed: expected current state {current.GetType().Name} but it changed concurrently."); } - private static bool InvokeTryOpenInner(SqlConnection connection, TaskCompletionSource retry) - { - try - { - return (bool)s_tryOpenInner.Invoke(connection, [retry])!; - } - catch (TargetInvocationException tie) when (tie.InnerException != null) - { - throw tie.InnerException; - } - } - [Fact] public void InnerConnection_DbConnectionClosedConnecting_IsNotAssignableToSqlConnectionInternal() { @@ -102,7 +86,7 @@ public void TryOpenInner_WhenInnerConnectionRacesToNonSqlConnectionInternalState Exception ex = Assert.ThrowsAny(() => { - InvokeTryOpenInner(connection, completedRetry); + connection.TryOpenInner(completedRetry, forceNewConnection: false); }); Assert.True( diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStateTransitionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStateTransitionTests.cs index b1decd2be0..2955a6825e 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStateTransitionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStateTransitionTests.cs @@ -24,11 +24,10 @@ public class SqlConnectionStateTransitionTests public void TryOpenInner_WhenInnerConnectionChangesAfterTryOpen_ThrowsInvalidOperation() { using SqlConnection connection = new(); - connection.ForceNewConnection = false; bool initialized = connection.SetInnerConnectionFrom(new TestRaceDbConnectionInternal(), DbConnectionClosedNeverOpened.SingletonInstance); Assert.True(initialized); - InvalidOperationException exception = Assert.Throws(() => connection.TryOpenInner(null)); + InvalidOperationException exception = Assert.Throws(() => connection.TryOpenInner(null, forceNewConnection: false)); Assert.NotNull(exception); Assert.Same(DbConnectionClosedPreviouslyOpened.SingletonInstance, connection.InnerConnection); } @@ -40,11 +39,10 @@ public void TryOpenInner_WhenInnerConnectionChangesAfterTryOpen_ThrowsInvalidOpe public void TryOpenInner_WhenInnerConnectionChangesAfterTryReplace_ThrowsInvalidOperation() { using SqlConnection connection = new(); - connection.ForceNewConnection = true; bool initialized = connection.SetInnerConnectionFrom(new TestRaceDbConnectionInternal(), DbConnectionClosedNeverOpened.SingletonInstance); Assert.True(initialized); - InvalidOperationException exception = Assert.Throws(() => connection.TryOpenInner(null)); + InvalidOperationException exception = Assert.Throws(() => connection.TryOpenInner(null, forceNewConnection: true)); Assert.NotNull(exception); Assert.Same(DbConnectionClosedPreviouslyOpened.SingletonInstance, connection.InnerConnection); } From fdebcd22257207e50046ad78620580b027e0c783 Mon Sep 17 00:00:00 2001 From: priyankatiwari08 Date: Tue, 7 Jul 2026 15:50:46 +0530 Subject: [PATCH 175/298] [v7.1.0-preview2] Release Notes and vBump (#4422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [v7.1.0-preview2] Release Notes and vBump * vBump in Versions.props, OneBranch pipelines, and CI/PR pipelines to v7.1.0-preview2 * Release notes for v7.1.0-preview2 * Docs: clarify FileVersion vs AssemblyVersion in per-package alignment note Address Paul's PR feedback: the phrase 'unchanged from 7.0.2' attached to both FileVersion and AssemblyVersion, making it sound like FileVersion was also unchanged. Split into two sentences so only AssemblyVersion is described as unchanged from 7.0.2. Applied across all 4 per-package 7.1.0-preview2 release-notes files (AzureKeyVaultProvider, Extensions.Azure, Extensions.Abstractions, Internal.Logging). * Docs: correct WAM broker attribution in 7.1.0-preview2 release notes Address review feedback on PR #4422: - Rewrite Extensions.Azure preview2 notes to document WAM broker support properly (removes false "no functional changes" claim and wrong "added in 7.0.2" pointer). WAM was added in 7.1.0-preview2 per PR #4288's milestone, not 7.0.2. - Remove WAM Broker Support section from core preview2 notes; WAM lives only in the Extensions.Azure package. - Drop Microsoft.Identity.Client / Microsoft.Identity.Client.Broker dependency-bump bullet from core preview2 notes and CHANGELOG; MSAL packages are referenced only by Extensions.Azure, not by core Microsoft.Data.SqlClient (which uses the Wilson IdentityModel stack). Keep the .NET 10 SDK bump (PR #4287) in the core notes. - Split CHANGELOG "Re-shipped" bullet so Extensions.Azure gets its own callout for the WAM addition; AKV, Extensions.Abstractions, and Internal.Logging remain grouped as alignment-only. * Docs: address Malcolm's review comments on pool timeout release notes - Drop 'pool v2' framing from Configurable Idle Connection Timeout section; frame the new keyword/property as applying to the existing connection pool as well (channel pool changes will be announced in preview 3). - Add Switch.Microsoft.Data.SqlClient.UseOverallConnectTimeoutForPoolWait AppContext switch details (default false, opt-in to true) to the Connection Timeout Now Propagates Through the Pool section, and reframe Who Benefits/Impact to lead with unchanged default behavior. * Docs: drop channel-based pool pruning/shutdown section from preview2 notes Per Malcolm's follow-up review, channel pool (pool v2) changes will be announced in preview 3. Removes: - The 'Automatic Pool Pruning and Shutdown for the Channel-Based Pool' section (What Changed / Who Benefits / Impact) from release-notes/7.1/7.1.0-preview2.md. - The matching 'Implemented pool shutdown for ChannelDbConnectionPool and added automatic pool size reduction (pruning) for the channel-based pool' bullet from CHANGELOG.md's Changed section. PRs #4302 and #4304 are no longer referenced in the preview2 notes. --- CHANGELOG.md | 111 +++++++++ .../libraries/ci-build-variables.yml | 16 +- .../onebranch/variables/package-variables.yml | 26 +-- release-notes/7.1/7.1.0-preview2.md | 210 ++++++++++++++++++ release-notes/7.1/README.md | 1 + .../Abstractions/7.1/7.1.0-preview2.md | 19 ++ .../Extensions/Abstractions/7.1/README.md | 8 + .../Extensions/Azure/7.1/7.1.0-preview2.md | 73 ++++++ release-notes/Extensions/Azure/7.1/README.md | 8 + .../Internal/Logging/7.1/7.1.0-preview2.md | 22 ++ release-notes/Internal/Logging/7.1/README.md | 11 + release-notes/README.md | 4 + .../7.1/7.1.0-preview2.md | 23 ++ .../AzureKeyVaultProvider/7.1/README.md | 8 + .../src/Versions.props | 2 +- .../Abstractions/src/Versions.props | 2 +- .../Azure/src/Versions.props | 2 +- .../Logging/src/Versions.props | 2 +- src/Microsoft.Data.SqlClient/Versions.props | 2 +- 19 files changed, 524 insertions(+), 26 deletions(-) create mode 100644 release-notes/7.1/7.1.0-preview2.md create mode 100644 release-notes/Extensions/Abstractions/7.1/7.1.0-preview2.md create mode 100644 release-notes/Extensions/Abstractions/7.1/README.md create mode 100644 release-notes/Extensions/Azure/7.1/7.1.0-preview2.md create mode 100644 release-notes/Extensions/Azure/7.1/README.md create mode 100644 release-notes/Internal/Logging/7.1/7.1.0-preview2.md create mode 100644 release-notes/Internal/Logging/7.1/README.md create mode 100644 release-notes/add-ons/AzureKeyVaultProvider/7.1/7.1.0-preview2.md create mode 100644 release-notes/add-ons/AzureKeyVaultProvider/7.1/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7725184521..4f15b44925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,117 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) > **Note:** Releases are sorted in reverse chronological order (newest first). +## [Preview Release 7.1.0-preview2] - 2026-07-09 + +This update brings the following changes since the [7.1.0-preview1](release-notes/7.1/7.1.0-preview1.md) release. +See the [full release notes](release-notes/7.1/7.1.0-preview2.md) for detailed descriptions. + +> **Important — package version alignment:** Starting with the [7.0.2](release-notes/7.0/7.0.2.md) release, the `Microsoft.Data.SqlClient` driver and its companion packages share a single aligned version. Preview 2 of the `7.1` line continues this alignment; the following packages now ship together as `7.1.0-preview2`: +> +> - `Microsoft.Data.SqlClient` +> - `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` +> - `Microsoft.Data.SqlClient.Extensions.Azure` +> - `Microsoft.Data.SqlClient.Extensions.Abstractions` +> - `Microsoft.Data.SqlClient.Internal.Logging` +> +> (`Microsoft.SqlServer.Server` continues to version independently and remains at `1.0.0`.) +> +> Applications that reference `Microsoft.Data.SqlClient.Extensions.Azure` must upgrade it to `7.1.0-preview2` when upgrading `Microsoft.Data.SqlClient` to `7.1.0-preview2`. +> +> **Compatibility guarantee:** All aligned assemblies ship with `FileVersion 7.1.0.x` and `AssemblyVersion 7.0.0.0`. The `AssemblyVersion` is unchanged from [7.0.2](release-notes/7.0/7.0.2.md), so upgrading from `7.0.2` to `7.1.0-preview2` does **not** require any new .NET Framework strong-name binding redirects. See the 7.0.2 release notes for the original `AssemblyVersion` alignment (the one-time breaking change that raised `AssemblyVersion` from `1.0.0.0` to `7.0.0.0` for `Extensions.Azure`, `Extensions.Abstractions`, and `Internal.Logging`; `AzureKeyVaultProvider` was already on `7.x`). + +### Added + +- Added `SqlConnection.GetSchemaAsync` overloads mirroring the existing synchronous shapes. + ([#3005](https://github.com/dotnet/SqlClient/pull/3005)) + +- Added SQL Graph column-alias support (`$node_id`, `$edge_id`, `$from_id`, `$to_id`) as destination columns in `SqlBulkCopy`. + ([#3677](https://github.com/dotnet/SqlClient/pull/3677)) + +- `SqlBatchCommand.CommandBehavior` (a driver-specific property that existed since batching was introduced but was previously ignored) is now honored during `SqlBatch` execution, and `SqlBatch.ExecuteReader` now respects the `CommandBehavior` value passed to it. + ([#4125](https://github.com/dotnet/SqlClient/pull/4125)) + +- Added a `Connection Idle Timeout` connection-string keyword and matching `SqlConnectionStringBuilder.IdleTimeout` property to evict idle pooled connections (default `300` seconds; `0` disables). Enforcement is opt-in via `Switch.Microsoft.Data.SqlClient.UseLegacyIdleTimeoutBehavior=false`; the default preserves the historical pooling behavior. When enabled, idle-timeout enforcement applies to the existing connection pool as well. + ([#4295](https://github.com/dotnet/SqlClient/pull/4295)) + +### Changed + +- The `Connect Timeout` budget can now be propagated through pool acquisition via a shared `TimeoutTimer`, so time spent waiting in the pool is deducted from the overall timeout. Enforcement is opt-in via `Switch.Microsoft.Data.SqlClient.UseOverallConnectTimeoutForPoolWait=true`; the default (`false`) preserves the historical behavior where pool waits do not count against `Connect Timeout`. Adds a dependency on `Microsoft.Bcl.TimeProvider`. + ([#4270](https://github.com/dotnet/SqlClient/pull/4270)) + +- Hardened `SqlConnection` internal state transitions with `Interlocked.CompareExchange` guards. + ([#4267](https://github.com/dotnet/SqlClient/pull/4267)) + +- Removed legacy connection-options inheritance from internal APIs and simplified `TryOpenConnection` / `TryReplaceConnection` / pool interfaces. + ([#4235](https://github.com/dotnet/SqlClient/pull/4235), + [#4237](https://github.com/dotnet/SqlClient/pull/4237), + [#4261](https://github.com/dotnet/SqlClient/pull/4261), + [#4415](https://github.com/dotnet/SqlClient/pull/4415)) + +- Added the SQL Server 2025 `json` data type to the `DataTypes` schema table returned by `SqlConnection.GetSchema`. + ([#3858](https://github.com/dotnet/SqlClient/pull/3858)) + +- Added async generic helpers to reduce sync/async duplication. + ([#4334](https://github.com/dotnet/SqlClient/pull/4334)) + +- Use hardcoded LCID mappings when decoding strings. + ([#4212](https://github.com/dotnet/SqlClient/pull/4212)) + +- Reduced allocations by skipping lock acquisition on `SqlErrorCollection` counters when no errors exist, and by avoiding stack-trace materialization on expected `null`-return paths. + ([#4099](https://github.com/dotnet/SqlClient/pull/4099), + [#4102](https://github.com/dotnet/SqlClient/pull/4102), + [#4157](https://github.com/dotnet/SqlClient/pull/4157)) + +- Improved accuracy of `EnclaveDiffieHellmanInfo.Size`. + ([#4346](https://github.com/dotnet/SqlClient/pull/4346)) + +- `SqlVector` now serializes and deserializes multibyte values as little-endian explicitly. + ([#3861](https://github.com/dotnet/SqlClient/pull/3861)) + +- Updated the bundled .NET 10 SDK to `10.0.300`. + ([#4287](https://github.com/dotnet/SqlClient/pull/4287)) + +- Re-shipped `Microsoft.Data.SqlClient.Extensions.Azure` as `7.1.0-preview2`, adding Windows Account Manager (WAM) broker support for Entra ID authentication on Windows. See [release notes](release-notes/Extensions/Azure/7.1/7.1.0-preview2.md). + +- Re-shipped `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider`, `Microsoft.Data.SqlClient.Extensions.Abstractions`, and `Microsoft.Data.SqlClient.Internal.Logging` as `7.1.0-preview2` (version alignment only, no functional changes). See release notes for [AKV](release-notes/add-ons/AzureKeyVaultProvider/7.1/7.1.0-preview2.md), [Abstractions](release-notes/Extensions/Abstractions/7.1/7.1.0-preview2.md), and [Logging](release-notes/Internal/Logging/7.1/7.1.0-preview2.md). + +### Fixed + +- Fixed a `NullReferenceException` in `SqlCommand.Cancel()` when the active connection has already been torn down. + ([#4372](https://github.com/dotnet/SqlClient/pull/4372)) + +- Fixed Always Encrypted column master key signature verification incorrectly reusing cached results after a prior verification failure. + ([#4339](https://github.com/dotnet/SqlClient/pull/4339)) + +- Fixed missing bounds checks on TDS token and feature-extension-acknowledgment data lengths that could allow a spoofing server to trigger unbounded allocations. + ([#4340](https://github.com/dotnet/SqlClient/pull/4340)) + +- Fixed `SqlBulkCopy` failing in least-privilege environments. + ([#4306](https://github.com/dotnet/SqlClient/pull/4306)) + +- Fixed Always Encrypted reads of `CekMdVersion` and `EkValueCount` to align with the TDS specification. + ([#4240](https://github.com/dotnet/SqlClient/pull/4240)) + +- Fixed `LoginWithFailover` to validate parser state before continuing. + ([#4140](https://github.com/dotnet/SqlClient/pull/4140)) + +- Fixed the SPN used during login to use the resolved port instead of the instance name when `Protocol=None` or `Protocol=Admin`. + ([#4180](https://github.com/dotnet/SqlClient/pull/4180)) + +- Fixed a race in `SqlConnection.TryOpenInner` that could surface as `InvalidCastException`; the same race now returns a deterministic `InvalidOperationException`. + ([#4179](https://github.com/dotnet/SqlClient/pull/4179)) + +- Fixed several `CancellationTokenSource` leaks across `SqlDataReader`, `SqlConnection` reconnect, `SqlCommand` reconnect timeout, and sequential-stream helpers. + ([#4009](https://github.com/dotnet/SqlClient/pull/4009)) + +- Fixed server certificate documentation. + ([#4408](https://github.com/dotnet/SqlClient/pull/4408)) + +### Removed + +- **Breaking:** Removed SQL Server 7.0 and SQL Server 2000 support, along with the `TypeSystem.SQLServer2000` enum value and the `Type System Version=SQL Server 2000` connection-string value. Connection strings that specify this value now throw `ArgumentException` when the connection is opened. Supported values are `Latest`, `SQL Server 2005`, `SQL Server 2008`, and `SQL Server 2012`. + ([#4015](https://github.com/dotnet/SqlClient/pull/4015)) + ## [Stable Release 7.0.2] - 2026-06-24 This update brings the following changes since the [7.0.1](release-notes/7.0/7.0.1.md) release. diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 438b011fab..3cf0b6c01e 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -24,11 +24,11 @@ variables: # Abstractions library assembly file version - name: abstractionsAssemblyFileVersion - value: 1.1.0.$(assemblyBuildNumber) + value: 7.1.0.$(assemblyBuildNumber) # Abstractions library NuGet package version - name: abstractionsPackageVersion - value: 1.1.0-preview1ci$(Build.BuildNumber) + value: 7.1.0-preview2ci$(Build.BuildNumber) # AKV provider assembly file version - name: akvAssemblyFileVersion @@ -36,30 +36,30 @@ variables: # AKV provider NuGet package version - name: akvPackageVersion - value: 7.1.0-preview1ci$(Build.BuildNumber) + value: 7.1.0-preview2ci$(Build.BuildNumber) # Azure library assembly file version - name: azureAssemblyFileVersion - value: 1.1.0.$(assemblyBuildNumber) + value: 7.1.0.$(assemblyBuildNumber) # Azure library NuGet package version - name: azurePackageVersion - value: 1.1.0-preview1ci$(Build.BuildNumber) + value: 7.1.0-preview2ci$(Build.BuildNumber) # Logging library assembly file version - name: loggingAssemblyFileVersion - value: 1.1.0.$(assemblyBuildNumber) + value: 7.1.0.$(assemblyBuildNumber) # Logging library NuGet package version - name: loggingPackageVersion - value: 1.1.0-preview1ci$(Build.BuildNumber) + value: 7.1.0-preview2ci$(Build.BuildNumber) # MDS library NuGet package version # NOTE: This differs from the other structures! MdsVersions.props will deconstruct a provided # package version and build an assembly version from it. If the build number is included # before the "-" the build number will be appended again, generating an invalid file version. - name: mdsPackageVersion - value: 7.1.0-preview1ci$(AssemblyBuildNumber) + value: 7.1.0-preview2ci$(AssemblyBuildNumber) # SqlServer library NuGet package version # NOTE: SqlServer's version props derive file version from the package version by appending the diff --git a/eng/pipelines/onebranch/variables/package-variables.yml b/eng/pipelines/onebranch/variables/package-variables.yml index 0a84bc3a70..5a7d456df8 100644 --- a/eng/pipelines/onebranch/variables/package-variables.yml +++ b/eng/pipelines/onebranch/variables/package-variables.yml @@ -53,13 +53,13 @@ variables: value: 'drop_build_abstractions_build_package_Abstractions' - name: abstractionsFileVersion - value: '1.0.0.$(_assemblyBuildNumber)' + value: '7.1.0.$(_assemblyBuildNumber)' - name: abstractionsPackageVersion ${{ if parameters.isPreview }}: - value: '1.0.0-preview1.$(Build.BuildNumber)' + value: '7.1.0-preview2.$(Build.BuildNumber)' ${{ else }}: - value: '1.0.0' + value: '7.1.0' # ------------------------------------------------------------------------ # AKV Provider Package @@ -68,13 +68,13 @@ variables: value: 'drop_build_addons_build_package_AkvProvider' - name: akvProviderFileVersion - value: '7.0.0.$(_assemblyBuildNumber)' + value: '7.1.0.$(_assemblyBuildNumber)' - name: akvProviderPackageVersion ${{ if parameters.isPreview }}: - value: '7.0.0-preview1.$(Build.BuildNumber)' + value: '7.1.0-preview2.$(Build.BuildNumber)' ${{ else }}: - value: '7.0.0' + value: '7.1.0' # ------------------------------------------------------------------------ # Azure Package @@ -83,13 +83,13 @@ variables: value: 'drop_build_dependent_build_package_Azure' - name: azureFileVersion - value: '1.0.0.$(_assemblyBuildNumber)' + value: '7.1.0.$(_assemblyBuildNumber)' - name: azurePackageVersion ${{ if parameters.isPreview }}: - value: '1.0.0-preview1.$(Build.BuildNumber)' + value: '7.1.0-preview2.$(Build.BuildNumber)' ${{ else }}: - value: '1.0.0' + value: '7.1.0' # ------------------------------------------------------------------------ # Logging Package @@ -98,13 +98,13 @@ variables: value: 'drop_build_independent_build_package_Logging' - name: loggingFileVersion - value: '1.0.0.$(_assemblyBuildNumber)' + value: '7.1.0.$(_assemblyBuildNumber)' - name: loggingPackageVersion ${{ if parameters.isPreview }}: - value: '1.0.0-preview1.$(Build.BuildNumber)' + value: '7.1.0-preview2.$(Build.BuildNumber)' ${{ else }}: - value: '1.0.0' + value: '7.1.0' # ------------------------------------------------------------------------ # SqlClient Package @@ -117,7 +117,7 @@ variables: - name: sqlClientPackageVersion ${{ if parameters.isPreview }}: - value: '7.1.0-preview1.$(Build.BuildNumber)' + value: '7.1.0-preview2.$(Build.BuildNumber)' ${{ else }}: value: '7.1.0' diff --git a/release-notes/7.1/7.1.0-preview2.md b/release-notes/7.1/7.1.0-preview2.md new file mode 100644 index 0000000000..5f4a65bb78 --- /dev/null +++ b/release-notes/7.1/7.1.0-preview2.md @@ -0,0 +1,210 @@ +# Release Notes + +## Preview Release 7.1.0-preview2 - 2026-07-09 + +This update brings the following changes since the [7.1.0-preview1](7.1.0-preview1.md) release: + +> **Important — package version alignment:** Starting with the [7.0.2](../7.0/7.0.2.md) release, the `Microsoft.Data.SqlClient` driver and its companion packages share a single aligned version. Preview 2 of the `7.1` line continues this alignment; the following packages ship together as `7.1.0-preview2`: +> +> - `Microsoft.Data.SqlClient` +> - `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` +> - `Microsoft.Data.SqlClient.Extensions.Azure` +> - `Microsoft.Data.SqlClient.Extensions.Abstractions` +> - `Microsoft.Data.SqlClient.Internal.Logging` +> +> (`Microsoft.SqlServer.Server` continues to version independently and remains at `1.0.0`.) +> +> Applications must reference the same versions of `Microsoft.Data.SqlClient` and its extensions for best compatibility. In particular, applications that reference `Microsoft.Data.SqlClient.Extensions.Azure` must upgrade it to `7.1.0-preview2` when upgrading `Microsoft.Data.SqlClient` to `7.1.0-preview2`. +> +> **Compatibility guarantee:** All aligned assemblies ship with `FileVersion 7.1.0.x` and `AssemblyVersion 7.0.0.0`. The `AssemblyVersion` is unchanged from [7.0.2](../7.0/7.0.2.md), so upgrading from `7.0.2` to `7.1.0-preview2` does **not** require any new .NET Framework strong-name binding redirects. See the 7.0.2 release notes for the original `AssemblyVersion` alignment (the one-time breaking change that raised `AssemblyVersion` from `1.0.0.0` to `7.0.0.0` for `Extensions.Azure`, `Extensions.Abstractions`, and `Internal.Logging`; `AzureKeyVaultProvider` was already on `7.x`). + +### Added + +#### `SqlConnection.GetSchemaAsync` + +*What Changed:* + +- Added asynchronous overloads of `SqlConnection.GetSchema` that mirror the existing synchronous shapes and honor a supplied `CancellationToken`. The .NET Framework schema code paths were also unified with the .NET Core implementation. + ([#3005](https://github.com/dotnet/SqlClient/pull/3005)) + +*Who Benefits:* + +- Applications that enumerate database metadata (databases, tables, columns, procedures, etc.) as part of a request pipeline can now do so without blocking a thread on synchronous I/O. + +*Impact:* + +- New `GetSchemaAsync(...)` overloads are additive; existing `GetSchema(...)` calls are unchanged. + +#### SQL Graph Column Aliases in `SqlBulkCopy` + +*What Changed:* + +- `SqlBulkCopy` column mappings now accept the SQL Graph pseudo-column aliases `$node_id`, `$edge_id`, `$from_id`, and `$to_id` as destination column names and transparently map them to the underlying physical columns. + ([#3677](https://github.com/dotnet/SqlClient/pull/3677)) + +*Who Benefits:* + +- Applications that bulk-load SQL Graph node or edge tables can now map source fields directly to the friendly graph aliases instead of pre-computing the internal physical column names. + +*Impact:* + +- Existing mappings continue to work; if a physical column with the same name as an alias already exists, the physical column keeps priority. + +#### `SqlBatchCommand.CommandBehavior` Is Now Honored + +*What Changed:* + +- The `SqlBatchCommand.CommandBehavior` property (which existed since batching was introduced but was previously ignored) is now plumbed through the batch execution path so per-command behaviors are honored inside a `SqlBatch`. `SqlBatch.ExecuteReader` also now respects the `CommandBehavior` value passed to it. + ([#4125](https://github.com/dotnet/SqlClient/pull/4125)) + +*Who Benefits:* + +- Applications composing heterogeneous batches (for example, mixing `SchemaOnly` metadata queries with data-returning statements) can now express per-command reader behavior without splitting the batch. + +*Impact:* + +- Batches that previously set `SqlBatchCommand.CommandBehavior` but relied on it being ignored will now see that value applied. Batches that left it at the default (`CommandBehavior.Default`) are unaffected. + +#### Configurable Idle Connection Timeout + +*What Changed:* + +- Added a `Connection Idle Timeout` connection-string keyword and matching `SqlConnectionStringBuilder.IdleTimeout` property that let the connection pool evict pooled connections whose idle time exceeds the configured value. The default is `300` seconds; setting `0` disables idle expiration, and negative values throw `ArgumentException`. Enforcement is gated on the new `Switch.Microsoft.Data.SqlClient.UseLegacyIdleTimeoutBehavior` AppContext switch, which defaults to `true` to preserve historical pooling behavior; set it to `false` to enable idle-timeout enforcement. + ([#4295](https://github.com/dotnet/SqlClient/pull/4295)) + +*Who Benefits:* + +- Applications running against Azure SQL Database and other elastic backends can bound connection idle time to align with server-side session recycling and reduce stale-connection failures. + +*Impact:* + +- Default behavior is unchanged: the new keyword is parsed but not enforced until the legacy switch is turned off. When enabled, idle-timeout enforcement applies to the existing connection pool as well. + +### Changed + +#### Connection Timeout Now Propagates Through the Pool + +*What Changed:* + +- Replaced raw `TimeSpan` timeouts with a shared `TimeoutTimer` across `SqlConnection.Open[Async]`, pool acquisition, and physical connection creation, so the `Connect Timeout` budget can be deducted as a request waits in the pool. Introduces a dependency on `Microsoft.Bcl.TimeProvider`. Enforcement is gated on the new `Switch.Microsoft.Data.SqlClient.UseOverallConnectTimeoutForPoolWait` AppContext switch, which defaults to `false` to preserve historical behavior; set it to `true` to have pool waits count against the caller's `Connect Timeout` budget. + ([#4270](https://github.com/dotnet/SqlClient/pull/4270)) + +*Who Benefits:* + +- Applications that observe pool contention can opt in via the new switch to have the configured `Connect Timeout` respected end-to-end instead of the budget effectively restarting when a physical connection is eventually opened. + +*Impact:* + +- Default behavior is unchanged: pool waits do not count against `Connect Timeout` unless the new switch is enabled. When enabled, `Open`/`OpenAsync` under heavy pool contention may surface timeouts sooner than before. Successful opens are unaffected. + +#### Other changes + +- Added the SQL Server 2025 `json` data type to the `DataTypes` collection returned by `SqlConnection.GetSchema`. ([#3858](https://github.com/dotnet/SqlClient/pull/3858)) +- Refactored `SqlConnection` internal state transitions to use `Interlocked.CompareExchange` guards, hardening the driver against races surfaced by concurrent open/close/reconnect. ([#4267](https://github.com/dotnet/SqlClient/pull/4267)) +- Removed legacy connection-options inheritance from internal APIs, simplifying `TryOpenConnection`, `TryReplaceConnection`, and the pool interfaces. ([#4237](https://github.com/dotnet/SqlClient/pull/4237), [#4261](https://github.com/dotnet/SqlClient/pull/4261), [#4235](https://github.com/dotnet/SqlClient/pull/4235)) +- Refactored `ForceNewConnection` handling. ([#4415](https://github.com/dotnet/SqlClient/pull/4415)) +- Added Async generic helpers to reduce duplication across sync/async code paths. ([#4334](https://github.com/dotnet/SqlClient/pull/4334)) +- Use hardcoded LCID mappings when decoding strings, avoiding repeated culture lookups. ([#4212](https://github.com/dotnet/SqlClient/pull/4212)) +- Reduced allocations by avoiding lock acquisition on `SqlErrorCollection` counters when no errors exist, and by avoiding stack-trace materialization for expected `null`-return paths. ([#4157](https://github.com/dotnet/SqlClient/pull/4157), [#4099](https://github.com/dotnet/SqlClient/pull/4099), [#4102](https://github.com/dotnet/SqlClient/pull/4102)) +- Improved `EnclaveDiffieHellmanInfo.Size` accuracy. ([#4346](https://github.com/dotnet/SqlClient/pull/4346)) +- `SqlVector` now serializes and deserializes little-endian multibyte values explicitly for consistent behavior across architectures. ([#3861](https://github.com/dotnet/SqlClient/pull/3861)) +- Updated the bundled .NET 10 SDK to `10.0.300`. ([#4287](https://github.com/dotnet/SqlClient/pull/4287)) + +### Fixed + +- Fixed a `NullReferenceException` in `SqlCommand.Cancel()` when the active connection has already been torn down. ([#4372](https://github.com/dotnet/SqlClient/pull/4372)) +- Fixed Always Encrypted column master key signature verification incorrectly reusing cached results after a prior verification failure. ([#4339](https://github.com/dotnet/SqlClient/pull/4339)) +- Fixed missing bounds checks on TDS token and feature-extension-acknowledgment data lengths that could allow a spoofing server to trigger unbounded allocations. ([#4340](https://github.com/dotnet/SqlClient/pull/4340)) +- Fixed `SqlBulkCopy` failing in least-privilege environments where the caller lacks permission to run the previous metadata query path. ([#4306](https://github.com/dotnet/SqlClient/pull/4306)) +- Fixed Always Encrypted reads of `CekMdVersion` and `EkValueCount` to align with the TDS specification. ([#4240](https://github.com/dotnet/SqlClient/pull/4240)) +- Fixed `LoginWithFailover` to validate parser state before continuing, preventing null-reference failures during failover login. ([#4140](https://github.com/dotnet/SqlClient/pull/4140)) +- Fixed the SPN used during login to use the resolved port instead of the instance name when `Protocol=None` or `Protocol=Admin` is specified. ([#4180](https://github.com/dotnet/SqlClient/pull/4180)) +- Fixed a race in `SqlConnection.TryOpenInner` that could surface as `InvalidCastException`; the same race now returns a deterministic `InvalidOperationException`. ([#4179](https://github.com/dotnet/SqlClient/pull/4179)) +- Fixed several `CancellationTokenSource` leaks in `SqlDataReader`, `SqlConnection`, `SqlCommand` reconnect paths, and the sequential-stream helpers. ([#4009](https://github.com/dotnet/SqlClient/pull/4009)) +- Documentation fix for server certificate configuration. ([#4408](https://github.com/dotnet/SqlClient/pull/4408)) + +### Removed + +#### SQL Server 7.0 and 2000 Support + +*What Changed:* + +- Removed dead protocol-level code paths for SQL Server 7.0 and SQL Server 2000, along with the now-orphaned `SQL Server 2000` type-system compatibility option. The `TypeSystem.SQLServer2000` enum value and the `Type System Version=SQL Server 2000` connection-string branch are gone. + ([#4015](https://github.com/dotnet/SqlClient/pull/4015)) + +*Who Benefits:* + +- The driver no longer carries dead legacy code, and the documented connection-string surface (`Latest`, `SQL Server 2005`, `SQL Server 2008`, `SQL Server 2012`) now matches the implementation exactly. + +*Impact:* + +- **Breaking:** A connection string that specifies `Type System Version=SQL Server 2000` now throws `ArgumentException` when the connection is opened, instead of being accepted. Applications currently using this value should switch to a supported value such as `Latest`. There is no change to which servers the driver will connect to — SQL Server 7.0 and 2000 were already rejected during login version negotiation. + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64, Windows ARM64) +- .NET 8.0+ (Windows x86, Windows x64, Windows ARM, Windows ARM64, Linux, macOS) + +### Dependencies + +#### .NET 9.0 + +- Microsoft.Bcl.Cryptography 9.0.13 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.1.0-preview2 +- Microsoft.Data.SqlClient.Internal.Logging 7.1.0-preview2 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.13 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.13 +- System.Security.Cryptography.Pkcs 9.0.13 + +#### .NET 8.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.1.0-preview2 +- Microsoft.Data.SqlClient.Internal.Logging 7.1.0-preview2 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET Standard 2.0 + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Bcl.TimeProvider 8.0.1 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.1.0-preview2 +- Microsoft.Data.SqlClient.Internal.Logging 7.1.0-preview2 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 + +#### .NET Framework 4.6.2+ + +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Bcl.TimeProvider 8.0.1 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.1.0-preview2 +- Microsoft.Data.SqlClient.Internal.Logging 7.1.0-preview2 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 8.16.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0 +- System.Buffers 4.6.1 +- System.Data.Common 4.3.0 +- System.Diagnostics.DiagnosticSource 10.0.3 +- System.Memory 4.6.3 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Json 10.0.3 +- System.Threading.Channels 10.0.3 +- System.ValueTuple 4.6.2 diff --git a/release-notes/7.1/README.md b/release-notes/7.1/README.md index 7bb05b1a70..843ba62b36 100644 --- a/release-notes/7.1/README.md +++ b/release-notes/7.1/README.md @@ -5,3 +5,4 @@ The following Microsoft.Data.SqlClient 7.1 releases have been shipped: | Release Date | Version | Notes | |:-------------|:---------------|:----------------------------------:| | 2026-04-29 | 7.1.0-preview1 | [Release Notes](7.1.0-preview1.md) | +| 2026-07-09 | 7.1.0-preview2 | [Release Notes](7.1.0-preview2.md) | diff --git a/release-notes/Extensions/Abstractions/7.1/7.1.0-preview2.md b/release-notes/Extensions/Abstractions/7.1/7.1.0-preview2.md new file mode 100644 index 0000000000..9c1f8b1b7b --- /dev/null +++ b/release-notes/Extensions/Abstractions/7.1/7.1.0-preview2.md @@ -0,0 +1,19 @@ +# Release Notes + +## Preview Release 7.1.0-preview2 - 2026-07-09 + +This release continues version-alignment of `Microsoft.Data.SqlClient.Extensions.Abstractions` with the core `Microsoft.Data.SqlClient` driver version (`7.1.0-preview2`). The previous release of this package was [7.0.2](../7.0/7.0.2.md). + +There are no functional or API changes in this release. See the core [Microsoft.Data.SqlClient 7.1.0-preview2](../../../7.1/7.1.0-preview2.md) release notes for the driver-family changes shipped alongside this version. + +> **Version alignment:** This package's version continues to track the core `Microsoft.Data.SqlClient` driver version. See the [7.0.2 release notes](../../../7.0/7.0.2.md) for the initial alignment announcement. This assembly ships with `FileVersion 7.1.0.x`. The `AssemblyVersion 7.0.0.0` remains unchanged from the `7.0.2` release. + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- Microsoft.Data.SqlClient.Internal.Logging 7.1.0-preview2 diff --git a/release-notes/Extensions/Abstractions/7.1/README.md b/release-notes/Extensions/Abstractions/7.1/README.md new file mode 100644 index 0000000000..133eb71b32 --- /dev/null +++ b/release-notes/Extensions/Abstractions/7.1/README.md @@ -0,0 +1,8 @@ +# Microsoft.Data.SqlClient.Extensions.Abstractions 7.1 Releases + +The following `Microsoft.Data.SqlClient.Extensions.Abstractions` +7.1 releases have been shipped: + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2026-07-09 | 7.1.0-preview2 | [Release Notes](7.1.0-preview2.md) | diff --git a/release-notes/Extensions/Azure/7.1/7.1.0-preview2.md b/release-notes/Extensions/Azure/7.1/7.1.0-preview2.md new file mode 100644 index 0000000000..d2803f02db --- /dev/null +++ b/release-notes/Extensions/Azure/7.1/7.1.0-preview2.md @@ -0,0 +1,73 @@ +# Release Notes + +## Preview Release 7.1.0-preview2 - 2026-07-09 + +This release adds Windows Account Manager (WAM) broker support for Microsoft Entra ID authentication on Windows and continues version-alignment of `Microsoft.Data.SqlClient.Extensions.Azure` with the core `Microsoft.Data.SqlClient` driver version (`7.1.0-preview2`). The previous release of this package was [7.0.2](../7.0/7.0.2.md). See the core [Microsoft.Data.SqlClient 7.1.0-preview2](../../../7.1/7.1.0-preview2.md) release notes for the driver-family changes shipped alongside this version. + +> **Version alignment:** This package's version continues to track the core `Microsoft.Data.SqlClient` driver version. See the [7.0.2 release notes](../../../7.0/7.0.2.md) for the initial alignment announcement. This assembly ships with `FileVersion 7.1.0.x`. The `AssemblyVersion 7.0.0.0` remains unchanged from the `7.0.2` release. + +### Added + +#### WAM Broker Support for Entra ID Authentication (Windows only) + +*What Changed:* + +- Added Windows Account Manager (WAM) broker support for the supported Entra ID authentication modes (`ActiveDirectoryIntegrated`, `ActiveDirectoryInteractive`, `ActiveDirectoryDeviceCodeFlow`, and the deprecated `ActiveDirectoryPassword`). Introduces a new `ActiveDirectoryAuthenticationProviderOptions` type (with `UseWamBroker`, `ApplicationClientId`, and `DeviceCodeFlowCallback`), a new `ActiveDirectoryAuthenticationProvider(ActiveDirectoryAuthenticationProviderOptions)` constructor overload, a cross-platform `SetParentActivityOrWindowFunc(Func?)` method (superseding the .NET Framework-only `SetIWin32WindowFunc`), and a `useWamBroker` attribute on the `` configuration section. + ([#4288](https://github.com/dotnet/SqlClient/pull/4288), + [#4388](https://github.com/dotnet/SqlClient/pull/4388)) + +*Who Benefits:* + +- Windows users get single sign-on with the signed-in Windows account, Windows Hello, and Conditional Access support through the OS broker. Device Code Flow now uses its own token-lifetime timeout instead of inheriting `Connect Timeout`, fixing spurious `"The operation was canceled."` failures when the user takes longer than a few seconds to enter the device code. + +*Impact:* + +- WAM is enabled by default when the driver's built-in client id is used. With a custom client id, WAM is opt-in via `ActiveDirectoryAuthenticationProviderOptions.UseWamBroker` or the `useWamBroker` config attribute. Non-Windows platforms never invoke the broker. +- Existing `ActiveDirectoryAuthenticationProvider` constructors remain source- and binary-compatible. +- Adding WAM broker support materially increases the on-disk footprint of `Microsoft.Data.SqlClient.Extensions.Azure` because it pulls in the `Microsoft.Identity.Client.Broker` package and its native OS-broker components. See [#4410](https://github.com/dotnet/SqlClient/issues/4410) for tracking. + +```c# +var options = new ActiveDirectoryAuthenticationProviderOptions +{ + ApplicationClientId = "", + UseWamBroker = true, // Windows only +}; +var provider = new ActiveDirectoryAuthenticationProvider(options); +SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, provider); +``` + +- A new `AzureSqlConnector` sample demonstrating interactive Entra ID authentication with and without the WAM broker is available under [doc/apps/AzureSqlConnector](../../../../doc/apps/AzureSqlConnector/README.md). + +### Changed + +- Updated Dependencies + ([#4288](https://github.com/dotnet/SqlClient/pull/4288)): + - Added `Microsoft.Identity.Client.Broker` v4.84.2 (required for WAM broker support). + - Updated `Microsoft.Identity.Client` to v4.84.2. + +## Target Platform Support + +- .NET Standard 2.0 +- .NET Framework 4.6.2+ + +### Dependencies + +#### .NET Standard 2.0 + +- Azure.Core 1.51.1 +- Azure.Identity 1.18.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.1.0-preview2 +- Microsoft.Data.SqlClient.Internal.Logging 7.1.0-preview2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.84.2 +- Microsoft.Identity.Client.Broker 4.84.2 + +#### .NET Framework 4.6.2+ + +- Azure.Core 1.51.1 +- Azure.Identity 1.18.0 +- Microsoft.Data.SqlClient.Extensions.Abstractions 7.1.0-preview2 +- Microsoft.Data.SqlClient.Internal.Logging 7.1.0-preview2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.Identity.Client 4.84.2 +- Microsoft.Identity.Client.Broker 4.84.2 diff --git a/release-notes/Extensions/Azure/7.1/README.md b/release-notes/Extensions/Azure/7.1/README.md new file mode 100644 index 0000000000..3acdf3d45c --- /dev/null +++ b/release-notes/Extensions/Azure/7.1/README.md @@ -0,0 +1,8 @@ +# Microsoft.Data.SqlClient.Extensions.Azure 7.1 Releases + +The following `Microsoft.Data.SqlClient.Extensions.Azure` +7.1 releases have been shipped: + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2026-07-09 | 7.1.0-preview2 | [Release Notes](7.1.0-preview2.md) | diff --git a/release-notes/Internal/Logging/7.1/7.1.0-preview2.md b/release-notes/Internal/Logging/7.1/7.1.0-preview2.md new file mode 100644 index 0000000000..a5987cdd0d --- /dev/null +++ b/release-notes/Internal/Logging/7.1/7.1.0-preview2.md @@ -0,0 +1,22 @@ +# Release Notes + +## Preview Release 7.1.0-preview2 - 2026-07-09 + +> **Note:** This package is for internal use by other Microsoft.Data.SqlClient packages only +> and should not be referenced directly by application code. + +This release continues version-alignment of `Microsoft.Data.SqlClient.Internal.Logging` with the core `Microsoft.Data.SqlClient` driver version (`7.1.0-preview2`). The previous release of this package was [7.0.2](../7.0/7.0.2.md). + +There are no functional or API changes in this release. See the core [Microsoft.Data.SqlClient 7.1.0-preview2](../../../7.1/7.1.0-preview2.md) release notes for the driver-family changes shipped alongside this version. + +> **Version alignment:** This package's version continues to track the core `Microsoft.Data.SqlClient` driver version. See the [7.0.2 release notes](../../../7.0/7.0.2.md) for the initial alignment announcement. This assembly ships with `FileVersion 7.1.0.x`. The `AssemblyVersion 7.0.0.0` remains unchanged from the `7.0.2` release. + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- None diff --git a/release-notes/Internal/Logging/7.1/README.md b/release-notes/Internal/Logging/7.1/README.md new file mode 100644 index 0000000000..f0c0f8c274 --- /dev/null +++ b/release-notes/Internal/Logging/7.1/README.md @@ -0,0 +1,11 @@ +# Microsoft.Data.SqlClient.Internal.Logging 7.1 Releases + +> **Note:** This package is for internal use by other Microsoft.Data.SqlClient packages only +> and should not be referenced directly by application code. + +The following `Microsoft.Data.SqlClient.Internal.Logging` +7.1 releases have been shipped: + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2026-07-09 | 7.1.0-preview2 | [Release Notes](7.1.0-preview2.md) | diff --git a/release-notes/README.md b/release-notes/README.md index dc90822a2d..c32e0bc2be 100644 --- a/release-notes/README.md +++ b/release-notes/README.md @@ -27,6 +27,7 @@ The latest stable release is ## Release Information +- [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 7.1](add-ons/AzureKeyVaultProvider/7.1) - [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 7.0](add-ons/AzureKeyVaultProvider/7.0) - [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 6.1](add-ons/AzureKeyVaultProvider/6.1) - [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 6.0](add-ons/AzureKeyVaultProvider/6.0) @@ -44,6 +45,7 @@ The latest release is ## Release Information +- [Microsoft.Data.SqlClient.Extensions.Abstractions 7.1](Extensions/Abstractions/7.1) - [Microsoft.Data.SqlClient.Extensions.Abstractions 7.0](Extensions/Abstractions/7.0) - [Microsoft.Data.SqlClient.Extensions.Abstractions 1.0](Extensions/Abstractions/1.0) @@ -54,6 +56,7 @@ The latest release is ## Release Information +- [Microsoft.Data.SqlClient.Extensions.Azure 7.1](Extensions/Azure/7.1) - [Microsoft.Data.SqlClient.Extensions.Azure 7.0](Extensions/Azure/7.0) - [Microsoft.Data.SqlClient.Extensions.Azure 1.0](Extensions/Azure/1.0) @@ -65,6 +68,7 @@ The latest release is ## Release Information +- [Microsoft.Data.SqlClient.Internal.Logging 7.1](Internal/Logging/7.1) - [Microsoft.Data.SqlClient.Internal.Logging 7.0](Internal/Logging/7.0) - [Microsoft.Data.SqlClient.Internal.Logging 1.0](Internal/Logging/1.0) diff --git a/release-notes/add-ons/AzureKeyVaultProvider/7.1/7.1.0-preview2.md b/release-notes/add-ons/AzureKeyVaultProvider/7.1/7.1.0-preview2.md new file mode 100644 index 0000000000..e076bd52e6 --- /dev/null +++ b/release-notes/add-ons/AzureKeyVaultProvider/7.1/7.1.0-preview2.md @@ -0,0 +1,23 @@ +# Release Notes + +## Preview Release 7.1.0-preview2 - 2026-07-09 + +This release continues version-alignment of `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` with the core `Microsoft.Data.SqlClient` driver version (`7.1.0-preview2`). The previous release of this package was [7.0.2](../7.0/7.0.2.md). + +There are no functional or API changes in this release. See the core [Microsoft.Data.SqlClient 7.1.0-preview2](../../../7.1/7.1.0-preview2.md) release notes for the driver-family changes shipped alongside this version. + +> **Version alignment:** This package's version continues to track the core `Microsoft.Data.SqlClient` driver version. See the [7.0.2 release notes](../../../7.0/7.0.2.md) for the initial alignment announcement. This assembly ships with `FileVersion 7.1.0.x`. The `AssemblyVersion 7.0.0.0` remains unchanged from the `7.0.2` release. + +## Target Platform Support + +- .NET Standard 2.0 + +### Dependencies + +#### .NET Standard 2.0 + +- Azure.Core 1.51.1 +- Azure.Security.KeyVault.Keys 4.9.0 +- Microsoft.Data.SqlClient 7.1.0-preview2 +- Microsoft.Data.SqlClient.Internal.Logging 7.1.0-preview2 +- Microsoft.Extensions.Caching.Memory 8.0.1 diff --git a/release-notes/add-ons/AzureKeyVaultProvider/7.1/README.md b/release-notes/add-ons/AzureKeyVaultProvider/7.1/README.md new file mode 100644 index 0000000000..9fda87ae8f --- /dev/null +++ b/release-notes/add-ons/AzureKeyVaultProvider/7.1/README.md @@ -0,0 +1,8 @@ +# Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 7.1 Releases + +The following `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` +7.1 releases have been shipped: + +| Release Date | Description | Notes | +| :-- | :-- | :--: | +| 2026-07-09 | 7.1.0-preview2 | [Release Notes](7.1.0-preview2.md) | diff --git a/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props index dd9ddc03d5..8d939d6c86 100644 --- a/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props @@ -29,7 +29,7 @@ This is the *next* version to release of AkvProvider. Update this after a version is released. @TODO: Determine how versions should be updated in release branches vs main --> - 7.1.0-preview1 + 7.1.0-preview2 diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props index cc6d4ca14a..465620fe19 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Versions.props @@ -30,7 +30,7 @@ decided. @TODO: Determine how versions should be updated in release branches vs main --> - 1.1.0-preview1 + 7.1.0-preview2 diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props index 636face4e6..a7762cbbc0 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Versions.props @@ -29,7 +29,7 @@ This is the *next* version to release of Azure. Update this when the next version is decided. @TODO: Determine how versions should be updated in release branches vs main --> - 1.1.0-preview1 + 7.1.0-preview2 diff --git a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props index 9d031d21d0..3810bd91ae 100644 --- a/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props +++ b/src/Microsoft.Data.SqlClient.Internal/Logging/src/Versions.props @@ -30,7 +30,7 @@ decided. @TODO: Determine how versions should be updated in release branches vs main --> - 1.1.0-preview1 + 7.1.0-preview2 diff --git a/src/Microsoft.Data.SqlClient/Versions.props b/src/Microsoft.Data.SqlClient/Versions.props index 712263b73e..7c8dd25305 100644 --- a/src/Microsoft.Data.SqlClient/Versions.props +++ b/src/Microsoft.Data.SqlClient/Versions.props @@ -29,7 +29,7 @@ This is the *next* version to release of SqlClient. Update this after a version is released. @TODO: Determine how versions should be updated in release branches vs main --> - 7.1.0-preview1 + 7.1.0-preview2 From ba94b2b03d64dfc81b839fc5259671ced1fa0c18 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Tue, 7 Jul 2026 22:31:55 +0530 Subject: [PATCH 176/298] Improve release-notes prompt for version-aligned companion packages (#4409) --- .github/prompts/release-notes.prompt.md | 48 +++++++++++++++++-------- release-notes/7.0/7.0.2.md | 9 +++++ 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/.github/prompts/release-notes.prompt.md b/.github/prompts/release-notes.prompt.md index 523fe3524c..b800dfe245 100644 --- a/.github/prompts/release-notes.prompt.md +++ b/.github/prompts/release-notes.prompt.md @@ -10,6 +10,14 @@ Generate release notes for the milestone "${input:milestone}" on the branch "${i This repository ships multiple packages. Only generate release notes for packages that have relevant PRs in the milestone. All packages use the same template: [release-notes/template/release-notes-template.md](release-notes/template/release-notes-template.md). +## Branch Model + +The release notes content and the source code it describes live on different branches: + +- **Release notes files are maintained on `main`.** Every release's notes (for all branches/versions) are committed under `release-notes/` on `main`. Create and edit the release notes Markdown files on `main` (or a PR targeting `main`), not on the release branch. +- **The source code for the release lives only on the target branch `${input:branch}`.** Version sources (`Versions.props`), project files (`*.csproj`), and dependency files (`Directory.Packages.props`) reflect the released bits *as they exist on `${input:branch}`*, which can differ from `main`. When you look up versions, dependencies, TFM/OS scope, or verify API names (Steps 2.1, 2.2, 4, and the Version and Dependency Lookup table), read those source files from `${input:branch}` — not from your current `main` checkout. +- **Practical implication:** Do not assume a `...VersionDefault` or dependency version read from `main` matches what shipped on `${input:branch}`. Confirm against `${input:branch}` (e.g., `git show ${input:branch}:`), or against the milestone/release artifacts. + ## Package Registry | Package | Release Notes Directory | How to Identify PRs | @@ -21,20 +29,28 @@ This repository ships multiple packages. Only generate release notes for package | `Microsoft.Data.SqlClient.Extensions.Azure` | `release-notes/Extensions/Azure//` | PR titles/bodies/files referencing `Extensions.Azure` | | `Microsoft.Data.SqlClient.Internal.Logging` | `release-notes/Internal/Logging//` | PR titles/bodies/files referencing `Internal.Logging` | +> **Not all packages exist on every branch.** This table is the full, current package set. Older release branches ship a subset — for example, `release/6.1` and earlier have no extension packages (`Extensions.Abstractions`, `Extensions.Azure`) and no `Internal.Logging`; the companion-package set and even the `AzureKeyVaultProvider` source location vary by branch. Before generating notes for a package, confirm it actually exists on the target branch `${input:branch}` (e.g., `git ls-tree -r --name-only ${input:branch} | grep -i ""`). Skip any package that does not exist on `${input:branch}`, even if the table lists it. + ## Version and Dependency Lookup -Each package has its own versioning and dependency sources. Use these to determine package versions and dependency lists: +Each package's version and dependency information comes from MSBuild props/project files **on the target branch `${input:branch}`** (see Branch Model). The exact file paths, file names, and property names that hold versions **differ by branch**, because the versioning layout was refactored over time. Do not assume the layout of your current checkout — discover the version source on `${input:branch}`. + +Two known layouts: -| Package | Version Source | Dependency Source | -| ------- | -------------- | ----------------- | -| `Microsoft.Data.SqlClient` | [tools/props/Versions.props](tools/props/Versions.props) (`MdsVersionDefault`) | [Directory.Packages.props](Directory.Packages.props) and the [project file](src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj) | -| `AzureKeyVaultProvider` | [tools/props/Versions.props](tools/props/Versions.props) (`AkvVersionDefault`) | [AKV project file](src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider/src/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj) and [Directory.Packages.props](Directory.Packages.props) | -| `Microsoft.SqlServer.Server` | [tools/props/Versions.props](tools/props/Versions.props) (`SqlServerPackageVersion`) | [SqlServer project file](src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj) | -| `Extensions.Abstractions` | [AbstractionsVersions.props](src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props) | [Abstractions.csproj](src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj) | -| `Extensions.Azure` | [AzureVersions.props](src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props) | [Azure.csproj](src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj) | -| `Internal.Logging` | [LoggingVersions.props](src/Microsoft.Data.SqlClient.Internal/Logging/src/LoggingVersions.props) | [Logging.csproj](src/Microsoft.Data.SqlClient.Internal/Logging/src/Logging.csproj) | +| Layout | Branches | MDS version source | Companion/extension version sources | +| ------ | -------- | ------------------ | ----------------------------------- | +| **Centralized** | `release/7.0` (and earlier 7.0.x) | `tools/props/Versions.props` (`MdsVersionDefault`) | `tools/props/Versions.props` imports per-package props with the older names: `…/Extensions/Abstractions/src/AbstractionsVersions.props`, `…/Extensions/Azure/src/AzureVersions.props`, `…/Internal/Logging/src/LoggingVersions.props`, `…/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AkvProviderVersions.props` | +| **Per-package** | `main`, `7.1+` | `src/Microsoft.Data.SqlClient/Versions.props` (`SqlClientVersionDefault`) | Each package has its own `Versions.props`: `…/Extensions/Abstractions/src/Versions.props` (`AbstractionsVersionDefault`), `…/Extensions/Azure/src/Versions.props` (`AzureVersionDefault`), `…/Internal/Logging/src/Versions.props` (`LoggingVersionDefault`), `…/AlwaysEncrypted.AzureKeyVaultProvider/src/Versions.props` (`AkvProviderVersionDefault`), `…/Microsoft.SqlServer.Server/Versions.props` (`SqlServerVersionDefault`) | -Concrete dependency versions (e.g., `Azure.Core 1.49.0`) are centrally managed in [Directory.Packages.props](Directory.Packages.props). Framework-conditional versions (e.g., `net9.0` vs everything else) are handled by `Condition` attributes in the same file. +Discovery approach (works regardless of layout): + +1. List the version props on the target branch, e.g. `git ls-tree -r --name-only ${input:branch} | grep -i "Versions.props$"`. +2. Read the relevant file from the target branch, e.g. `git show ${input:branch}:`, and find the package's default/`PackageVersion` property. +3. Prefer the explicit shipped version: on a release branch the actual version may be supplied by the pipeline (`...PackageVersion`) rather than the `...VersionDefault` fallback, so confirm against the milestone/release artifacts rather than assuming the default. + +Dependency sources (read from `${input:branch}`): the per-package project file (`src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj`, the AKV `.csproj`, `Abstractions.csproj`, `Azure.csproj`, `Logging.csproj`, `Microsoft.SqlServer.Server.csproj`) plus the centrally-managed concrete versions in `Directory.Packages.props`. Framework-conditional versions (e.g., `net9.0` vs everything else) are handled by `Condition` attributes there. + +> **Companion package version alignment (7.0.2 and later):** Starting with 7.0.2, the companion packages (`AzureKeyVaultProvider`, `Extensions.Azure`, `Extensions.Abstractions`, `Internal.Logging`) ship version-aligned with the core `Microsoft.Data.SqlClient` driver. When generating notes for an aligned release, use the core MDS version (read from the target branch) for these companion packages — their per-package default version on `main` may point at a different next version and must not be assumed to be the shipped version. `Microsoft.SqlServer.Server` continues to version independently. ## Skills @@ -122,7 +138,7 @@ When release notes reference a public API (property, method, class): ### 5. Generate Release Notes for Each Package -For each package that has relevant PRs in the milestone: +For each package that ships in this milestone — i.e., it has relevant PRs, **or** it is a version-aligned companion package (7.0.2+) bumping to match the core `Microsoft.Data.SqlClient` release even without its own changes (see item 2): 1. **Determine the package version** using the Version Source from the lookup table above. Read the actual props/project file to find the version. @@ -138,6 +154,7 @@ For each package that has relevant PRs in the milestone: 1. **"Changes Since [last preview]"** — only the delta since the most recent preview of this package. 2. **"Cumulative Changes Since [last stable]"** — all changes since the last stable release of this package, synthesized from all preview release notes plus the GA milestone. This applies to every package (MDS, AKV, Extensions.Azure, Abstractions, Internal.Logging, etc.), not just the core driver. Apply the cross-referencing from Step 3 to eliminate items already shipped in prior stable patch releases. - **Preview releases:** Only include the delta since the previous release (preview or stable). No cumulative section is needed. + - **Version-alignment-only releases (7.0.2+ companion packages):** Starting with 7.0.2, the companion packages (`AzureKeyVaultProvider`, `Extensions.Azure`, `Extensions.Abstractions`, `Internal.Logging`) ship a new version aligned with the core driver on every core release — **even when they have no functional or API changes**. In that case, still create the package's release notes file using a version-alignment-only style: state that there are no functional or API changes, note the version alignment with the core driver, link to the core `Microsoft.Data.SqlClient ` notes, and (for .NET Framework) call out any `AssemblyVersion` strong-name change. Use the shipped 7.0.2 companion notes as the reference pattern (e.g., [release-notes/Extensions/Abstractions/7.0/7.0.2.md](release-notes/Extensions/Abstractions/7.0/7.0.2.md), [release-notes/Internal/Logging/7.0/7.0.2.md](release-notes/Internal/Logging/7.0/7.0.2.md), [release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.2.md](release-notes/add-ons/AzureKeyVaultProvider/7.0/7.0.2.md)). For the `Internal.Logging` package, retain its internal-use note. `Microsoft.SqlServer.Server` is **not** version-aligned and follows the normal skip rule. 3. **Create or update the version README** at `/README.md`. Follow the existing format — see [release-notes/add-ons/AzureKeyVaultProvider/6.1/README.md](release-notes/add-ons/AzureKeyVaultProvider/6.1/README.md) for reference: @@ -152,7 +169,9 @@ For each package that has relevant PRs in the milestone: | | | [Release Notes](.md) | ``` -4. **Skip packages without changes.** If a package has no relevant PRs in the milestone, do not create release notes for it. Report which packages had changes and which did not. +4. **Skip packages without changes (or that don't exist on the branch).** If a package has no relevant PRs in the milestone, or the package does not exist on the target branch `${input:branch}` (see the Package Registry note — older branches like `release/6.1` have no extension or `Internal.Logging` packages), do not create release notes for it. **Exception (7.0.2+):** version-aligned companion packages (`AzureKeyVaultProvider`, `Extensions.Azure`, `Extensions.Abstractions`, `Internal.Logging`) still get a release notes file when they bump to the aligned core version, even with no functional changes — use the version-alignment-only style from item 2. Report which packages had changes, which shipped alignment-only notes, which did not, and which are not present on the branch. + +5. **Cross-link companion packages from the core release notes.** When one or more companion packages (`AzureKeyVaultProvider`, `Extensions.Azure`, `Extensions.Abstractions`, `Internal.Logging`, `Microsoft.SqlServer.Server`) also ship in this milestone, add a `### Companion package release notes` section to the core `Microsoft.Data.SqlClient` release notes file that links to each companion package's release notes for the same version. This preserves context for the companion packages when the core release notes are used as the published GitHub release body. Use relative links (e.g., `../Extensions/Azure//.md`). Only list packages that actually shipped release notes in this milestone. ### 6. Update CHANGELOG.md @@ -177,6 +196,7 @@ For each package that has relevant PRs in the milestone: ## Notes -- Packages may ship as preview or stable independently. Use the actual version from the project/spec files. -- The directory structure mirrors existing conventions: `add-ons/AzureKeyVaultProvider/` for AKV, `MSqlServerServer/` for SqlServer, and `Extensions//` for the new extension packages. +- Release notes are maintained on `main` for all branches/releases, while the corresponding source code lives only on the target branch `${input:branch}` (see Branch Model above). Read version/dependency/source files from `${input:branch}`; write release notes files on `main`. +- Packages may ship as preview or stable independently. Use the actual version from the project/spec files on the target branch. +- The directory structure mirrors existing conventions: `add-ons/AzureKeyVaultProvider/` for AKV, `MSqlServerServer/` for SqlServer, `Extensions//` for the extension packages (e.g., `Extensions/Abstractions/`, `Extensions/Azure/`), and `Internal/Logging/` for the internal logging package. - When referencing code samples, link to files in the `doc/samples/` directory if a relevant sample exists. diff --git a/release-notes/7.0/7.0.2.md b/release-notes/7.0/7.0.2.md index d1ea72a8db..c6eaf4cece 100644 --- a/release-notes/7.0/7.0.2.md +++ b/release-notes/7.0/7.0.2.md @@ -18,6 +18,15 @@ This update brings the following changes since the [7.0.1](7.0.1.md) release: > **Breaking change (.NET Framework only):** As part of this alignment, the `AssemblyVersion` of `Microsoft.Data.SqlClient.Extensions.Azure`, `Microsoft.Data.SqlClient.Extensions.Abstractions`, and `Microsoft.Data.SqlClient.Internal.Logging` changed from `1.0.0.0` to `7.0.0.0` (the `Microsoft.Data.SqlClient` and `Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider` assembly versions are unchanged). On .NET Framework, `AssemblyVersion` is part of the strong-name identity, so applications that drop these assemblies into an existing deployment without rebuilding must rebuild against the 7.0.2 packages (or add binding redirects). Applications on .NET / .NET Core are not affected. +### Companion package release notes + +The following companion packages ship aligned as `7.0.2`. See their individual release notes for package-specific changes (including the `Microsoft.Data.SqlClient.Extensions.Azure` WAM broker support): + +- [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 7.0.2](../add-ons/AzureKeyVaultProvider/7.0/7.0.2.md) +- [Microsoft.Data.SqlClient.Extensions.Azure 7.0.2](../Extensions/Azure/7.0/7.0.2.md) +- [Microsoft.Data.SqlClient.Extensions.Abstractions 7.0.2](../Extensions/Abstractions/7.0/7.0.2.md) +- [Microsoft.Data.SqlClient.Internal.Logging 7.0.2](../Internal/Logging/7.0/7.0.2.md) + ### Fixed - Fixed a `NullReferenceException` in `SqlCommand.Cancel()`. The diagnostic message built during cancellation dereferenced the active connection directly; it now uses a null-conditional access so cancellation no longer throws when the connection has already been torn down. From 604b95a03bf46d73715d3751be4608b68fb341db Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Tue, 7 Jul 2026 16:05:55 -0500 Subject: [PATCH 177/298] PR Pipeline | Fix Test Result Publishing (#4428) --- eng/pipelines/pr/steps/publish-test-results-step.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eng/pipelines/pr/steps/publish-test-results-step.yml b/eng/pipelines/pr/steps/publish-test-results-step.yml index c64c3a974f..fa8e5839b7 100644 --- a/eng/pipelines/pr/steps/publish-test-results-step.yml +++ b/eng/pipelines/pr/steps/publish-test-results-step.yml @@ -45,10 +45,9 @@ steps: displayName: 'Publish Test Results' inputs: buildConfiguration: ${{ parameters.buildConfiguration }} + failTaskOnMissingResultsFile: true mergeTestResults: true - testResultsFiles: | - ${{ parameters.testResultsPath }}/*.trx - ${{ parameters.testResultsPath }}/**/*.coverage + testResultsFiles: ${{ parameters.testResultsPath }}/*.trx testResultsFormat: VSTest testRunTitle: "${{ parameters.platformDisplayName }}_${{ parameters.testDisplayName }}" condition: succeededOrFailed() From 6c03ccbf5f25898762547d36aae13489b79ca021 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:23:16 -0700 Subject: [PATCH 178/298] [Scheduled Run] Localized resource files from OneLocBuild (#4430) --- src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx | 4 ++-- src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx | 4 ++-- .../src/Resources/Strings.zh-Hans.resx | 4 ++-- .../src/Resources/Strings.zh-Hant.resx | 4 ++-- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx index dc677ada6a..fa8320d327 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx @@ -607,7 +607,7 @@ Pro {0} nelze najít zprostředkovatele ověřování. Nainstalujte balíček NuGet Microsoft.Data.SqlClient.Extensions.Azure (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), abyste mohli používat metody ověřování Active Directory (Entra ID). - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Nastavení useWamBroker vyžaduje balíček Microsoft.Data.SqlClient.Extensions.Azure, aby se zpřístupnilo Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions. Upgradujte balíček Microsoft.Data.SqlClient.Extensions.Azure alespoň na verzi 1.1.0, která zahrnuje tento typ. Parametr {0} nesmí obsahovat hodnotu Null ani nesmí být prázdný. @@ -964,7 +964,7 @@ Minimální doba (v sekundách), po jakou má být toto připojení uchováváno ve fondu, než bude zrušeno. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Maximální doba (v sekundách), po kterou může být připojení ve fondu nevyužité (nečinné), než se zahodí, když je zakázané starší chování při vypršení časového limitu nečinnosti. Hodnota 0 zakáže vypršení platnosti při nečinnosti. Maximální počet připojení povolených ve fondu. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx index 820d9f4568..ba72a1e54e 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx @@ -607,7 +607,7 @@ Für „{0}“ wurde kein Authentifizierungsanbieter gefunden. Installieren Sie das NuGet-Paket „Microsoft.Data.SqlClient.Extensions.Azure“ (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), um Active Directory (Entra ID)-Authentifizierungsmethoden zu verwenden. - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Die Einstellung „useWamBroker“ erfordert das Paket „Microsoft.Data.SqlClient.Extensions.Azure“, um „Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions“ bereitzustellen. Aktualisieren Sie das Paket „Microsoft.Data.SqlClient.Extensions.Azure“ auf mindestens Version 1.1.0, die diesen Typ enthält. Der Parameter "{0}" darf nicht NULL oder leer sein. @@ -964,7 +964,7 @@ Die Mindestdauer (in Sekunden), die diese Verbindung im Pool verbleibt, bevor sie gelöscht wird. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Die maximale Zeitspanne in Sekunden, die eine Verbindung im Pool ungenutzt bleiben kann, bevor sie verworfen wird, wenn das Verhalten des alten Leerlaufzeitlimits deaktiviert ist. Der Wert 0 deaktiviert den Ablauf des Leerlaufs. Die maximal zulässige Anzahl von Verbindungen im Pool. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx index 2789788846..b55a28adec 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx @@ -607,7 +607,7 @@ No se encuentra un proveedor de autenticación para ''{0}''. Instale el paquete NuGet "Microsoft.Data.SqlClient.Extensions.Azure" (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) para usar métodos de autenticación Active Directory (Id. de Entra). - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + La configuración "useWamBroker" requiere que el paquete "Microsoft.Data.SqlClient.Extensions.Azure" exponga "Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions". Actualice el paquete "Microsoft.Data.SqlClient.Extensions.Azure" a, como mínimo, la versión 1.1.0, que incluye este tipo. El parámetro "{0}" no puede ser nulo ni estar vacío. @@ -964,7 +964,7 @@ Periodo mínimo de tiempo (en segundos) que residirá esta conexión en el grupo antes de ser destruida. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + La cantidad máxima de tiempo (en segundos) que una conexión puede permanecer sin usar (inactiva) en el grupo antes de descartarse cuando se deshabilita el comportamiento heredado de tiempo de espera de inactividad. Un valor de 0 deshabilita la expiración por inactividad. Número máximo de conexiones permitidas en el grupo. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx index 67815a7f9f..d3bcada373 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx @@ -607,7 +607,7 @@ Impossible de trouver un fournisseur d'authentification pour « {0} ». Installez le package NuGet « Microsoft.Data.SqlClient.Extensions.Azure » (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) pour utiliser les méthodes d'authentification Active Directory Domain Services (Entra ID). - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Le paramètre « useWamBroker » nécessite le package « Microsoft.Data.SqlClient.Extensions.Azure » pour exposer « Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions ». Mettez à niveau le package « Microsoft.Data.SqlClient.Extensions.Azure » vers la version 1.1.0 ou ultérieure, qui inclut ce type. Le paramètre « {0} » ne peut pas être Null ou vide. @@ -964,7 +964,7 @@ Durée minimale de conservation (en secondes) de cette connexion avant sa destruction. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Durée maximale (en secondes) pendant laquelle une connexion peut rester inutilisée (inactive) dans le pool avant d’être supprimée lorsque le comportement hérité du délai d’inactivité est désactivé. Une valeur de 0 désactive l’expiration d’inactivité. Nombre maximal de connexions autorisé dans le pool. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx index 16ef46bd2c..69bd41ed1e 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx @@ -607,7 +607,7 @@ Non è possibile trovare un provider di autenticazione per '{0}'. Installare il pacchetto NuGet 'Microsoft.Data.SqlClient.Extensions.Azure' (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) per usare i metodi di autenticazione di Active Directory (Entra ID). - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + L'impostazione ''useWamBroker'' richiede che il pacchetto ''Microsoft.Data.SqlClient.Extensions.Azure'' esponga ''Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions''. Aggiorna il pacchetto ''Microsoft.Data.SqlClient.Extensions.Azure'' almeno alla versione 1.1.0, che include questo tipo. Il parametro '{0}' non può essere Null o vuoto. @@ -964,7 +964,7 @@ Tempo minimo di attesa (in secondi) perché la connessione sia attiva nel pool prima di essere eliminata. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Il periodo massimo di tempo (in secondi) durante il quale una connessione può rimanere inutilizzata (inattiva) nel pool prima di essere eliminata quando il comportamento legacy del timeout di inattività è disabilitato. Un valore di 0 disabilita la scadenza per inattività. Numero massimo di connessioni consentite nel pool. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx index 6fc0d5b867..cd3db1aa6e 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx @@ -607,7 +607,7 @@ '{0}' の認証プロバイダーが見つかりません。Active Directory (Entra ID) 認証方法を使用するには、'Microsoft.Data.SqlClient.Extensions.Azure' NuGet パッケージ (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) をインストールしてください。 - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + 'useWamBroker' を設定するには、'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions' を公開する 'Microsoft.Data.SqlClient.Extensions.Azure' パッケージが必要です。この型を含む v1.1.0 以降の 'Microsoft.Data.SqlClient.Extensions.Azure' パッケージにアップグレードしてください。 パラメーター '{0}' を null や空にすることはできません。 @@ -964,7 +964,7 @@ この接続が破棄されるまでにプールに存在できる最低限の時間 (秒) です。 - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + レガシのアイドル タイムアウト動作が無効になっている場合に、未使用 (アイドル) 状態の接続をプール内に保持できる最大時間 (秒単位) を指定します。値を 0 にすると、アイドルの有効期限が無効になります。 プールに保持できる最大接続数です。 diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx index fc186e4188..2b7ea71615 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx @@ -607,7 +607,7 @@ '{0}'에 대한 인증 공급자를 찾을 수 없습니다. Active Directory(Entra ID) 인증 방법을 사용하려면 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 패키지(https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure)를 설치하세요. - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + 'useWamBroker'를 설정하려면 'Microsoft.Data.SqlClient.Extensions.Azure' 패키지가 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'를 노출해야 합니다. 이 형식을 포함하는 'Microsoft.Data.SqlClient.Extensions.Azure' 패키지를 v1.1.0 이상으로 업그레이드하세요. '{0}' 매개 변수는 null이거나 비워 둘 수 없습니다. @@ -964,7 +964,7 @@ 이 연결이 제거되기 전에 풀에서 활성화되어 있는 최소 시간(초)입니다. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + 레거시 유휴 시간 제한 동작을 사용하지 않도록 설정한 경우, 연결이 풀에서 사용되지 않은 상태로 유지될 수 있는 최대 시간(초)입니다. 이 시간이 지나면 연결이 삭제됩니다. 값이 0이면 유휴 만료가 사용되지 않습니다. 풀에서 허용되는 최대 연결 수입니다. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx index ff7594a995..10b748e844 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx @@ -607,7 +607,7 @@ Nie można odnaleźć dostawcy uwierzytelniania dla elementu „{0}”. Zainstaluj pakiet NuGet „Microsoft.Data.SqlClient.Extensions.Azure” (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure), aby korzystać z metod uwierzytelniania usługi Active Directory (Entra ID). - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Ustawienie „useWamBroker” wymaga pakietu „Microsoft.Data.SqlClient.Extensions.Azure”, aby uwidoczniono element „Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions”. Uaktualnij pakiet „Microsoft.Data.SqlClient.Extensions.Azure” do wersji co najmniej 1.1.0, która zawiera ten typ. Parametr „{0}” nie może mieć wartości null ani być pusty. @@ -964,7 +964,7 @@ Minimalny czas (w sekundach) przechowywania tego połączenia w puli przed jego usunięciem. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Maksymalny czas (w sekundach), przez jaki połączenie może znajdować się w stanie nieużywalnym (bezczynnym) w puli, zanim zostanie odrzucone po wyłączeniu starszego zachowania limitu czasu bezczynności. Wartość 0 powoduje wyłączenie bezczynności. Maksymalna liczba połączeń dozwolonych w puli. 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 aceb8af554..5fdcafdde2 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx @@ -607,7 +607,7 @@ Não é possível localizar um provedor de autenticação para '{0}'. Instale o pacote NuGet 'Microsoft.Data.SqlClient.Extensions.Azure' (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) para usar métodos de autenticação do Active Directory (Entra ID). - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + A definição ''useWamBroker'' exige que o pacote ''Microsoft.Data.SqlClient.Extensions.Azure'' exponha ''Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions''. Atualize o pacote ''Microsoft.Data.SqlClient.Extensions.Azure'' para a versão v1.1.0 ou posterior, que inclui esse tipo. O parâmetro '{0}' não pode ser nulo ou vazio. @@ -964,7 +964,7 @@ O tempo mínimo (em segundos) para que essa conexão exista no pool antes de ser destruída. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + A quantidade máxima de tempo (em segundos) que uma conexão pode ficar sem uso (ociosa) no pool antes de ser descartada quando o comportamento herdado de tempo limite ocioso está desabilitado. Um valor de 0 desabilita a expiração por inatividade. O número máximo de conexões permitidas no pool. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx index e6bd7e21ed..33bcd135f4 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx @@ -607,7 +607,7 @@ Не удается найти поставщик проверки подлинности для "{0}". Установите пакет NuGet "Microsoft.Data.SqlClient.Extensions.Azure" (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) для использования методов проверки подлинности Active Directory (Entra ID). - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + Для установки параметра "useWamBroker" требуется, чтобы пакет "Microsoft.Data.SqlClient.Extensions.Azure" предоставлял доступ к "Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions". Обновите пакет "Microsoft.Data.SqlClient.Extensions.Azure" как минимум до версии v1.1.0, в которую входит этот тип. Параметр «{0}» не может иметь значение NULL или быть пустым. @@ -964,7 +964,7 @@ Минимальное время (в секундах), которое это подключение будет оставаться в пуле до уничтожения. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Максимальное время (в секундах), в течение которого соединение может оставаться неактивным (в режиме ожидания) в пуле, прежде чем оно будет отклонено при отключенном устаревшем поведении времени ожидания. Значение "0" отключает окончание срока действия неактивности. Максимально допустимое число подключений в пуле. diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx index bee4c2db2d..e0ace1a9a2 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx @@ -607,7 +607,7 @@ '{0}' için kimlik doğrulaması sağlayıcısı bulunamıyor. Active Directory (Entra ID) kimlik doğrulaması yöntemlerini kullanmak için ‘Microsoft.Data.SqlClient.Extensions.Azure’ NuGet paketini (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure) yükleyin. - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + 'useWamBroker' ayarı, 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions' türünü kullanıma sunmak için 'Microsoft.Data.SqlClient.Extensions.Azure' paketini gerektirir. Bu türü içeren en az v1.1.0 sürümüne ulaşmak için 'Microsoft.Data.SqlClient.Extensions.Azure' paketini yükseltin. '{0}' parametresi null veya boş olamaz. @@ -964,7 +964,7 @@ Bu bağlantının, yok edilmeden önce havuzda en az ne süreyle (saniye olarak) etkin kalacağı. - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + Eski boşta kalma zaman aşımı davranışı devre dışı bırakıldığında, bir bağlantının havuzda kullanılmadan boşta kalabileceği ve atılmadan önce geçebileceği en fazla süreyi saniye cinsinden belirtir. 0 değeri, boşta kalma süresi sonunu devre dışı bırakır. Havuzda izin verilen en çok bağlantı sayısı. 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 5fbca4eeb0..7a3776e539 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx @@ -607,7 +607,7 @@ 找不到 ‘{0}’ 的身份验证提供程序。安装 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 包(https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure)以使用 Active Directory (Entra ID)身份验证方法。 - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + 要设置 "useWamBroker",需要 "Microsoft.Data.SqlClient.Extensions.Azure" 包公开 "Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions"。将 "Microsoft.Data.SqlClient.Extensions.Azure" 包升级到至少 v1.1.0,该版本包含此类型。 参数“{0}”不能为 null 或为空。 @@ -964,7 +964,7 @@ 此连接在池中存活直至被销毁的最短时间(以秒为单位)。 - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + 在禁用传统空闲超时行为时,连接在池中保持未使用(空闲)状态的最长时间(以秒为单位),之后将被丢弃。值为 0 表示禁用空闲过期。 池中所允许连接的最大数目。 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 aea00e873a..454cc34d36 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx @@ -607,7 +607,7 @@ 找不到 '{0}' 的驗證提供者。請安裝 'Microsoft.Data.SqlClient.Extensions.Azure' NuGet 套件 (https://www.nuget.org/packages/Microsoft.Data.SqlClient.Extensions.Azure),以使用 Active Directory (Entra ID) 認證方法。 - Setting 'useWamBroker' requires the 'Microsoft.Data.SqlClient.Extensions.Azure' package to expose 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'. Upgrade the 'Microsoft.Data.SqlClient.Extensions.Azure' package to at least v1.1.0 that includes this type. + 設定 'useWamBroker' 需要 'Microsoft.Data.SqlClient.Extensions.Azure' 套件以公開 'Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProviderOptions'。請將 'Microsoft.Data.SqlClient.Extensions.Azure' 套件升級至至少 v1.1.0,其中包含此類型。 參數 '{0}' 不能是 Null 或空白。 @@ -964,7 +964,7 @@ 集區中的連接在被終結前的存在時間下限 (單位為秒)。 - The maximum amount of time (in seconds) a connection can sit unused (idle) in the pool before it is discarded when legacy idle-timeout behavior is disabled. A value of 0 disables idle expiration. + 停用舊版閒置逾時行為時,連線在集區中可處於未使用 (閒置) 狀態的最長時間 (以秒為單位),超過後就會遭到捨棄。值為 0 時會停用閒置到期。 集區中允許的連接上限。 From 90dda56c362dee1f3c091471c7d6199f3af80cba Mon Sep 17 00:00:00 2001 From: priyankatiwari08 Date: Thu, 9 Jul 2026 02:01:15 +0530 Subject: [PATCH 179/298] Pin Microsoft.SqlServer.Server package version to 1.0.0 (#4434) --- eng/pipelines/onebranch/variables/package-variables.yml | 5 +---- src/Microsoft.SqlServer.Server/Versions.props | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/eng/pipelines/onebranch/variables/package-variables.yml b/eng/pipelines/onebranch/variables/package-variables.yml index 5a7d456df8..7ddcb0ecc6 100644 --- a/eng/pipelines/onebranch/variables/package-variables.yml +++ b/eng/pipelines/onebranch/variables/package-variables.yml @@ -131,7 +131,4 @@ variables: value: '1.0.0.$(_assemblyBuildNumber)' - name: sqlServerPackageVersion - ${{ if parameters.isPreview }}: - value: '1.0.0-preview1.$(Build.BuildNumber)' - ${{ else }}: - value: '1.0.0' + value: '1.0.0' diff --git a/src/Microsoft.SqlServer.Server/Versions.props b/src/Microsoft.SqlServer.Server/Versions.props index 3615bd95a8..2a35868c7d 100644 --- a/src/Microsoft.SqlServer.Server/Versions.props +++ b/src/Microsoft.SqlServer.Server/Versions.props @@ -30,7 +30,7 @@ decided. @TODO: Determine how versions should be updated in release branches vs main --> - 1.1.0-preview1 + 1.0.0 From 52ed94b13600c0f71c5a2572107059d7fb2f81c1 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Thu, 9 Jul 2026 13:18:57 -0700 Subject: [PATCH 180/298] Fix paths. (#4436) --- eng/pipelines/onebranch/stages/release-stages.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eng/pipelines/onebranch/stages/release-stages.yml b/eng/pipelines/onebranch/stages/release-stages.yml index 45309a77c6..15d0cba8dd 100644 --- a/eng/pipelines/onebranch/stages/release-stages.yml +++ b/eng/pipelines/onebranch/stages/release-stages.yml @@ -193,7 +193,7 @@ stages: parameters: packageName: Microsoft.SqlServer.Server artifactName: '${{ parameters.sqlServerArtifactsName }}' - packagePath: 'Package-Release/Microsoft.SqlServer.Server.${{ parameters.sqlServerPackageVersion }}.nupkg' + packagePath: 'Release/Microsoft.SqlServer.Server.${{ parameters.sqlServerPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -203,7 +203,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Internal.Logging artifactName: '${{ parameters.loggingArtifactsName }}' - packagePath: 'Package-Release/Microsoft.Data.SqlClient.Internal.Logging.${{ parameters.loggingPackageVersion }}.nupkg' + packagePath: 'Release/Microsoft.Data.SqlClient.Internal.Logging.${{ parameters.loggingPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -213,7 +213,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Extensions.Abstractions artifactName: '${{ parameters.abstractionsArtifactsName }}' - packagePath: 'Package-Release/Microsoft.Data.SqlClient.Extensions.Abstractions.${{ parameters.abstractionsPackageVersion }}.nupkg' + packagePath: 'Release/Microsoft.Data.SqlClient.Extensions.Abstractions.${{ parameters.abstractionsPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -233,7 +233,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.Extensions.Azure artifactName: '${{ parameters.azureArtifactsName }}' - packagePath: 'Package-Release/Microsoft.Data.SqlClient.Extensions.Azure.${{ parameters.azurePackageVersion }}.nupkg' + packagePath: 'Release/Microsoft.Data.SqlClient.Extensions.Azure.${{ parameters.azurePackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} @@ -243,7 +243,7 @@ stages: parameters: packageName: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider artifactName: '${{ parameters.akvProviderArtifactsName }}' - packagePath: 'Package-Release/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.${{ parameters.akvProviderPackageVersion }}.nupkg' + packagePath: 'Release/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.${{ parameters.akvProviderPackageVersion }}.nupkg' nugetServiceConnection: ${{ variables.nugetServiceConnection }} isProduction: ${{ parameters.isOfficial }} displaySuffix: ${{ variables.nugetTargetSuffix }} From d0b276463bca93f9a0fce76f4df024b68f8379da Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:36:05 +0100 Subject: [PATCH 181/298] Allow writing DateOnly instance as a Variant parameter (#4294) --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 18 ++++ .../SQL/ParameterTest/DateTimeVariantTests.cs | 98 ++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) 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 d53bf6f827..8a00c41743 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -7801,6 +7801,12 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars WriteDateTimeOffset((DateTimeOffset)value, mt.Scale, length, stateObj); break; + #if NET + case TdsEnums.SQLDATE: + WriteDate((DateOnly)value, stateObj); + break; + #endif + default: Debug.Fail("unknown tds type for sqlvariant!"); break; @@ -7972,6 +7978,13 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta WriteDateTimeOffset((DateTimeOffset)value, metatype.Scale, 10, stateObj); break; + #if NET + case TdsEnums.SQLDATE: + WriteSqlVariantHeader(5, metatype.TDSType, metatype.PropBytes, stateObj); + WriteDate((DateOnly)value, stateObj); + break; + #endif + default: Debug.Fail("unknown tds type for sqlvariant!"); break; @@ -8145,6 +8158,11 @@ private void WriteDate(DateTime value, TdsParserStateObject stateObj) WritePartialLong(days, 3, stateObj); } +#if NET + private void WriteDate(DateOnly value, TdsParserStateObject stateObj) => + WritePartialLong(value.DayNumber, 3, stateObj); +#endif + private byte[] SerializeTime(TimeSpan value, byte scale, int length) { if (0 > value.Ticks || value.Ticks >= TimeSpan.TicksPerDay) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs index 05a01788c3..3d84e0ff2c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/DateTimeVariantTests.cs @@ -407,6 +407,12 @@ public void TestSqlDataReader_TVP_Type( { value = ((TimeSpan)paramValue).ToString(); } + #if NET + else if (paramValue.GetType() == typeof(DateOnly)) + { + value = ((DateOnly)paramValue).ToString(); + } + #endif else { value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; @@ -476,6 +482,12 @@ public void TestSqlDataReader_TVP_Variant( { value = ((TimeSpan)paramValue).ToString(); } + #if NET + else if (paramValue.GetType() == typeof(DateOnly)) + { + value = ((DateOnly)paramValue).ToString(); + } + #endif else { value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; @@ -542,6 +554,12 @@ public void TestSimpleDataReader_Type( { value = ((TimeSpan)paramValue).ToString(); } + #if NET + else if (paramValue.GetType() == typeof(DateOnly)) + { + value = ((DateOnly)paramValue).ToString(); + } + #endif else { value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; @@ -593,6 +611,12 @@ public void TestSimpleDataReader_Variant( { value = ((TimeSpan)paramValue).ToString(); } + #if NET + else if (paramValue.GetType() == typeof(DateOnly)) + { + value = ((DateOnly)paramValue).ToString(); + } + #endif else { value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; @@ -645,6 +669,12 @@ public void SqlBulkCopySqlDataReader_Type( { value = ((TimeSpan)paramValue).ToString(); } + #if NET + else if (paramValue.GetType() == typeof(DateOnly)) + { + value = ((DateOnly)paramValue).ToString(); + } + #endif else { value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; @@ -712,6 +742,12 @@ public void SqlBulkCopySqlDataReader_Variant( { value = ((TimeSpan)paramValue).ToString(); } + #if NET + else if (paramValue.GetType() == typeof(DateOnly)) + { + value = ((DateOnly)paramValue).ToString(); + } + #endif else { value = ((DateTime)paramValue).ToString("M/d/yyyy") + " " + ((DateTime)paramValue).TimeOfDay; @@ -949,6 +985,66 @@ public void SqlBulkCopyDataRow_Variant( /// public static IEnumerable GetParameterCombinations() { + #if NET + yield return new object[] { DateOnly.MinValue, "date", + new Dictionary { + { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary() + { + { TestVariations.TestSimpleParameter_Type, new DateTime(0) }, + { TestVariations.TestSimpleParameter_Variant, new DateTime(0) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, new DateTime(0) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, new DateTime(0) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, new DateTime(0) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, new DateTime(0) }, + { TestVariations.TestSqlDataReader_TVP_Type, new DateTime(0) }, + { TestVariations.TestSqlDataReader_TVP_Variant, new DateTime(0) }, + { TestVariations.TestSimpleDataReader_Type, new DateTime(0) }, + { TestVariations.TestSimpleDataReader_Variant, new DateTime(0) }, + { TestVariations.SqlBulkCopySqlDataReader_Type, new DateTime(0) }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, new DateTime(0) }, + { TestVariations.SqlBulkCopyDataTable_Type, new DateTime(0) }, + { TestVariations.SqlBulkCopyDataTable_Variant, new DateTime(0) }, + { TestVariations.SqlBulkCopyDataRow_Type, new DateTime(0) }, + { TestVariations.SqlBulkCopyDataRow_Variant, new DateTime(0) } + }, + new Dictionary()}; + yield return new object[] { DateOnly.MaxValue, "date", + new Dictionary { + { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataTable_Variant, SqlDateTimeOverflow }, + { TestVariations.SqlBulkCopyDataRow_Variant, SqlDateTimeOverflow }}, + new Dictionary() + { + { TestVariations.TestSimpleParameter_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSimpleParameter_Variant, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataRecordParameterToTVP_Variant, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataReaderParameterToTVP_Variant, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataReader_TVP_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSqlDataReader_TVP_Variant, new DateTime(3155378112000000000) }, + { TestVariations.TestSimpleDataReader_Type, new DateTime(3155378112000000000) }, + { TestVariations.TestSimpleDataReader_Variant, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopySqlDataReader_Type, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopySqlDataReader_Variant, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopyDataTable_Type, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopyDataTable_Variant, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopyDataRow_Type, new DateTime(3155378112000000000) }, + { TestVariations.SqlBulkCopyDataRow_Variant, new DateTime(3155378112000000000) } + }, + new Dictionary() + { + {TestVariations.TestSqlDataRecordParameterToTVP_Variant, "datetime"} + } + }; + #endif yield return new object[] { DateTime.MinValue, "date", new Dictionary { { TestVariations.TestSimpleParameter_Variant, SqlDateTimeOverflow }, @@ -1070,7 +1166,7 @@ public static IEnumerable GetParameterCombinations() new Dictionary(), new Dictionary(), new Dictionary()}; - yield return new object[] { DateTimeOffset.Parse("12/31/1999 23:59:59.9999999 -08:30"), "datetimeoffset", + yield return new object[] { DateTimeOffset.Parse("1999-12-31 23:59:59.9999999 -08:30"), "datetimeoffset", new Dictionary(), new Dictionary(), new Dictionary()}; From 6c1c8e05a87cb973ce1e12ee3f4d6e41b8c8d1b9 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 13 Jul 2026 07:56:36 -0300 Subject: [PATCH 182/298] Replace dead #if _WINDOWS test guards with runtime OS checks (#4423) --- .../Common/LocalAppContextSwitchesHelper.cs | 42 ++++++++++++++----- .../SqlClient/LocalAppContextSwitchesTest.cs | 23 +++++++--- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 5c07d37a32..49ad2712ec 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Reflection; +#if NET +using System.Runtime.InteropServices; +#endif namespace Microsoft.Data.SqlClient.Tests.Common; @@ -56,9 +59,12 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable private readonly bool? _useConnectionPoolV2Original; private readonly bool? _useLegacyIdleTimeoutBehaviorOriginal; private readonly bool? _useOverallConnectTimeoutForPoolWaitOriginal; - #if NET && _WINDOWS + #if NET + // The s_useManagedNetworking field only exists in the SqlClient assembly + // when it is built for .NET on Windows, so it is captured/restored at + // runtime only when running on Windows. See UseManagedNetworking below. private readonly bool? _useManagedNetworkingOriginal; - #endif + #endif private readonly bool? _useMinimumLoginTimeoutOriginal; #endregion @@ -121,9 +127,12 @@ public LocalAppContextSwitchesHelper() GetSwitchValue("s_useLegacyIdleTimeoutBehavior"); _useOverallConnectTimeoutForPoolWaitOriginal = GetSwitchValue("s_useOverallConnectTimeoutForPoolWait"); - #if NET && _WINDOWS - _useManagedNetworkingOriginal = - GetSwitchValue("s_useManagedNetworking"); + #if NET + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _useManagedNetworkingOriginal = + GetSwitchValue("s_useManagedNetworking"); + } #endif _useMinimumLoginTimeoutOriginal = GetSwitchValue("s_useMinimumLoginTimeout"); @@ -194,10 +203,13 @@ public void Dispose() SetSwitchValue( "s_useOverallConnectTimeoutForPoolWait", _useOverallConnectTimeoutForPoolWaitOriginal); - #if NET && _WINDOWS - SetSwitchValue( - "s_useManagedNetworking", - _useManagedNetworkingOriginal); + #if NET + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + SetSwitchValue( + "s_useManagedNetworking", + _useManagedNetworkingOriginal); + } #endif SetSwitchValue( "s_useMinimumLoginTimeout", @@ -358,10 +370,20 @@ public bool? UseOverallConnectTimeoutForPoolWait set => SetSwitchValue("s_useOverallConnectTimeoutForPoolWait", value); } - #if NET && _WINDOWS + #if NET /// /// Get or set the UseManagedNetworking switch value. /// + /// + /// The underlying s_useManagedNetworking field only exists in the SqlClient + /// assembly when it is built for .NET on Windows. The getter reads the + /// public LocalAppContextSwitches.UseManagedNetworking property, which + /// exists on all platforms and is safe to read anywhere. Only the setter + /// relies on the s_useManagedNetworking field, so callers must set this + /// property only when running on Windows (see + /// RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); otherwise the + /// reflection lookup of the field will fail. + /// public bool? UseManagedNetworking { get => GetSwitchPropertyValue(nameof(UseManagedNetworking)); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index e2007280a9..c159389bf7 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using Microsoft.Data.SqlClient.Tests.Common; +#if NET +using System.Runtime.InteropServices; +#endif using Xunit; namespace Microsoft.Data.SqlClient.UnitTests; @@ -42,9 +45,10 @@ public void TestDefaultAppContextSwitchValues() switchesHelper.UseMinimumLoginTimeout = null; #if NET switchesHelper.GlobalizationInvariantMode = null; - #endif - #if NET && _WINDOWS - switchesHelper.UseManagedNetworking = null; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + switchesHelper.UseManagedNetworking = null; + } #endif #if NETFRAMEWORK switchesHelper.DisableTnirByDefault = null; @@ -66,9 +70,16 @@ public void TestDefaultAppContextSwitchValues() Assert.False(switchesHelper.EnableMultiSubnetFailoverByDefault); #if NET Assert.False(switchesHelper.GlobalizationInvariantMode); - #endif - #if NET && _WINDOWS - Assert.False(switchesHelper.UseManagedNetworking); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.False(switchesHelper.UseManagedNetworking); + } + else + { + // On .NET Unix, native SNI is unavailable, so UseManagedNetworking + // is a constant true. + Assert.True(switchesHelper.UseManagedNetworking); + } #endif #if NETFRAMEWORK Assert.False(switchesHelper.DisableTnirByDefault); From ca469d52ef965a0aa223706854f507a3fc20cd7e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:00:06 +0100 Subject: [PATCH 183/298] Cleanup | Consolidate connection capability metadata (#3862) Co-authored-by: Malcolm Daigle --- .../src/Microsoft/Data/Common/AdapterUtil.cs | 11 + .../Data/ProviderBase/DbConnectionClosed.cs | 2 + .../Data/ProviderBase/DbConnectionInternal.cs | 2 + .../Connection/ConnectionCapabilities.cs | 221 ++++++++++++++++++ .../Connection/SqlConnectionInternal.cs | 142 ++++------- .../Data/SqlClient/SqlConnectionFactory.cs | 4 +- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 2 +- .../Data/SqlClient/SqlMetaDataFactory.cs | 7 +- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 55 +---- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 136 ++++------- .../Data/SqlClient/TdsParserHelperClasses.cs | 8 - .../ChannelDbConnectionPoolTest.cs | 2 + .../ConnectionPool/ConnectionPoolSlotsTest.cs | 4 +- .../IdleConnectionChannelTest.cs | 3 + .../TransactedConnectionPoolTest.cs | 2 + ...itHandleDbConnectionPoolTransactionTest.cs | 2 + .../Microsoft/Data/Common/AdapterUtilTest.cs | 156 +++++++++++++ 17 files changed, 512 insertions(+), 247 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/AdapterUtilTest.cs 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 e8ec7c9e0e..6f1e31f47f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -466,6 +466,17 @@ internal static void ValidateCommandBehavior(CommandBehavior value) } } + internal static void ValidateTdsVersion(uint tdsVersion) + { + if (tdsVersion is not TdsEnums.SQL2005_VERSION + and not TdsEnums.SQL2008_VERSION + and not TdsEnums.TDS7X_VERSION + and not TdsEnums.TDS80_VERSION) + { + throw SQL.InvalidTDSVersion(); + } + } + internal static ArgumentOutOfRangeException InvalidUserDefinedTypeSerializationFormat(Format value) { #if DEBUG 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 37782dddf3..cc135a5714 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -22,6 +22,8 @@ protected DbConnectionClosed(ConnectionState state, bool hidePassword, bool allo public override string ServerVersion => throw ADP.ClosedConnectionError(); + public override ConnectionCapabilities Capabilities => throw ADP.ClosedConnectionError(); + protected override void Activate(System.Transactions.Transaction transaction) => throw ADP.ClosedConnectionError(); public override DbTransaction BeginTransaction(IsolationLevel il) => throw ADP.ClosedConnectionError(); 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 907e491949..7e0ef67b94 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -181,6 +181,8 @@ internal bool IsInPool public abstract string ServerVersion { get; } + public virtual ConnectionCapabilities Capabilities => null; + // this should be abstract but until it is added to all the providers virtual will have to do RickFe public virtual string ServerVersionNormalized { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs new file mode 100644 index 0000000000..ebc2ff6bbc --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/ConnectionCapabilities.cs @@ -0,0 +1,221 @@ +// 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.Internal; +using System; +using System.Text; + +#nullable enable + +namespace Microsoft.Data.SqlClient; + +/// +/// Describes the capabilities and related information (such as the +/// reported server version and TDS version) of the connection. +/// +internal sealed class ConnectionCapabilities +{ + /// + /// The TDS version reported by the LoginAck response + /// from the server. + /// + /// + /// is negotiated for SQL Server 2005. + /// is negotiated for SQL Server 2008. + /// is negotiated for SQL Server 2012 and upwards using TDS 7.x. + /// is negotiated for the TDS 8.0 flow in SQL Server 2022 and upwards. + /// + public uint TdsVersion { get; set; } + + /// + /// The SQL Server major version reported by the LoginAck + /// response from the server. + /// + public byte ServerMajorVersion { get; set; } + + /// + /// The SQL Server minor version reported by the LoginAck + /// response from the server. + /// + public byte ServerMinorVersion { get; set; } + + /// + /// The SQL Server build number reported by the LoginAck + /// response from the server. + /// + public ushort ServerBuildNumber { get; set; } + + /// + /// The user-friendly SQL Server version reported by the + /// LoginAck response from the server. + /// + public string ServerVersion => + $"{ServerMajorVersion:00}.{ServerMinorVersion:00}.{ServerBuildNumber:0000}"; + + /// + /// If true (as determined by the value of ) + /// then the connection is to SQL Server 2008 R2 or newer. + /// + public bool Is2008R2OrNewer => + Is2012OrNewer || TdsVersion is TdsEnums.SQL2008_VERSION; + + /// + /// If true (as determined by the value of ) + /// then the connection is to SQL Server 2012 or newer. + /// + public bool Is2012OrNewer => + TdsVersion is TdsEnums.TDS7X_VERSION or TdsEnums.TDS80_VERSION; + + /// + /// If true, this connection is to an Azure SQL instance. This is determined + /// by the receipt of a FEATUREEXTACK token of value 0x08. + /// + public bool IsAzureSql { get; set; } + + /// + /// Indicates support for user-defined CLR types (up to a length of 8000 + /// bytes.) This was introduced in SQL Server 2005. + /// + public bool UserDefinedTypes => true; + + /// + /// Indicates support for the xml data type. This was introduced + /// in SQL Server 2005. + /// + public bool XmlDataType => true; + + /// + /// Indicates support for the date, time, datetime2 + /// and datetimeoffset data types. These were introduced in SQL + /// Server 2008. + /// + public bool ExpandedDateTimeDataTypes => Is2008R2OrNewer; + + /// + /// Indicates support for user-defined CLR types of any length. This + /// was introduced in SQL Server 2008. + /// + public bool LargeUserDefinedTypes => Is2008R2OrNewer; + + /// + /// Indicates support for the client to include a TDS trace header, + /// which is surfaced in XEvents traces to correlate events between + /// the client and the server. This was introduced in SQL Server 2012. + /// + public bool TraceHeader => Is2012OrNewer; + + /// + /// Indicates support for UTF8 collations. This was introduced in SQL + /// Server 2019, and is only available if a FEATUREEXTACK token of value + /// 0x0A is received. + /// + public bool Utf8 { get; set; } + + /// + /// Indicates support for the client to cache DNS resolution responses for + /// the server. This is only supported by Azure SQL, and is only available + /// if a FEATUREEXTACK token of value 0x0B is received. + /// + public bool DnsCaching { get; set; } + + /// + /// Indicates support for Data Classification and specifies the version of + /// Data Classification which is supported. This was introduced in SQL + /// Server 2019, and is only available if a FEATUREEXTACK token of value + /// 0x09 is received. + /// + /// + /// This should only be 1 or 2. + /// + public byte DataClassificationVersion { get; set; } + + /// + /// Indicates that Global Transactions are available (even if not currently enabled.) + /// Global Transactions are only supported by Azure SQL, and are only available if a + /// FEATUREEXTACK token of value 0x05 is received. + /// + public bool GlobalTransactionsAvailable { get; set; } + + /// + /// Indicates support for Global Transactions. This is only supported by + /// Azure SQL, and is only available if a FEATUREEXTACK token of value + /// 0x05 is received. + /// + public bool GlobalTransactionsSupported { get; set; } + + /// + /// Indicates support for Enhanced Routing. This is only supported by + /// Azure SQL, and is only available if a FEATUREEXTACK token of value + /// 0x0F is received. + /// + public bool EnhancedRouting { get; set; } + + /// + /// Indicates support for connecting to the current connection's failover + /// partner with an Application Intent of ReadOnly. This is only supported + /// by Azure SQL, and is only available if a FEATUREEXTACK token of value + /// 0x08 is received, and if bit zero of this token's data is set. + /// + public bool ReadOnlyFailoverPartnerConnection { get; set; } + + /// + /// Indicates support for the vector data type, with a backing type + /// of float32. This was introduced in SQL Server 2022, and is only + /// available if a FEATUREEXTACK token of value 0x0E is received, and + /// if the version in this token's data is greater than or equal to 1. + /// + public bool Float32VectorType { get; set; } + + /// + /// Indicates support for the json data type. This was introduced in + /// SQL Server 2022, and is only available if a FEATUREEXTACK token of value + /// 0x0D is received, and if the version in this token's data is + /// greater than or equal to 1. + /// + public bool JsonType { get; set; } + + /// + /// Indicates support for column encryption and specifies the version of column + /// encryption which is supported. This was introduced in SQL Server 2016, and is + /// only available if a FEATUREEXTACK token of value 0x04 is received. + /// + /// + /// This should only be 1, 2 or 3. v1 is supported from SQL + /// Server 2016 upwards, v2 is supported from SQL Server 2019 upwards, v3 is supported + /// from SQL Server 2022 upwards. + /// + public byte ColumnEncryptionVersion { get; set; } + + /// + /// If column encryption is enabled, the type of enclave reported by the server. This + /// was introduced in SQL Server 2019, and is only available if a FEATUREEXTACK token + /// of value 0x04 is received, and the resultant + /// is 2 or 3. + /// + public string? ColumnEncryptionEnclaveType { get; set; } + + /// + /// Returns the capability records to unset values. + /// + public void Reset() + { + TdsVersion = 0; + ServerMajorVersion = 0; + ServerMinorVersion = 0; + ServerBuildNumber = 0; + + IsAzureSql = false; + Utf8 = false; + DnsCaching = false; + DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED; + GlobalTransactionsAvailable = false; + GlobalTransactionsSupported = false; + EnhancedRouting = false; + ReadOnlyFailoverPartnerConnection = false; + Float32VectorType = false; + JsonType = false; + ColumnEncryptionVersion = TdsEnums.TCE_NOT_ENABLED; + ColumnEncryptionEnclaveType = null; + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index a70ab35360..fd8817b9aa 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -179,23 +179,10 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Should be private and accessed via internal property internal bool _federatedAuthenticationRequested; - /// - /// Flag indicating whether JSON objects are supported by the server. - /// - // @TODO: Should be private and accessed via internal property - internal bool IsJsonSupportEnabled = false; - - /// - /// Flag indicating whether vector objects are supported by the server. - /// - // @TODO: Should be private and accessed via internal property - internal bool IsVectorSupportEnabled = false; - /// /// Flag indicating whether enhanced routing is supported by the server. /// - // @TODO: Should be private and accessed via internal property - internal bool IsEnhancedRoutingSupportEnabled = false; + internal bool IsEnhancedRoutingSupportEnabled => Capabilities.EnhancedRouting; // @TODO: This should be private internal readonly SyncAsyncLock _parserLock = new SyncAsyncLock(); @@ -206,12 +193,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Should be private and accessed via internal property internal readonly SspiContextProvider _sspiContextProvider; - /// - /// TCE flags supported by the server. - /// - // @TODO: Should be private and accessed via internal property - internal byte _tceVersionSupported; - private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper; /// @@ -252,8 +233,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable private string _instanceName = string.Empty; - private SqlLoginAck _loginAck; - /// /// 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 @@ -288,9 +267,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Rename to match naming conventions private bool _SQLDNSRetryEnabled = false; - // @TODO: Rename to match naming conventions - private bool _serverSupportsDNSCaching = false; - private bool _sessionRecoveryRequested; private int _threadIdOwningParserLock = -1; @@ -459,10 +435,11 @@ internal SqlConnectionInternal( #region Properties // @TODO: Make internal - public override string ServerVersion - { - get => $"{_loginAck.majorVersion:00}.{(short)_loginAck.minorVersion:00}.{_loginAck.buildNum:0000}"; - } + public override string ServerVersion => + Capabilities.ServerVersion; + + public override ConnectionCapabilities Capabilities => + _parser.Capabilities; /// /// Gets the collection of async call contexts that belong to this connection. @@ -598,16 +575,15 @@ internal bool IsDNSCachingBeforeRedirectSupported internal bool IsEnlistedInTransaction { get; private set; } /// - /// Whether this is a Global Transaction (Non-MSDTC, Azure SQL DB Transaction) - /// TODO: overlaps with IsGlobalTransactionsEnabledForServer, need to consolidate to avoid bugs + /// Whether the server is capable of supporting a Global Transaction (Non-MSDTC, Azure SQL DB Transaction) /// - internal bool IsGlobalTransaction { get; private set; } + internal bool IsGlobalTransaction => Capabilities.GlobalTransactionsAvailable; /// /// Whether Global Transactions are enabled. Only supported by Azure SQL. False if disabled /// or connected to on-prem SQL Server. /// - internal bool IsGlobalTransactionsEnabledForServer { get; private set; } + internal bool IsGlobalTransactionsEnabledForServer => Capabilities.GlobalTransactionsSupported; /// /// Whether this connection is locked for bulk copy operations. @@ -621,11 +597,7 @@ internal bool IsLockedForBulkCopy /// Get or set if SQLDNSCaching is supported by the server. /// // @TODO: Make auto-property - internal bool IsSQLDNSCachingSupported - { - get => _serverSupportsDNSCaching; - set => _serverSupportsDNSCaching = value; - } + internal bool IsSQLDNSCachingSupported => Capabilities.DnsCaching; /// /// Get or set if we need retrying with IP received from FeatureExtAck. @@ -761,31 +733,8 @@ private SqlInternalTransaction AvailableInternalTransaction get => _parser._fResetConnection ? null : CurrentTransaction; } - /// - /// Whether this connection is to an Azure SQL Database. - /// - // @TODO: Make private field. - private bool IsAzureSqlConnection { get; set; } - internal TimeoutTimer Timeout => _timeout; - /// - /// Selects the that governs the login phase based on - /// . - /// - /// - /// When the switch is enabled the caller-supplied - /// is returned as-is so any time already consumed (e.g., waiting for the pool) counts - /// against the overall ConnectTimeout. When disabled, a fresh timer is started from - /// , preserving legacy behavior. Extracted so - /// this branch can be unit-tested without standing up a real connection. - /// - internal static TimeoutTimer ResolveLoginTimeout(TimeoutTimer callerTimeout, int connectTimeoutSeconds) - { - return LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait - ? callerTimeout - : TimeoutTimer.StartNew(TimeSpan.FromSeconds(connectTimeoutSeconds)); - } #endregion #region Public and Internal Methods @@ -988,8 +937,6 @@ public override void Dispose() finally { // Close will always close, even if exception is thrown. - // Remember to null out any object references. - _loginAck = null; // Mark internal connection as closed _fConnectionOpen = false; @@ -1387,12 +1334,8 @@ internal void OnFeatureExtAck(int featureId, byte[] data) throw SQL.ParsingError(); } - IsGlobalTransaction = true; - if (data[0] == 0x01) - { - IsGlobalTransactionsEnabledForServer = true; - } - + Capabilities.GlobalTransactionsAvailable = true; + Capabilities.GlobalTransactionsSupported = (data[0] == 0x01); break; } @@ -1520,19 +1463,12 @@ internal void OnFeatureExtAck(int featureId, byte[] data) throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidVersion, supportedTceVersion); } - _tceVersionSupported = supportedTceVersion; - - Debug.Assert(_tceVersionSupported <= TdsEnums.MAX_SUPPORTED_TCE_VERSION, - "Client support TCE version 2"); - - _parser.IsColumnEncryptionSupported = true; - _parser.TceVersionSupported = _tceVersionSupported; - _parser.AreEnclaveRetriesSupported = _tceVersionSupported == 3; + Capabilities.ColumnEncryptionVersion = supportedTceVersion; if (data.Length > 1) { // Extract the type of enclave being used by the server. - _parser.EnclaveType = Encoding.Unicode.GetString(data, 2, data.Length - 2); + Capabilities.ColumnEncryptionEnclaveType = Encoding.Unicode.GetString(data, 2, data.Length - 2); } break; } @@ -1548,11 +1484,11 @@ internal void OnFeatureExtAck(int featureId, byte[] data) throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } - IsAzureSqlConnection = true; - + Capabilities.IsAzureSql = true; // Bit 0 for RO/FP support - // @TODO: Add a constant somewhere for that - if ((data[0] & 1) == 1 && SqlClientEventSource.Log.IsTraceEnabled()) + Capabilities.ReadOnlyFailoverPartnerConnection = (data[0] & 0x01) == 0x01; + + if (Capabilities.ReadOnlyFailoverPartnerConnection && SqlClientEventSource.Log.IsTraceEnabled()) { SqlClientEventSource.Log.TryAdvancedTraceEvent( $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + @@ -1604,7 +1540,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } byte enabled = data[1]; - _parser.DataClassificationVersion = enabled == 0 + Capabilities.DataClassificationVersion = enabled == 0 ? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED : supportedDataClassificationVersion; @@ -1627,6 +1563,9 @@ internal void OnFeatureExtAck(int featureId, byte[] data) throw SQL.ParsingError(); } + // The server can send and receive UTF8-encoded data if bit 0 of the + // feature data is set. + Capabilities.Utf8 = (data[0] & 0x01) == 0x01; break; } case TdsEnums.FEATUREEXT_SQLDNSCACHING: @@ -1648,7 +1587,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data[0] == 1) { - IsSQLDNSCachingSupported = true; + Capabilities.DnsCaching = true; _cleanSQLDNSCaching = false; if (RoutingInfo != null) @@ -1659,7 +1598,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) else { // we receive the IsSupported whose value is 0 - IsSQLDNSCachingSupported = false; + Capabilities.DnsCaching = false; _cleanSQLDNSCaching = true; } @@ -1697,7 +1636,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) throw SQL.ParsingError(); } - IsJsonSupportEnabled = true; + Capabilities.JsonType = true; break; } case TdsEnums.FEATUREEXT_VECTORSUPPORT: @@ -1729,7 +1668,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) throw SQL.ParsingError(); } - IsVectorSupportEnabled = true; + Capabilities.Float32VectorType = true; break; } @@ -1746,7 +1685,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } // A value of 1 indicates that the server supports the feature. - IsEnhancedRoutingSupportEnabled = data[0] == 1; + Capabilities.EnhancedRouting = data[0] == 1; SqlClientEventSource.Log.TryAdvancedTraceEvent( $"SqlInternalConnectionTds.OnFeatureExtAck | ADV | " + @@ -1946,12 +1885,11 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) _parser.SendFedAuthToken(_fedAuthToken); } - internal void OnLoginAck(SqlLoginAck rec) + internal void OnLoginAck() { - _loginAck = rec; if (_recoverySessionData != null) { - if (_recoverySessionData._tdsVersion != rec.tdsVersion) + if (_recoverySessionData._tdsVersion != Capabilities.TdsVersion) { throw SQL.CR_TDSVersionNotPreserved(this); } @@ -1959,9 +1897,27 @@ internal void OnLoginAck(SqlLoginAck rec) if (_currentSessionData != null) { - _currentSessionData._tdsVersion = rec.tdsVersion; + _currentSessionData._tdsVersion = Capabilities.TdsVersion; } } + + /// + /// Selects the that governs the login phase based on + /// . + /// + /// + /// When the switch is enabled the caller-supplied + /// is returned as-is so any time already consumed (e.g., waiting for the pool) counts + /// against the overall ConnectTimeout. When disabled, a fresh timer is started from + /// , preserving legacy behavior. Extracted so + /// this branch can be unit-tested without standing up a real connection. + /// + internal static TimeoutTimer ResolveLoginTimeout(TimeoutTimer callerTimeout, int connectTimeoutSeconds) + { + return LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait + ? callerTimeout + : TimeoutTimer.StartNew(TimeSpan.FromSeconds(connectTimeoutSeconds)); + } internal override bool TryReplaceConnection( DbConnection outerConnection, @@ -3844,7 +3800,7 @@ private void OpenLoginEnlist( timeout); } - if (!IsAzureSqlConnection) + if (!Capabilities.IsAzureSql) { // If not a connection to Azure SQL, Readonly with FailoverPartner is not supported if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 889a9c014c..3aa89f5332 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -753,8 +753,8 @@ private static SqlMetaDataFactory CreateMetaDataFactory(DbConnectionInternal int Stream xmlStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml"); Debug.Assert(xmlStream is not null, $"{nameof(xmlStream)} may not be null."); - - return new SqlMetaDataFactory(xmlStream, internalConnection.ServerVersion); + + return new SqlMetaDataFactory(xmlStream, internalConnection.Capabilities); } private Task CreateReplaceConnectionContinuation( diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 14c8a51b4f..6a0dc09765 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -607,7 +607,7 @@ internal DataTable BuildSchemaTable() if (col.type == SqlDbType.Udt) { // Additional metadata for UDTs. - Debug.Assert(Connection.Is2008OrNewer, "Invalid Column type received from the server"); + Debug.Assert(Connection.Parser.Capabilities.UserDefinedTypes, "Invalid Column type received from the server"); schemaRow[udtAssemblyQualifiedName] = col.udt?.AssemblyQualifiedName; } else if (col.type == SqlDbType.Xml) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs index 841c71d410..bea2fa7e41 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs @@ -40,12 +40,13 @@ internal sealed partial class SqlMetaDataFactory : IDisposable private readonly DataSet _collectionDataSet; private readonly string _serverVersion; - public SqlMetaDataFactory(Stream xmlStream, string serverVersion) + public SqlMetaDataFactory(Stream xmlStream, ConnectionCapabilities connectionCapabilities) { ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); - ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); + ADP.CheckArgumentNull(connectionCapabilities, nameof(connectionCapabilities)); + ADP.CheckArgumentNull(connectionCapabilities.ServerVersion, nameof(connectionCapabilities.ServerVersion)); - _serverVersion = serverVersion; + _serverVersion = connectionCapabilities.ServerVersion; _collectionDataSet = LoadDataSetFromXml(xmlStream); } 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 46f447d582..2590c28690 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -301,56 +301,13 @@ public enum ActiveDirectoryWorkflow : byte public const int TEXT_TIME_STAMP_LEN = 8; public const int COLLATION_INFO_LEN = 4; - /* - public const byte INT4_LSB_HI = 0; // lsb is low byte (e.g. 68000) - // public const byte INT4_LSB_LO = 1; // lsb is low byte (e.g. VAX) - public const byte INT2_LSB_HI = 2; // lsb is low byte (e.g. 68000) - // public const byte INT2_LSB_LO = 3; // lsb is low byte (e.g. VAX) - public const byte FLT_IEEE_HI = 4; // lsb is low byte (e.g. 68000) - public const byte CHAR_ASCII = 6; // ASCII character set - public const byte TWO_I4_LSB_HI = 8; // lsb is low byte (e.g. 68000 - // public const byte TWO_I4_LSB_LO = 9; // lsb is low byte (e.g. VAX) - // public const byte FLT_IEEE_LO = 10; // lsb is low byte (e.g. MSDOS) - public const byte FLT4_IEEE_HI = 12; // IEEE 4-byte floating point -lsb is high byte - // public const byte FLT4_IEEE_LO = 13; // IEEE 4-byte floating point -lsb is low byte - public const byte TWO_I2_LSB_HI = 16; // lsb is high byte - // public const byte TWO_I2_LSB_LO = 17; // lsb is low byte - - public const byte LDEFSQL = 0; // server sends its default - public const byte LDEFUSER = 0; // regular old user - public const byte LINTEGRATED = 8; // integrated security login - */ - - /* Versioning scheme table: - - Client sends: - 0x70000000 -> 7.0 - 0x72xx0002 -> 2005 RTM - - Server responds: - 0x72xx0002 -> 2005 RTM - */ - - // Majors: - // 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 + // Versions to be used on login for SQL Server 2005, SQL Server 2008 R2, TDS 7.x and TDS 8.0 + public const uint SQL2005_VERSION = 0x72_09_0002; + public const uint SQL2008_VERSION = 0x73_0B_0003; + public const uint TDS7X_VERSION = 0x74_00_0004; + public const uint TDS80_VERSION = 0x08_00_0000; public const string TDS8_Protocol = "tds/8.0"; //TDS8 - // Increments: - 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 SQL2005_RTM_MINOR = 0x0002; - public const int SQL2008_MINOR = 0x0003; - public const int SQL2012_MINOR = 0x0004; - public const int TDS8_MINOR = 0x00; - public const int ORDER_68000 = 1; public const int USE_DB_ON = 1; public const int INIT_DB_FATAL = 1; @@ -706,8 +663,10 @@ internal enum FedAuthInfoId : byte internal const int VECTOR_HEADER_SIZE = 8; // TCE Related constants + internal const byte TCE_NOT_ENABLED = 0x00; 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 + internal const byte MIN_TCE_VERSION_WITH_ENCLAVE_RETRY_SUPPORT = 0x03; internal const ushort MAX_TCE_CIPHERINFO_SIZE = 2048; // max size of cipherinfo blob internal const long MAX_TCE_CIPHERTEXT_SIZE = 2147483648; // max size of encrypted blob- currently 2GB. internal const byte CustomCipherAlgorithmId = 0; // Id used for custom encryption algorithm. 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 8a00c41743..1a42894779 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -117,14 +117,6 @@ internal sealed partial class TdsParser internal TdsParserSessionPool _sessionPool = null; // initialized only when we're a MARS parser. - // Version variables - - private bool _is2008 = false; - - private bool _is2012 = false; - - private bool _is2022 = false; - // SqlStatistics private SqlStatistics _statistics = null; @@ -165,25 +157,30 @@ internal sealed partial class TdsParser private static XmlWriterSettings SyncXmlWriterSettings => s_syncXmlWriterSettings ??= new() { CloseOutput = false, ConformanceLevel = ConformanceLevel.Fragment }; + // Capability records + internal ConnectionCapabilities Capabilities { get; } + /// /// Get or set if column encryption is supported by the server. /// - internal bool IsColumnEncryptionSupported { get; set; } = false; + internal bool IsColumnEncryptionSupported => + TceVersionSupported != TdsEnums.TCE_NOT_ENABLED; /// /// TCE version supported by the server /// - internal byte TceVersionSupported { get; set; } + internal byte TceVersionSupported => Capabilities.ColumnEncryptionVersion; /// /// Server supports retrying when the enclave CEKs sent by the client do not match what is needed for the query to run. /// - internal bool AreEnclaveRetriesSupported { get; set; } + internal bool AreEnclaveRetriesSupported => + TceVersionSupported >= TdsEnums.MIN_TCE_VERSION_WITH_ENCLAVE_RETRY_SUPPORT; /// /// Type of enclave being used by the server /// - internal string EnclaveType { get; set; } + internal string EnclaveType => Capabilities.ColumnEncryptionEnclaveType; internal bool isTcpProtocol { get; set; } internal string FQDNforDNSCache { get; set; } @@ -197,7 +194,7 @@ internal sealed partial class TdsParser /// /// Get or set data classification version. A value of 0 means that sensitivity classification is not enabled. /// - internal int DataClassificationVersion { get; set; } + internal int DataClassificationVersion => Capabilities.DataClassificationVersion; private SqlCollation _cachedCollation; @@ -216,8 +213,9 @@ internal TdsParser(bool MARS, bool fAsynchronous) { _fMARS = MARS; // may change during Connect to pre 2005 servers + Capabilities = new(); + _physicalStateObj = TdsParserStateObjectFactory.Singleton.CreateTdsParserStateObject(this); - DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED; } internal SqlConnectionInternal Connection @@ -273,13 +271,7 @@ internal EncryptionOptions EncryptionOptions } } - internal bool Is2008OrNewer - { - get - { - return _is2008; - } - } + internal bool Is2008OrNewer => Capabilities.Is2008R2OrNewer; internal bool MARSOn { @@ -334,13 +326,7 @@ internal SqlStatistics Statistics } } - private bool IncludeTraceHeader - { - get - { - return (_is2012 && SqlClientEventSource.Log.IsEnabled()); - } - } + private bool IncludeTraceHeader => Capabilities.TraceHeader && SqlClientEventSource.Log.IsEnabled(); internal int IncrementNonTransactedOpenResultCount() { @@ -402,8 +388,8 @@ bool withFailover _connHandler = connHandler; _loginWithFailover = withFailover; - // Clean up IsSQLDNSCachingSupported flag from previous status - _connHandler.IsSQLDNSCachingSupported = false; + // Clean up all server capabilities from previous status. + Capabilities.Reset(); uint sniStatus = TdsParserStateObjectFactory.Singleton.SNIStatus; @@ -2764,14 +2750,13 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle case TdsEnums.SQLLOGINACK: { SqlClientEventSource.Log.TryTraceEvent(" Received login acknowledgement token"); - SqlLoginAck ack; - result = TryProcessLoginAck(stateObj, out ack); + result = TryProcessLoginAck(stateObj); if (result != TdsOperationStatus.Done) { return result; } - _connHandler.OnLoginAck(ack); + _connHandler.OnLoginAck(); break; } case TdsEnums.SQLFEATUREEXTACK: @@ -3870,7 +3855,7 @@ private TdsOperationStatus TryProcessFeatureExtAck(TdsParserStateObject stateObj ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCache); } - if (_connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null + if (Capabilities.DnsCaching && _connHandler.pendingSQLDNSObject != null && !SQLFallbackDNSCache.Instance.IsDuplicate(_connHandler.pendingSQLDNSObject)) { ret = SQLFallbackDNSCache.Instance.AddDNSInfo(_connHandler.pendingSQLDNSObject); @@ -4310,12 +4295,8 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj, return TdsOperationStatus.Done; } - private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out SqlLoginAck sqlLoginAck) + private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj) { - SqlLoginAck a = new SqlLoginAck(); - - sqlLoginAck = null; - // read past interface type and version TdsOperationStatus result = stateObj.TrySkipBytes(1); if (result != TdsOperationStatus.Done) @@ -4329,47 +4310,17 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out { return result; } - a.tdsVersion = (uint)((((((b[0] << 8) | b[1]) << 8) | b[2]) << 8) | b[3]); // bytes are in motorola order (high byte first) - uint majorMinor = a.tdsVersion & 0xff00ffff; - uint increment = (a.tdsVersion >> 16) & 0xff; + // When connecting to SQL Server 2000, the TDS version sent + // by the client would be a completely different value to the + // TDS version received by the server. + // From SQL Server 2000 SP1, the TDS version is identical. As + // an artifact of this historical difference, the client sends + // its TDS version to the server in a little-endian layout, and + // receives the server's TDS version in a big-endian layout. + // Reference: MS-TDS, 2.2.7.14, footnote on TDSVersion field. + uint tdsVersion = BinaryPrimitives.ReadUInt32BigEndian(b); - // Server responds: - // 0x72xx0002 -> 2005 RTM - // information provided by S. Ashwin - switch (majorMinor) - { - case TdsEnums.SQL2005_MAJOR << 24 | TdsEnums.SQL2005_RTM_MINOR: // 2005 - if (increment != TdsEnums.SQL2005_INCREMENT) - { - throw SQL.InvalidTDSVersion(); - } - break; - case TdsEnums.SQL2008_MAJOR << 24 | TdsEnums.SQL2008_MINOR: - if (increment != TdsEnums.SQL2008_INCREMENT) - { - throw SQL.InvalidTDSVersion(); - } - _is2008 = true; - break; - case TdsEnums.SQL2012_MAJOR << 24 | TdsEnums.SQL2012_MINOR: - if (increment != TdsEnums.SQL2012_INCREMENT) - { - throw SQL.InvalidTDSVersion(); - } - _is2012 = true; - break; - case TdsEnums.TDS8_MAJOR << 24 | TdsEnums.TDS8_MINOR: - if (increment != TdsEnums.TDS8_INCREMENT) - { - throw SQL.InvalidTDSVersion(); - } - _is2022 = true; - break; - default: - throw SQL.InvalidTDSVersion(); - } - _is2012 |= _is2022; - _is2008 |= _is2012; + ADP.ValidateTdsVersion(tdsVersion); stateObj._outBytesUsed = stateObj._outputHeaderLen; byte len; @@ -4384,29 +4335,33 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out { return result; } - result = stateObj.TryReadByte(out a.majorVersion); + result = stateObj.TryReadByte(out byte majorVersion); if (result != TdsOperationStatus.Done) { return result; } - result = stateObj.TryReadByte(out a.minorVersion); + result = stateObj.TryReadByte(out byte minorVersion); if (result != TdsOperationStatus.Done) { return result; } - byte buildNumHi, buildNumLo; - result = stateObj.TryReadByte(out buildNumHi); + result = stateObj.TryReadByte(out byte buildNumHi); if (result != TdsOperationStatus.Done) { return result; } - result = stateObj.TryReadByte(out buildNumLo); + result = stateObj.TryReadByte(out byte buildNumLo); if (result != TdsOperationStatus.Done) { return result; } - a.buildNum = (short)((buildNumHi << 8) + buildNumLo); + ushort buildNumber = (ushort)((buildNumHi << 8) | buildNumLo); + + Capabilities.ServerMajorVersion = majorVersion; + Capabilities.ServerMinorVersion = minorVersion; + Capabilities.ServerBuildNumber = buildNumber; + Capabilities.TdsVersion = tdsVersion; Debug.Assert(_state == TdsParserState.OpenNotLoggedIn, "ProcessLoginAck called with state not TdsParserState.OpenNotLoggedIn"); _state = TdsParserState.OpenLoggedIn; @@ -4426,7 +4381,6 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out ThrowExceptionAndWarning(stateObj); } - sqlLoginAck = a; return TdsOperationStatus.Done; } @@ -9317,11 +9271,11 @@ private void WriteLoginData(SqlLogin rec, { if (encrypt == SqlConnectionEncryptOption.Strict) { - WriteInt((TdsEnums.TDS8_MAJOR << 24) | (TdsEnums.TDS8_INCREMENT << 16) | TdsEnums.TDS8_MINOR, _physicalStateObj); + WriteUnsignedInt(TdsEnums.TDS80_VERSION, _physicalStateObj); } else { - WriteInt((TdsEnums.SQL2012_MAJOR << 24) | (TdsEnums.SQL2012_INCREMENT << 16) | TdsEnums.SQL2012_MINOR, _physicalStateObj); + WriteUnsignedInt(TdsEnums.TDS7X_VERSION, _physicalStateObj); } } else @@ -10265,7 +10219,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout continue; } - if (!_is2008 && !mt.Is90Supported) + if (!Is2008OrNewer && !mt.Is90Supported) { throw ADP.VersionDoesNotSupportDataType(mt.TypeName); } @@ -10644,7 +10598,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet } else if (mt.SqlDbType == SqlDbType.Udt) { - int maxSupportedSize = Is2008OrNewer ? int.MaxValue : short.MaxValue; + int maxSupportedSize = Capabilities.LargeUserDefinedTypes ? int.MaxValue : short.MaxValue; byte[] udtVal = null; if (string.IsNullOrEmpty(param.UdtTypeName)) @@ -11019,7 +10973,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa ParameterPeekAheadValue peekAhead; SmiParameterMetaData metaData = param.MetaDataForSmi(out peekAhead); - if (!_is2008) + if (!Is2008OrNewer) { MetaType mt = MetaType.GetMetaTypeFromSqlDbType(metaData.SqlDbType, metaData.IsMultiValued); throw ADP.VersionDoesNotSupportDataType(mt.TypeName); 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 4445905533..6bcc3f2d41 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -126,14 +126,6 @@ internal sealed class SqlLogin internal SecureString newSecurePassword; } - internal sealed class SqlLoginAck - { - internal byte majorVersion; - internal byte minorVersion; - internal short buildNum; - internal uint tdsVersion; - } - #nullable enable internal sealed class SqlFedAuthInfo diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index b79baefbd5..914e63019a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -1193,6 +1193,8 @@ internal class StubDbConnectionInternal : DbConnectionInternal #region Not Implemented Members public override string ServerVersion => throw new NotImplementedException(); + public override ConnectionCapabilities Capabilities => throw new NotImplementedException(); + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) { throw new NotImplementedException(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ConnectionPoolSlotsTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ConnectionPoolSlotsTest.cs index c4f437d981..28e59e7a5d 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ConnectionPoolSlotsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ConnectionPoolSlotsTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -24,6 +24,8 @@ public MockDbConnectionInternal() : base(ConnectionState.Open, true, false) { } public override string ServerVersion => "Mock Server 1.0"; + public override ConnectionCapabilities Capabilities => new(); + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) { throw new NotImplementedException(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs index faa4073a7d..dacc266753 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/IdleConnectionChannelTest.cs @@ -8,6 +8,7 @@ using System.Threading.Channels; using System.Threading.Tasks; using System.Transactions; +using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; using Xunit; @@ -316,6 +317,8 @@ private class StubDbConnectionInternal : DbConnectionInternal { public override string ServerVersion => throw new NotImplementedException(); + public override ConnectionCapabilities Capabilities => throw new NotImplementedException(); + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) => throw new NotImplementedException(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs index a29abd6bbb..588418ac03 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/TransactedConnectionPoolTest.cs @@ -713,6 +713,8 @@ internal class MockDbConnectionInternal : DbConnectionInternal public override string ServerVersion => "Mock"; + public override ConnectionCapabilities Capabilities => new(); + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) { throw new NotImplementedException(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs index 88af13922b..712708375a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs @@ -919,6 +919,8 @@ internal class MockDbConnectionInternal : DbConnectionInternal public override string ServerVersion => "Mock"; + public override ConnectionCapabilities Capabilities => new(); + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) { throw new NotImplementedException(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/AdapterUtilTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/AdapterUtilTest.cs new file mode 100644 index 0000000000..9fac3cd339 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/AdapterUtilTest.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. + +using System; +using Microsoft.Data.SqlClient; +using Xunit; + +namespace Microsoft.Data.Common.UnitTests; + +/// +/// Class containing tests of the various utility methods in . +/// +public class AdapterUtilTest +{ + /// + /// Gets a collection of test data representing the list of server TDS versions + /// which are expected by the client. + /// + /// + public static TheoryData ValidTdsVersions => [ + // SQL Server 2005 + TdsEnums.SQL2005_VERSION, + // SQL Server 2008 R2 + TdsEnums.SQL2008_VERSION, + // SQL Server 2012+ (TDS 7.x) + TdsEnums.TDS7X_VERSION, + // SQL Server 2022+ (TDS 8.0) + TdsEnums.TDS80_VERSION + ]; + + /// + /// Gets a collection of test data representing the list of known server TDS versions + /// which the client does not expect to receive and should reject. + /// + /// + public static TheoryData InvalidTdsVersions => [ + // Empty TDS version + 0x00000000, + // SQL Server 7.0 + 0x07000000, + // SQL Server 2000 + 0x07010000, + // SQL Server 2000 SP1 + 0x71000001, + // SQL Server 2008 + 0x730A0003 + ]; + + /// + /// Combines the test data sets in and . + /// + /// + public static TheoryData AllTdsVersions => [.. ValidTdsVersions, .. InvalidTdsVersions]; + + /// + /// Verifies that the client throws an InvalidOperationException in response to receiving an + /// unsupported TDS protocol version. + /// + /// + /// The TDS protocol version to validate. Represents an unsupported or invalid version value. + /// + [Theory] + [MemberData(nameof(InvalidTdsVersions))] + public void InvalidTdsVersion_ThrowsInvalidOperationException(uint tdsVersion) + { + Assert.Throws(() => ADP.ValidateTdsVersion(tdsVersion)); + } + + /// + /// Verifies that the client accepts one of the supported TDS protocol versions without throwing + /// an exception. + /// + /// + /// The TDS protocol version to validate. Represents a supported version value. + /// + [Theory] + [MemberData(nameof(ValidTdsVersions))] + public void ValidTdsVersion_ReturnsSuccessfully(uint tdsVersion) => + ADP.ValidateTdsVersion(tdsVersion); + + /// + /// Verifies that the SqlClient v7.1+ TDS version validation logic implemented in ADP.ValidateTdsVersion + /// is consistent with the legacy validation logic. + /// + /// + /// The TDS protocol version to validate. Represents a version value to be checked against the legacy + /// validation logic. + /// + [Theory] + [MemberData(nameof(AllTdsVersions))] + public void TdsVersionValidation_AlignsWithLegacyValidation(uint tdsVersion) + { + Action validation = () => ADP.ValidateTdsVersion(tdsVersion); + + if (LegacyVersionValidation(tdsVersion)) + { + validation(); + } + else + { + Assert.Throws(validation); + } + } + + private static bool LegacyVersionValidation(uint tdsVersion) + { + const int SQL2005_MAJOR = 0x72; + const int SQL2008_MAJOR = 0x73; + const int SQL2012_MAJOR = 0x74; + const int TDS8_MAJOR = 0x08; + + const int SQL2005_INCREMENT = 0x09; + const int SQL2008_INCREMENT = 0x0b; + const int SQL2012_INCREMENT = 0x00; + const int TDS8_INCREMENT = 0x00; + + const int SQL2005_RTM_MINOR = 0x0002; + const int SQL2008_MINOR = 0x0003; + const int SQL2012_MINOR = 0x0004; + const int TDS8_MINOR = 0x00; + + uint majorMinor = tdsVersion & 0xff00ffff; + uint increment = (tdsVersion >> 16) & 0xff; + + switch (majorMinor) + { + case SQL2005_MAJOR << 24 | SQL2005_RTM_MINOR: + if (increment != SQL2005_INCREMENT) + { + return false; + } + return true; + case SQL2008_MAJOR << 24 | SQL2008_MINOR: + if (increment != SQL2008_INCREMENT) + { + return false; + } + return true; + case SQL2012_MAJOR << 24 | SQL2012_MINOR: + if (increment != SQL2012_INCREMENT) + { + return false; + } + return true; + case TDS8_MAJOR << 24 | TDS8_MINOR: + if (increment != TDS8_INCREMENT) + { + return false; + } + return true; + default: + return false; + } + } +} From 73b9a96ffebbffa33f1b60d36b2a9f6bbfae2433 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 13 Jul 2026 16:12:10 -0700 Subject: [PATCH 184/298] Extract pool blocking-period error state into BlockingPeriodErrorState (#4395) * Extract pool blocking-period error state into BlockingPeriodErrorState Move the connection pool's blocking-period (error backoff) logic out of WaitHandleDbConnectionPool into a reusable, testable BlockingPeriodErrorState class with exponential backoff (5s..60s), cached-exception fast-fail, and an injectable TimeProvider for deterministic tests. Add DbConnectionPoolGroup.IsBlockingPeriodEnabled() and the ADP.UnsafeCreateTimer(TimeProvider,...) overload it depends on. Includes BlockingPeriodErrorStateTest. * Add comments * Improve locking * First round of review changes. * Require exhaustive switch statements. * Add wait handle pool unit tests for blocking period. * Remove rule. * Improve tests. Add TODOs. * Update test names and xml. * Add documentation about callback idempotency. * Fix xml docs * Add default switch case. * Swallow errors. Fix disposal. * Fix test conflicts * Simplify enter/exit API. * Restore original behavior. * Revert editorconfig changes. --- .editorconfig | 2 +- .../src/Microsoft/Data/Common/AdapterUtil.cs | 40 +- .../BlockingPeriodErrorState.cs | 269 ++++++++ .../ConnectionPool/DbConnectionPoolGroup.cs | 15 + .../ConnectionPool/IDbConnectionPool.cs | 1 + .../WaitHandleDbConnectionPool.cs | 160 ++--- .../BlockingPeriodErrorStateTest.cs | 634 ++++++++++++++++++ ...andleDbConnectionPoolBlockingPeriodTest.cs | 403 +++++++++++ .../WaitHandleDbConnectionPoolShutdownTest.cs | 24 - 9 files changed, 1400 insertions(+), 148 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/BlockingPeriodErrorState.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/BlockingPeriodErrorStateTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBlockingPeriodTest.cs diff --git a/.editorconfig b/.editorconfig index 819a41fe1e..1864df162c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -197,4 +197,4 @@ 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 +dotnet_diagnostic.xUnit1030.severity=none \ No newline at end of file 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 6f1e31f47f..22aa0360ec 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -133,9 +133,10 @@ internal static Exception ExceptionWithStackTrace(Exception e) } } +#nullable enable internal static Timer UnsafeCreateTimer( TimerCallback callback, - object state, + object? state, int dueTimeMilliseconds, int periodMilliseconds) => UnsafeCreateTimer( @@ -144,7 +145,7 @@ internal static Timer UnsafeCreateTimer( TimeSpan.FromMilliseconds(dueTimeMilliseconds), TimeSpan.FromMilliseconds(periodMilliseconds)); - internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) + internal static Timer UnsafeCreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period) { // Don't capture the current ExecutionContext and its AsyncLocals onto // a global timer causing them to live forever @@ -159,6 +160,41 @@ internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, Ti } } + /// + /// Creates an using the supplied without + /// capturing the current . This overload accepts a strongly + /// typed callback that receives the supplied + /// when the timer fires. + /// + /// The type of the state passed to the callback. + /// The time provider used to create the timer. + /// The delegate invoked with when the timer fires. + /// The state object passed to on each invocation. + /// The amount of time to wait before the first invocation, or + /// to create the timer disarmed. + /// The interval between invocations, or + /// to disable periodic signaling. + /// An created by . + internal static ITimer UnsafeCreateTimer( + TimeProvider timeProvider, + Action callback, + T state, + TimeSpan dueTime, + TimeSpan period) + { + if (ExecutionContext.IsFlowSuppressed()) + { + return timeProvider.CreateTimer(s => callback((T)s!), state, dueTime, period); + } + + using (ExecutionContext.SuppressFlow()) + { + return timeProvider.CreateTimer(s => callback((T)s!), state, dueTime, period); + } + } + + +#nullable restore #region COM+ exceptions internal static ArgumentException Argument(string error) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/BlockingPeriodErrorState.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/BlockingPeriodErrorState.cs new file mode 100644 index 0000000000..796f0e16dd --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/BlockingPeriodErrorState.cs @@ -0,0 +1,269 @@ +// 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.Threading; +using Microsoft.Data.Common; +using Microsoft.Data.SqlClient.Internal; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + /// + /// Encapsulates a connection pool's blocking-period error state: cached exception, exponential + /// backoff timer, and synchronization. Kept as a separate class so the pool's + /// connection-acquisition path remains focused on capacity/queue concerns and stays + /// decoupled from the (independent) rate limiting policy. + /// + internal sealed class BlockingPeriodErrorState : IDisposable + { + // Backoff interval used the first time the pool enters the blocking period, and the + // value the backoff resets to on a successful create or Clear(). + private static readonly TimeSpan InitialWait = TimeSpan.FromSeconds(5); + + // Upper bound the exponential backoff is capped at; further failures never wait longer. + private static readonly TimeSpan MaxWait = TimeSpan.FromSeconds(60); + + // Identifier of the owning pool, included in trace events for diagnostics. + private readonly int _ownerPoolId; + + // Optional error wait handle owned by the pool. Set() (under _lock) when the state + // enters the blocking period and Reset() (under _lock) when it leaves via the exit + // timer or Clear(). Set/Reset are inherently idempotent, so the repeated signalling + // that occurs across consecutive Enter() calls or an exit-timer/Clear() overlap is + // harmless. This instance does NOT own the handle and must never dispose it. If null, + // no signalling occurs. + private readonly ManualResetEvent? _errorEvent; + + // Time source used to create the exit timer; overridable so tests can drive scheduling + // deterministically. Defaults to TimeProvider.System. + private readonly TimeProvider _timeProvider; + + // Guards the mutable error state (_cachedException, _exitTimer, _nextWait, _disposed). + private readonly object _lock = new(); + + // Non-null while the pool is in the blocking period. Doubles as the "has error" + // flag, so callers don't need a separate bool. Volatile so other threads observe + // entry/exit transitions without acquiring _lock. + private volatile Exception? _cachedException; + + // True from the moment Enter() activates the blocking period until Clear()/Dispose() + // fully resets it. The exit timer clears _cachedException but leaves this set so a + // later Clear() still resets the backoff. Volatile so Clear() can take a + // lock-free path when there is nothing to do. This allows Clear() to be called on hot paths. + private volatile bool _inElevatedState; + + // The armed exit timer that ends the current blocking period; null when no period is + // active. Replaced (and the old one disposed) each time Enter() is called. + private ITimer? _exitTimer; + + // The backoff interval to use for the next Enter(); doubles per failure up to MaxWait + // and resets to InitialWait on a successful create or Clear(). + private TimeSpan _nextWait = InitialWait; + + /// + /// Creates a new instance. + /// + /// Identifier of the owning pool, used in trace events. + /// Optional error wait handle owned by the pool. It is + /// (while holding the internal lock) when the state + /// enters the blocking period and when it leaves via + /// the exit timer or , so a thread parked in WaitHandle.WaitAny + /// over the pool's handles wakes immediately on entry/exit. This instance does not own the + /// handle and never disposes it; the caller is responsible for its lifetime. Pass + /// (the default) to disable signalling. + /// The time provider used to create the exit timer. Defaults to + /// . Inject a test double (e.g. + /// Microsoft.Extensions.Time.Testing.FakeTimeProvider) in unit tests to + /// control timer scheduling deterministically. + internal BlockingPeriodErrorState(int ownerPoolId, ManualResetEvent? errorEvent = null, TimeProvider? timeProvider = null) + { + _ownerPoolId = ownerPoolId; + _errorEvent = errorEvent; + _timeProvider = timeProvider ?? TimeProvider.System; + } + + /// + /// True while the pool is in the blocking period. Subsequent acquisition attempts + /// should fast-fail with the cached exception. + /// + internal bool HasError => _cachedException is not null; + + /// + /// Throws the cached error if the pool is currently in the blocking period. + /// + internal void ThrowIfActive() + { + Exception? cached = _cachedException; + if (cached is null) + { + return; + } + + // Clone SqlExceptions so stack traces are not shared across callers; other + // exception types are rethrown as-is. + // TODO: Can we create a new exception to wrap the cached exception? + throw cached is SqlException sqlEx ? sqlEx.InternalClone() : cached; + } + + /// + /// Enters the blocking period, caching the supplied exception and scheduling a timer + /// to exit the period after the current backoff interval. Subsequent failures double + /// the backoff up to . + /// + internal void Enter(Exception ex) + { + TimeSpan wait; + ITimer? oldTimer; + + // If we call this, we're already in an exception path. Prefer correctness over performance. + lock (_lock) + { + _inElevatedState = true; + _cachedException = ex; + wait = _nextWait; + + ITimer newTimer = ADP.UnsafeCreateTimer( + _timeProvider, + ExitCallback, + this, + wait, + wait); + oldTimer = _exitTimer; + _exitTimer = newTimer; + + // Bump the backoff for the next failure, capped at MaxWait. FR-008. + TimeSpan doubled = _nextWait + _nextWait; + _nextWait = doubled > MaxWait ? MaxWait : doubled; + + // Signal the error wait handle while still holding the lock so the external signal + // order (Set/Reset) can never diverge from the internal state transitions under + // concurrent Enter/Clear/exit-timer activity. Set() is idempotent, so re-entering + // the blocking period on consecutive failures is harmless. The handle is owned by + // the pool, not this instance, so treat signalling as best-effort: it may be + // disposed out from under us during teardown, which would throw + // ObjectDisposedException. Swallow it so it cannot mask the original + // connection-creation exception. + try + { + _errorEvent?.Set(); + } + catch + { + // Ignore: the pool-owned handle may have been disposed during teardown. + } + } + + oldTimer?.Dispose(); + + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Entering blocking period for {1}ms.", + _ownerPoolId, + (int)wait.TotalMilliseconds); + } + + /// + /// Clears the cached error state, disposes the exit timer, and resets the backoff to + /// its initial value. + /// + internal void Clear() + { + // Fast path: the create flow calls Clear() after every successful create, so avoid + // taking the lock in the common (no-error) case where there is nothing to reset. + if (!_inElevatedState) + { + return; + } + + ITimer? oldTimer; + + lock (_lock) + { + _cachedException = null; + _nextWait = InitialWait; + _inElevatedState = false; + oldTimer = _exitTimer; + _exitTimer = null; + + // Reset the error wait handle under the lock so the exit signal is ordered + // consistently with the state transition relative to concurrent Enter/exit-timer + // callbacks. Reset() is idempotent, so an overlapping exit-timer/Clear() is harmless. + // The handle is owned by the pool, not this instance, so treat signalling as + // best-effort: it may be disposed out from under us during teardown, which would + // throw ObjectDisposedException. Swallow it so it cannot surface on the successful + // connection-create path and break pool recovery. + try + { + _errorEvent?.Reset(); + } + catch + { + // Ignore: the pool-owned handle may have been disposed during teardown. + } + } + + oldTimer?.Dispose(); + + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Error state cleared.", _ownerPoolId); + } + + /// + /// Timer callback that exits the blocking period by clearing the cached exception, + /// allowing the next caller to attempt a fresh connection creation. The current + /// backoff is left intact so that, if the next attempt fails, the backoff continues + /// to grow rather than resetting. The backoff is reset only on . + /// + private static void ExitCallback(BlockingPeriodErrorState state) + { + ITimer? oldTimer; + + lock (state._lock) + { + state._cachedException = null; + oldTimer = state._exitTimer; + state._exitTimer = null; + + // Reset the error wait handle under the lock so the exit signal is ordered + // consistently with the state transition relative to concurrent Enter/Clear + // callbacks. Reset() is idempotent, so an overlapping Clear() is harmless. + // The handle is owned by the pool, not this instance, so treat signalling as + // best-effort: it may be disposed out from under us during teardown, which would + // throw ObjectDisposedException on this ThreadPool timer callback. Swallow it. + try + { + state._errorEvent?.Reset(); + } + catch + { + // Ignore: the pool-owned handle may have been disposed during teardown. + } + } + + oldTimer?.Dispose(); + + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Exiting blocking period.", state._ownerPoolId); + } + + /// + /// Disposes the instance, releasing the exit timer if one is active. Clears the + /// error state so that any waiting callers do not observe a stale exception after + /// the owning pool is torn down. + /// + public void Dispose() + { + lock (_lock) + { + _cachedException = null; + _inElevatedState = false; + ITimer? timerToDispose = _exitTimer; + _exitTimer = null; + timerToDispose?.Dispose(); + } + + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index a7e0d1c48b..a54be9a2b4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -93,6 +93,21 @@ internal DbConnectionPoolGroupProviderInfo ProviderInfo internal SqlMetaDataFactory MetaDataFactory { get; set; } + /// + /// Determines whether the blocking period is enabled for this pool group based on the + /// configured and the target data source. + /// + internal bool IsBlockingPeriodEnabled() + { + return _connectionOptions.PoolBlockingPeriod switch + { + PoolBlockingPeriod.Auto => !ADP.IsAzureSqlServerEndpoint(_connectionOptions.DataSource), + PoolBlockingPeriod.AlwaysBlock => true, + PoolBlockingPeriod.NeverBlock => false, + _ => true + }; + } + internal int Clear() { // must be multi-thread safe with competing calls by Clear and Prune via background thread 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 index 2ae8e8ea98..3799cf54ea 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -46,6 +46,7 @@ internal interface IDbConnectionPool /// Indicates whether an error has occurred in the pool. /// Primarily used to support the pool blocking period feature. /// + /// TODO: rename to indicate that this relates to the blocking period bool ErrorOccurred { get; } /// diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index e83fdcf7fa..00ddb01c26 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -29,7 +29,7 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// 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. + /// Error Handling and Resilience: Implements retry and exponential backoff in TryGetConnection and delegates blocking-period bookkeeping (cached exception, exit timer) to . /// 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. @@ -165,8 +165,6 @@ public void Dispose() private const int WAIT_ABANDONED = 0x80; - private const int ERROR_WAIT_DEFAULT = 5 * 1000; // 5 seconds - // we do want a testable, repeatable set of generated random numbers private static readonly Random s_random = new Random(5101977); // Value obtained from Dave Driver @@ -194,11 +192,8 @@ public void Dispose() internal int _waitCount; private readonly PoolWaitHandles _waitHandles; - private Exception _resError; - private volatile bool _errorOccurred; - - private int _errorWait; - internal Timer _errorTimer; + private readonly TimeProvider _timeProvider; + private readonly BlockingPeriodErrorState _errorState; internal Timer _cleanupTimer; @@ -212,7 +207,8 @@ internal WaitHandleDbConnectionPool( SqlConnectionFactory connectionFactory, DbConnectionPoolGroup connectionPoolGroup, DbConnectionPoolIdentity identity, - DbConnectionPoolProviderInfo connectionPoolProviderInfo) + DbConnectionPoolProviderInfo connectionPoolProviderInfo, + TimeProvider timeProvider = null) { Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup"); @@ -249,11 +245,18 @@ internal WaitHandleDbConnectionPool( _connectionPoolGroupOptions = connectionPoolGroup.PoolGroupOptions; _connectionPoolProviderInfo = connectionPoolProviderInfo; _identity = identity; + _timeProvider = timeProvider ?? TimeProvider.System; _waitHandles = new PoolWaitHandles(); - _errorWait = ERROR_WAIT_DEFAULT; - _errorTimer = null; // No error yet. + // Hook the wait-handle event so any thread blocked in WaitAny over the pool's + // handles wakes up immediately when the blocking period is entered/exited. + // _timeProvider is the system clock in production; tests inject a fake clock to + // drive the exit timer deterministically. + _errorState = new BlockingPeriodErrorState( + Id, + errorEvent: _waitHandles.ErrorEvent, + timeProvider: _timeProvider); _objectList = new List(MaxPoolSize); @@ -283,7 +286,7 @@ private int CreationTimeout public SqlConnectionFactory ConnectionFactory => _connectionFactory; - public bool ErrorOccurred => _errorOccurred; + public bool ErrorOccurred => _errorState.HasError; private bool HasTransactionAffinity => PoolGroupOptions.HasTransactionAffinity; @@ -523,39 +526,6 @@ private Timer CreateCleanupTimer() => _cleanupWait, _cleanupWait); - private bool IsBlockingPeriodEnabled() - { - var poolGroupConnectionOptions = _connectionPoolGroup.ConnectionOptions; - if (poolGroupConnectionOptions == null) - { - return true; - } - - var policy = poolGroupConnectionOptions.PoolBlockingPeriod; - - switch (policy) - { - case PoolBlockingPeriod.Auto: - { - return !ADP.IsAzureSqlServerEndpoint(poolGroupConnectionOptions.DataSource); - } - case PoolBlockingPeriod.AlwaysBlock: - { - return true; //Enabled - } - case PoolBlockingPeriod.NeverBlock: - { - return false; //Disabled - } - default: - { - //we should never get into this path. - Debug.Fail("Unknown PoolBlockingPeriod. Please specify explicit results in above switch case statement."); - return true; - } - } - } - private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectionInternal oldConnection, TimeoutTimer timeout) { DbConnectionInternal newObj = null; @@ -581,14 +551,14 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Added to pool.", Id, newObj?.ObjectID); - // Reset the error wait: - _errorWait = ERROR_WAIT_DEFAULT; + // A successful creation clears any prior error state and resets backoff. + _errorState.Clear(); } catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { ADP.TraceExceptionWithoutRethrow(e); - if (!IsBlockingPeriodEnabled()) + if (!_connectionPoolGroup.IsBlockingPeriodEnabled()) { throw; } @@ -601,33 +571,10 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio newObj = null; // set to null, so we do not return bad new object - // Failed to create instance - _resError = e; - - // Make sure the timer starts even if ThreadAbort occurs after setting the ErrorEvent. - Timer t = new Timer(new TimerCallback(this.ErrorCallback), null, Timeout.Infinite, Timeout.Infinite); + // Enter the blocking period: caches the exception, schedules the exit timer, + // and signals the wait-handle error event via the onEnter callback. + _errorState.Enter(e); - bool timerIsNotDisposed; - - _waitHandles.ErrorEvent.Set(); - _errorOccurred = true; - - // Enable the timer. - // Note that the timer is created to allow periodic invocation. If ThreadAbort occurs in the middle of ErrorCallback, - // the timer will restart. Otherwise, the timer callback (ErrorCallback) destroys the timer after resetting the error to avoid second callback. - _errorTimer = t; - timerIsNotDisposed = t.Change(_errorWait, _errorWait); - - Debug.Assert(timerIsNotDisposed, "ErrorCallback timer has been disposed"); - - if (30000 < _errorWait) - { - _errorWait = 60000; - } - else - { - _errorWait *= 2; - } throw; } return newObj; @@ -800,35 +747,6 @@ private void DestroyObject(DbConnectionInternal obj) } } - internal void ErrorCallback(object state) - { - // Skip work if the pool is not Running. The shutdown path disposes the - // timer; this guard handles the in-flight-callback race. - if (State is not Running) - { - return; - } - - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Resetting Error handling.", Id); - _errorOccurred = false; - _waitHandles.ErrorEvent.Reset(); - - // the error state is cleaned, destroy the timer to avoid periodic invocation - Timer t = _errorTimer; - _errorTimer = null; - if (t != null) - { - t.Dispose(); // Cancel timer request. - } - } - - - private Exception TryCloneCachedException() - // Cached exception can be of any type, so is not always cloneable. - // This functions clones SqlException - // OleDb and Odbc connections are not passing throw this code - => _resError is SqlException sqlEx ? sqlEx.InternalClone() : _resError; - private void WaitForPendingOpen() { Debug.Assert(!Thread.CurrentThread.IsThreadPoolThread, "This thread may block for a long time. Threadpool threads should not be used."); @@ -1094,7 +1012,12 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj // Throw the error that PoolCreateRequest stashed. SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Errors are set.", Id); Interlocked.Decrement(ref _waitCount); - throw TryCloneCachedException(); + _errorState.ThrowIfActive(); + // Narrow race: error state cleared between WaitAny observing + // the signal and this check. Re-balance _waitCount and let the + // outer do/while loop retry. + Interlocked.Increment(ref _waitCount); + break; case CREATION_HANDLE: SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", Id); @@ -1655,8 +1578,8 @@ public void Shutdown() // and short-circuit (see CleanupCallback / ErrorCallback). Timer cleanup = Interlocked.Exchange(ref _cleanupTimer, null); cleanup?.Dispose(); - Timer error = Interlocked.Exchange(ref _errorTimer, null); - error?.Dispose(); + + _errorState.Dispose(); // Wake any threads parked in WaitHandle.WaitAny by releasing as many semaphore // slots as there are recorded waiters. Using _waitCount (rather than MaxPoolSize) @@ -1712,25 +1635,20 @@ private DbConnectionInternal UserCreateRequest(DbConnection owningObject, Timeou // instead obtained creation mutex DbConnectionInternal obj = null; - if (ErrorOccurred) - { - throw TryCloneCachedException(); - } - else + _errorState.ThrowIfActive(); + + if ((oldConnection != null) || (Count < MaxPoolSize) || (0 == MaxPoolSize)) { - if ((oldConnection != null) || (Count < MaxPoolSize) || (0 == MaxPoolSize)) - { - // If we have an odd number of total objects, reclaim any dead objects. - // If we did not find any objects to reclaim, create a new one. + // If we have an odd number of total objects, reclaim any dead objects. + // If we did not find any objects to reclaim, create a new one. - // TODO: Consider implement a control knob here; why do we only check for dead objects ever other time? why not every 10th time or every time? - if ((oldConnection != null) || (Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects()) - { - obj = CreateObject(owningObject, oldConnection, timeout); - } + // TODO: Consider implement a control knob here; why do we only check for dead objects ever other time? why not every 10th time or every time? + if ((oldConnection != null) || (Count & 0x1) == 0x1 || !ReclaimEmancipatedObjects()) + { + obj = CreateObject(owningObject, oldConnection, timeout); } - return obj; } + return obj; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/BlockingPeriodErrorStateTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/BlockingPeriodErrorStateTest.cs new file mode 100644 index 0000000000..fce4b232b0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/BlockingPeriodErrorStateTest.cs @@ -0,0 +1,634 @@ +// 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.Threading; +using Microsoft.Extensions.Time.Testing; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; + +#nullable enable + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool +{ + /// + /// Comprehensive unit tests for covering: + /// - Initial state and error caching + /// - and exception handling + /// - and state reset + /// - Exponential backoff progression (verified with ) + /// - Timer-driven exit behavior (verified with ) + /// - implementation and timer cleanup + /// - Error wait-handle () signalling on enter/exit + /// + public class BlockingPeriodErrorStateTest + { + #region HasError / initial state + + /// + /// Verifies that a newly constructed has + /// set to false. + /// + [Fact] + public void HasError_InitialState_IsFalse() + { + // Arrange + using var state = new BlockingPeriodErrorState(ownerPoolId: 1); + + // Act & Assert + Assert.False(state.HasError); + } + + /// + /// Verifies that does not throw + /// when called on a newly constructed instance with no cached error. + /// + [Fact] + public void ThrowIfActive_InitialState_DoesNotThrow() + { + // Arrange + using var state = new BlockingPeriodErrorState(ownerPoolId: 1); + + // Act & Assert + state.ThrowIfActive(); // Should complete without throwing + } + + #endregion + + #region Enter + + /// + /// Verifies that calling sets + /// to true. + /// + [Fact] + public void Enter_SetsHasErrorToTrue() + { + // Arrange + using var state = new BlockingPeriodErrorState(ownerPoolId: 1); + + // Act + state.Enter(new InvalidOperationException("test")); + + // Assert + Assert.True(state.HasError); + } + + /// + /// Verifies that throws + /// the exact exception type that was cached by . + /// + [Fact] + public void Enter_ThrowIfActive_ThrowsCachedExceptionType() + { + // Arrange + using var state = new BlockingPeriodErrorState(ownerPoolId: 1); + var exception = new InvalidOperationException("boom"); + + // Act + state.Enter(exception); + + // Assert + var ex = Assert.Throws(() => state.ThrowIfActive()); + Assert.Equal("boom", ex.Message); + } + + /// + /// Verifies that when a is cached, + /// throws a cloned instance rather than the original, to avoid sharing stack traces across callers. + /// + [Fact] + public void Enter_WithSqlException_ThrowsClonedInstance() + { + // Arrange + using var state = new BlockingPeriodErrorState(ownerPoolId: 1); + var original = SqlExceptionHelper.CreateSqlException("connection failed"); + + // Act + state.Enter(original); + var thrown = Assert.Throws(() => state.ThrowIfActive()); + + // Assert + Assert.NotSame(original, thrown); + Assert.Equal(original.Message, thrown.Message); + } + + /// + /// Verifies that signals the error wait + /// handle after entering the blocking period. + /// + [Fact] + public void Enter_SignalsErrorEvent() + { + // Arrange + using var errorEvent = new ManualResetEvent(false); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, errorEvent: errorEvent); + + // Act + state.Enter(new Exception()); + + // Assert + Assert.True(errorEvent.WaitOne(0)); + } + + /// + /// Verifies that calling a second time + /// replaces the cached exception, leaves the error wait handle signaled, and the new + /// exception is thrown. + /// + [Fact] + public void Enter_CalledTwice_ReplacesExceptionAndKeepsErrorEventSignaled() + { + // Arrange + using var errorEvent = new ManualResetEvent(false); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, errorEvent: errorEvent); + + // Act + state.Enter(new InvalidOperationException("first")); + state.Enter(new ArgumentException("second")); + + // Assert + Assert.True(errorEvent.WaitOne(0)); + var ex = Assert.Throws(() => state.ThrowIfActive()); + Assert.Equal("second", ex.Message); + } + + #endregion + + #region Clear + + /// + /// Verifies that resets + /// to false. + /// + [Fact] + public void Clear_AfterEnter_ResetsHasError() + { + // Arrange + using var state = new BlockingPeriodErrorState(ownerPoolId: 1); + state.Enter(new Exception()); + + // Act + state.Clear(); + + // Assert + Assert.False(state.HasError); + } + + /// + /// Verifies that after , + /// does not throw because the cached error has been cleared. + /// + [Fact] + public void Clear_AfterEnter_ThrowIfActiveDoesNotThrow() + { + // Arrange + using var state = new BlockingPeriodErrorState(ownerPoolId: 1); + state.Enter(new Exception()); + + // Act + state.Clear(); + + // Assert + state.ThrowIfActive(); // Must not throw + } + + /// + /// Verifies that resets the error wait + /// handle after clearing the error state. + /// + [Fact] + public void Clear_AfterEnter_ResetsErrorEvent() + { + // Arrange + using var errorEvent = new ManualResetEvent(false); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, errorEvent: errorEvent); + state.Enter(new Exception()); + Assert.True(errorEvent.WaitOne(0)); + + // Act + state.Clear(); + + // Assert + Assert.False(errorEvent.WaitOne(0)); + } + + /// + /// Verifies that calling twice leaves the + /// error wait handle reset (Reset is idempotent). + /// + [Fact] + public void Clear_CalledTwice_LeavesErrorEventReset() + { + // Arrange + using var errorEvent = new ManualResetEvent(false); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, errorEvent: errorEvent); + state.Enter(new Exception()); + + // Act + state.Clear(); + state.Clear(); + + // Assert + Assert.False(errorEvent.WaitOne(0)); + } + + /// + /// Verifies that resets the backoff + /// timeout to its initial value, so the next + /// uses the initial wait duration instead of the accumulated backoff. + /// + [Fact] + public void Clear_ResetsBackoffSoNextEnterUsesInitialWait() + { + // Arrange + using var state = new BlockingPeriodErrorState(ownerPoolId: 1); + state.Enter(new Exception("first")); + + // Act + state.Clear(); + state.Enter(new Exception("second")); + + // Assert + Assert.True(state.HasError); + } + + #endregion + + #region Backoff progression + + /// + /// Verifies that the initial enter schedules the timer with the 5-second initial wait. + /// The error state should persist until the timer fires, after which it clears automatically. + /// + [Fact] + public void Enter_FirstEntry_SchedulesInitialWaitTimer() + { + // Arrange + var fakeTime = new FakeTimeProvider(); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, timeProvider: fakeTime); + state.Enter(new Exception()); + + // Act: advance just under the initial 5s wait + fakeTime.Advance(TimeSpan.FromSeconds(4)); + + // Assert: timer has not fired yet + Assert.True(state.HasError); + + // Act: advance past the due time + fakeTime.Advance(TimeSpan.FromSeconds(1)); + + // Assert: timer has fired, error cleared + Assert.False(state.HasError); + } + + /// + /// Verifies that successive timer-driven exits double the backoff each time: + /// 5 s → 10 s → 20 s → 40 s → 60 s (capped at MaxWait). + /// Each Enter schedules the timer for the current accumulated wait and the error + /// persists until exactly that duration elapses. + /// + [Fact] + public void Enter_BackoffDoublesOnSuccessiveTimerFiredEntries() + { + // Arrange + var fakeTime = new FakeTimeProvider(); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, timeProvider: fakeTime); + + // (expectedWaitSeconds, _) — the wait used by Enter for that iteration + int[] expectedWaits = [5, 10, 20, 40, 60, 60]; + + // Act & Assert + foreach (int wait in expectedWaits) + { + state.Enter(new Exception($"attempt at wait={wait}s")); + + // One second before due time: error still active + fakeTime.Advance(TimeSpan.FromSeconds(wait - 1)); + Assert.True(state.HasError, $"HasError should be true after {wait - 1}s (scheduled wait={wait}s)"); + + // Final second: timer fires, error clears + fakeTime.Advance(TimeSpan.FromSeconds(1)); + Assert.False(state.HasError, $"HasError should be false after {wait}s (scheduled wait={wait}s)"); + } + } + + /// + /// Verifies that a timer-driven exit does NOT reset the backoff. The accumulated + /// backoff is preserved so the next failure uses the doubled wait, reflecting + /// continued instability. Only resets + /// the backoff to the initial value. In this way, we only reset the backoff when + /// a connection is successfully established. + /// + [Fact] + public void Enter_WhenTimerFires_DoesNotResetBackoff() + { + // Arrange + var fakeTime = new FakeTimeProvider(); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, timeProvider: fakeTime); + + // First enter: uses 5s wait; _nextWait advances to 10s + state.Enter(new Exception("first")); + fakeTime.Advance(TimeSpan.FromSeconds(5)); // timer fires + Assert.False(state.HasError); + + // Act: enter again — should use 10s, not the initial 5s + state.Enter(new Exception("second")); + + // Assert: not cleared after 9s + fakeTime.Advance(TimeSpan.FromSeconds(9)); + Assert.True(state.HasError); + + // Assert: cleared after the full 10s + fakeTime.Advance(TimeSpan.FromSeconds(1)); + Assert.False(state.HasError); + } + + /// + /// Verifies that resets the backoff + /// to the initial 5-second wait even after the timer has doubled it, so the next + /// enter cycle starts fresh. + /// + [Fact] + public void Clear_AfterTimerFiredEntry_ResetsBackoffToInitialWait() + { + // Arrange + var fakeTime = new FakeTimeProvider(); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, timeProvider: fakeTime); + + // First enter/timer-exit: _nextWait advances from 5s to 10s + state.Enter(new Exception("first")); + fakeTime.Advance(TimeSpan.FromSeconds(5)); + Assert.False(state.HasError); + + // Second enter to accumulate more backoff; then Clear resets it + state.Enter(new Exception("second")); // _nextWait advances to 20s + state.Clear(); // _nextWait resets to 5s + + // Act: enter again — should use the initial 5s wait + state.Enter(new Exception("third")); + + // Assert: not cleared after 4s + fakeTime.Advance(TimeSpan.FromSeconds(4)); + Assert.True(state.HasError); + + // Assert: cleared after the initial 5s + fakeTime.Advance(TimeSpan.FromSeconds(1)); + Assert.False(state.HasError); + } + + #endregion + + #region Timer behavior + + /// + /// Verifies that the timer-driven exit resets the error wait handle, the same + /// signalling path used by . + /// + [Fact] + public void Enter_WhenTimerFires_ResetsErrorEvent() + { + // Arrange + var fakeTime = new FakeTimeProvider(); + using var errorEvent = new ManualResetEvent(false); + using var state = new BlockingPeriodErrorState( + ownerPoolId: 1, + errorEvent: errorEvent, + timeProvider: fakeTime); + state.Enter(new Exception()); + Assert.True(errorEvent.WaitOne(0)); + + // Act + fakeTime.Advance(TimeSpan.FromSeconds(5)); + + // Assert + Assert.False(errorEvent.WaitOne(0)); + Assert.False(state.HasError); + } + + /// + /// Verifies that the timer does not fire before its due time, confirming the + /// scheduled wait is respected and not fired early. + /// + [Fact] + public void Enter_TimerDoesNotFireBeforeDueTime() + { + // Arrange + var fakeTime = new FakeTimeProvider(); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, timeProvider: fakeTime); + state.Enter(new Exception()); + + // Act: advance to 1ms before the 5s due time + fakeTime.Advance(TimeSpan.FromMilliseconds(4999)); + + // Assert + Assert.True(state.HasError); + } + + #endregion + + #region IDisposable + + /// + /// Verifies that on an initial state + /// does not throw and completes successfully. + /// + [Fact] + public void Dispose_OnInitialState_DoesNotThrow() + { + // Arrange & Act + var state = new BlockingPeriodErrorState(ownerPoolId: 1); + + // Assert + state.Dispose(); // Should not throw + } + + /// + /// Verifies that clears the cached + /// error state so is false after disposal. + /// + [Fact] + public void Dispose_AfterEnter_ClearsHasError() + { + // Arrange + var state = new BlockingPeriodErrorState(ownerPoolId: 1); + state.Enter(new Exception()); + + // Act + state.Dispose(); + + // Assert + Assert.False(state.HasError); + } + + /// + /// Verifies that is idempotent: + /// calling it multiple times does not throw and completes successfully. + /// + [Fact] + public void Dispose_CalledMultipleTimes_DoesNotThrow() + { + // Arrange & Act + var state = new BlockingPeriodErrorState(ownerPoolId: 1); + state.Dispose(); + + // Assert + state.Dispose(); // Must be idempotent and not throw + } + + /// + /// Verifies that does not reset the + /// error wait handle because disposal is a resource-cleanup path, not a logical + /// "exit blocking period" event. + /// + [Fact] + public void Dispose_DoesNotResetErrorEvent() + { + // Arrange + using var errorEvent = new ManualResetEvent(false); + var state = new BlockingPeriodErrorState(ownerPoolId: 1, errorEvent: errorEvent); + state.Enter(new Exception()); + + // Act + state.Dispose(); + + // Assert + Assert.True(errorEvent.WaitOne(0)); + } + + /// + /// Verifies that properly releases + /// and cancels the internal exit timer, preventing stale callbacks from firing after disposal. + /// Uses to advance time deterministically past the timer's due + /// time without relying on real-time sleeps. + /// + [Fact] + public void Dispose_ReleasesTimer_NoCallbackAfterDispose() + { + // Arrange + var fakeTime = new FakeTimeProvider(); + using var errorEvent = new ManualResetEvent(false); + var state = new BlockingPeriodErrorState( + ownerPoolId: 1, + errorEvent: errorEvent, + timeProvider: fakeTime); + state.Enter(new Exception()); + + // Act: dispose cancels the pending timer + state.Dispose(); + + // Advance well past the 5s due time — the cancelled timer must not fire (which would + // reset the event). + fakeTime.Advance(TimeSpan.FromSeconds(60)); + + // Assert: the event remains in the state Enter left it (signaled); the timer never fired. + Assert.True(errorEvent.WaitOne(0)); + } + + /// + /// Verifies that works correctly in a standard + /// using statement, with no exceptions thrown during disposal. + /// + [Fact] + public void Dispose_WithUsingStatement_DoesNotThrow() + { + // Arrange & Act + using (var state = new BlockingPeriodErrorState(ownerPoolId: 1)) + { + state.Enter(new Exception()); + Assert.True(state.HasError); + } + + // Assert + // No exception expected when the using block exits + } + + #endregion + + #region Error wait handle + + /// + /// Verifies that the error wait handle is optional (nullable) and the instance works + /// correctly when none is provided. + /// + [Fact] + public void ErrorEvent_IsNotRequiredAndDefaultsToNull() + { + // Arrange & Act + using var state = new BlockingPeriodErrorState(ownerPoolId: 42); + state.Enter(new Exception()); + + // Assert + state.Clear(); // Should work without an error wait handle + } + + /// + /// Verifies that the error wait handle is set on + /// and reset on , and that the instance never + /// disposes the caller-owned handle (it remains usable after the state is disposed). + /// + [Fact] + public void ErrorEvent_IsSetOnEnterAndResetOnClear_AndNotDisposedByState() + { + // Arrange + using var errorEvent = new ManualResetEvent(false); + var state = new BlockingPeriodErrorState(ownerPoolId: 1, errorEvent: errorEvent); + + // Act & Assert: set on enter + state.Enter(new Exception()); + Assert.True(errorEvent.WaitOne(0)); + + // reset on clear + state.Clear(); + Assert.False(errorEvent.WaitOne(0)); + + // The state does not own the handle, so disposing the state must leave it usable. + state.Dispose(); + Assert.False(errorEvent.WaitOne(0)); // no ObjectDisposedException + } + + /// + /// Verifies that signalling the error wait handle is best-effort: because the pool owns + /// the handle's lifecycle, it may be disposed out from under the state during teardown. + /// and + /// must swallow the resulting rather than surfacing it. + /// + [Fact] + public void ErrorEvent_WhenDisposed_SignallingDoesNotThrow() + { + // Arrange: hand the state a handle and then dispose it out from under the state. + var errorEvent = new ManualResetEvent(false); + using var state = new BlockingPeriodErrorState(ownerPoolId: 1, errorEvent: errorEvent); + errorEvent.Dispose(); + + // Act & Assert: Enter (Set) and Clear (Reset) must not throw despite the disposed handle. + state.Enter(new Exception()); // best-effort Set() on disposed handle + Assert.True(state.HasError); + + state.Clear(); // best-effort Reset() on disposed handle + Assert.False(state.HasError); + } + + #endregion + } + + /// + /// Test helper for creating instances. Since has + /// an internal constructor, instances must be created via the factory method. + /// + internal static class SqlExceptionHelper + { + /// + /// Creates a with the specified message using the internal factory method. + /// + /// The error message for the exception. + /// A new with the specified message. + internal static SqlException CreateSqlException(string message) + { + var collection = new SqlErrorCollection(); + collection.Add(new SqlError(0, (byte)0, (byte)0, "TestServer", message, "", 0)); + return SqlException.CreateException(collection, ""); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBlockingPeriodTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBlockingPeriodTest.cs new file mode 100644 index 0000000000..e84f040caa --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBlockingPeriodTest.cs @@ -0,0 +1,403 @@ +// 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; +using System.Transactions; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Microsoft.Extensions.Time.Testing; +using Xunit; + +#nullable enable + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool; + +/// +/// End-to-end coverage of the blocking-period +/// behavior, exercising the full connection-acquisition path rather than the +/// state machine in isolation. Verifies the wiring +/// between a failed physical connection create and the pool's error state: +/// - A failed create enters the blocking period and surfaces the error (CreateObject → Enter). +/// - Subsequent requests fast-fail with the cached (cloned) exception without re-invoking the +/// connection factory (error wait-handle → ThrowIfActive). +/// - A successful create does not trip the blocking period. +/// - bypasses the blocking period entirely so each +/// request retries the factory. +/// - After the blocking-period exit timer fires, the next request retries the factory and a +/// successful create recovers the pool and resets the backoff (driven deterministically by an +/// injected ). +/// +public class WaitHandleDbConnectionPoolBlockingPeriodTest : IDisposable +{ + private const int DefaultMaxPoolSize = 50; + private const int DefaultMinPoolSize = 0; + private const int DefaultCreationTimeoutInMilliseconds = 15_000; + + private WaitHandleDbConnectionPool? _pool; + + public void Dispose() + { + _pool?.Shutdown(); + _pool?.Clear(); + } + + /// + /// Builds a running backed by the supplied factory. + /// The controls the data source and + /// policy used to resolve whether blocking is enabled. + /// When is supplied, the pool's + /// uses it as its clock so the exit timer can be driven + /// deterministically; otherwise the system clock is used. + /// + private WaitHandleDbConnectionPool CreatePool( + SqlConnectionFactory connectionFactory, + string connectionString = "Data Source=localhost;", + TimeProvider? timeProvider = null) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: DefaultMinPoolSize, + maxPoolSize: DefaultMaxPoolSize, + creationTimeout: DefaultCreationTimeoutInMilliseconds, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0); + + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions(connectionString), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + poolGroupOptions); + + var pool = new WaitHandleDbConnectionPool( + connectionFactory, + dbConnectionPoolGroup, + DbConnectionPoolIdentity.NoIdentity, + new DbConnectionPoolProviderInfo(), + timeProvider); + + pool.Startup(); + _pool = pool; + return pool; + } + + /// + /// Synchronously requests a connection from the pool, mirroring the sync acquisition path + /// (taskCompletionSource == null) used by callers of SqlConnection.Open. + /// + private static bool TryGetConnectionSync( + WaitHandleDbConnectionPool pool, + DbConnection owner, + out DbConnectionInternal? connection) + { + TimeoutTimer timer = TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)); + return pool.TryGetConnection(owner, taskCompletionSource: null, timer, out connection); + } + + /// + /// Verifies that when the connection factory fails, the pool enters the blocking period: + /// the originating exception is surfaced to the caller and + /// becomes true. Guards the CreateObject → wiring. + /// + [Fact] + public void TryGetConnection_WhenFactoryThrows_EntersBlockingPeriod() + { + // Arrange + SqlException failure = SqlExceptionHelper.CreateSqlException("server unreachable"); + var factory = new ConfigurableSqlConnectionFactory(_ => throw failure); + var pool = CreatePool(factory); + using var owner = new SqlConnection(); + + // Act + SqlException thrown = Assert.Throws( + () => TryGetConnectionSync(pool, owner, out _)); + + // Assert + Assert.Equal(failure.Message, thrown.Message); + Assert.True(pool.ErrorOccurred); + Assert.Equal(1, factory.CreateConnectionCallCount); + } + + /// + /// Verifies that once the pool is in the blocking period, a subsequent request fast-fails + /// with the cached exception without invoking the connection factory again. The first throw + /// rethrows the original instance; the fast-fail throw returns a clone (to avoid sharing stack + /// traces). Guards the error wait-handle → path. + /// + [Fact] + public void TryGetConnection_WhileBlocked_FastFailsWithCachedExceptionWithoutInvokingFactory() + { + // Arrange + SqlException failure = SqlExceptionHelper.CreateSqlException("server unreachable"); + var factory = new ConfigurableSqlConnectionFactory(_ => throw failure); + var pool = CreatePool(factory); + using var owner = new SqlConnection(); + + // Act: first request enters the blocking period and rethrows the original exception. + SqlException first = Assert.Throws( + () => TryGetConnectionSync(pool, owner, out _)); + + // Second request must fast-fail from the cached state without reaching the factory. + SqlException second = Assert.Throws( + () => TryGetConnectionSync(pool, owner, out _)); + + // Assert + Assert.Same(failure, first); // original instance rethrown on entry + Assert.NotSame(failure, second); // cloned on fast-fail + Assert.Equal(failure.Message, second.Message); // but message is preserved + Assert.Equal(1, factory.CreateConnectionCallCount); // factory not re-invoked while blocked + Assert.True(pool.ErrorOccurred); + } + + /// + /// Verifies that with , a failed create does not + /// cache the error: stays false and each + /// request retries the connection factory. Guards the + /// IsBlockingPeriodEnabled() == false bypass in CreateObject. + /// + [Fact] + public void TryGetConnection_WithNeverBlockPolicy_DoesNotCacheErrorAndRetriesFactory() + { + // Arrange + SqlException failure = SqlExceptionHelper.CreateSqlException("server unreachable"); + var factory = new ConfigurableSqlConnectionFactory(_ => throw failure); + var pool = CreatePool(factory, "Data Source=localhost;PoolBlockingPeriod=NeverBlock"); + using var owner = new SqlConnection(); + + // Act: two independent failures, neither of which should be cached. + SqlException first = Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); + SqlException second = Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); + + // Assert + Assert.Same(failure, first); // original instance rethrown, never cached/cloned + Assert.Same(failure, second); // original instance rethrown again on retry + Assert.False(pool.ErrorOccurred); + Assert.Equal(2, factory.CreateConnectionCallCount); + } + + /// + /// Verifies that a successful create returns a connection and does not trip the blocking + /// period, confirming the normal path leaves + /// false (and exercises the fast no-op on success). + /// + [Fact] + public void TryGetConnection_WhenFactorySucceeds_DoesNotEnterBlockingPeriod() + { + // Arrange + var factory = new ConfigurableSqlConnectionFactory(_ => new MockDbConnectionInternal()); + var pool = CreatePool(factory); + using var owner = new SqlConnection(); + + // Act + bool completed = TryGetConnectionSync(pool, owner, out DbConnectionInternal? connection); + + // Assert + Assert.True(completed); + Assert.NotNull(connection); + Assert.False(pool.ErrorOccurred); + Assert.Equal(1, factory.CreateConnectionCallCount); + } + + /// + /// Verifies that once the blocking period's exit timer fires, the next request retries the + /// factory and a successful create recovers the pool: + /// returns to false and a connection is produced. Drives the exit timer deterministically with + /// an injected , guarding the + /// timer-exit → retry → Clear path at the pool level. + /// + [Fact] + public void TryGetConnection_AfterBlockingPeriodExpires_RetriesFactoryAndRecovers() + { + // Arrange: first create fails, every later create succeeds. + SqlException failure = SqlExceptionHelper.CreateSqlException("server unreachable"); + var factory = new ConfigurableSqlConnectionFactory( + call => call == 1 ? throw failure : new MockDbConnectionInternal()); + var fakeTime = new FakeTimeProvider(); + var pool = CreatePool(factory, timeProvider: fakeTime); + using var owner = new SqlConnection(); + + // Act: first request enters the blocking period. + Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); + Assert.True(pool.ErrorOccurred); + Assert.Equal(1, factory.CreateConnectionCallCount); + + // While blocked, a request fast-fails without reaching the factory. + Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); + Assert.Equal(1, factory.CreateConnectionCallCount); + + // Advance past the initial 5s blocking period so the exit timer fires. + fakeTime.Advance(TimeSpan.FromSeconds(5)); + + // Assert: the pool has recovered and a new request reaches the factory and succeeds. + Assert.False(pool.ErrorOccurred); + bool completed = TryGetConnectionSync(pool, owner, out DbConnectionInternal? connection); + Assert.True(completed); + Assert.NotNull(connection); + Assert.False(pool.ErrorOccurred); + Assert.Equal(2, factory.CreateConnectionCallCount); + } + + /// + /// Verifies that a successful create resets the exponential backoff to the initial 5-second + /// wait. The backoff is first allowed to grow (fail → timer-exit → fail → timer-exit, observing + /// the doubled 10-second wait) so the reset is observable. A successful create then resets the + /// backoff via , and a subsequent failure blocks for + /// only the initial 5 seconds rather than the accumulated 20 seconds, confirming the success + /// reset is wired through the pool. Drives timing deterministically with an injected + /// . + /// + [Fact] + public void TryGetConnection_SuccessfulCreate_ResetsBackoffToInitialWait() + { + // Arrange: creates #1, #2 and #4 fail; #3 succeeds (recovery) to reset the backoff. + SqlException failure = SqlExceptionHelper.CreateSqlException("server unreachable"); + var factory = new ConfigurableSqlConnectionFactory( + call => call == 3 ? new MockDbConnectionInternal() : throw failure); + var fakeTime = new FakeTimeProvider(); + var pool = CreatePool(factory, timeProvider: fakeTime); + using var owner = new SqlConnection(); + + // Act: first failure enters the blocking period with the initial 5s wait. + Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); + Assert.Equal(1, factory.CreateConnectionCallCount); + Assert.True(pool.ErrorOccurred); + fakeTime.Advance(TimeSpan.FromSeconds(5)); // timer fires -> backoff doubles to 10s + Assert.False(pool.ErrorOccurred); + + // Second failure enters with the doubled 10s wait; confirm it lasts the full 10s so the + // backoff has demonstrably grown before we reset it. + Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); // create #2 fails + Assert.Equal(2, factory.CreateConnectionCallCount); + fakeTime.Advance(TimeSpan.FromSeconds(9)); + Assert.True(pool.ErrorOccurred); // still blocked at 9s -> wait is 10s, not 5s + fakeTime.Advance(TimeSpan.FromSeconds(1)); // timer fires at 10s -> backoff would double to 20s + Assert.False(pool.ErrorOccurred); + + // A successful create resets the backoff to the initial 5s. + Assert.True(TryGetConnectionSync(pool, owner, out _)); // create #3 succeeds -> Clear() + Assert.Equal(3, factory.CreateConnectionCallCount); + Assert.False(pool.ErrorOccurred); + + // A new failure enters the blocking period again. + Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); // create #4 fails + Assert.Equal(4, factory.CreateConnectionCallCount); + Assert.True(pool.ErrorOccurred); + + // Assert: the wait was reset to the initial 5s (not the accumulated 20s). + fakeTime.Advance(TimeSpan.FromSeconds(4)); + Assert.True(pool.ErrorOccurred); // still blocked just before the 5s mark + fakeTime.Advance(TimeSpan.FromSeconds(1)); + Assert.False(pool.ErrorOccurred); // cleared exactly at 5s, proving the backoff reset + } + + /// + /// Verifies that a timer-driven exit alone does NOT reset the backoff: when a failure recurs + /// after the exit timer fires (with no intervening successful create), the next blocking period + /// uses the doubled wait (10s, not the initial 5s). Confirms that only + /// — invoked on a successful create — resets the + /// backoff at the pool level. Drives timing deterministically with an injected + /// . + /// + [Fact] + public void TryGetConnection_FailingAgainAfterExitTimer_StillDoublesBackoff() + { + // Arrange: every create fails, so the backoff is never reset by a success. + SqlException failure = SqlExceptionHelper.CreateSqlException("server unreachable"); + var factory = new ConfigurableSqlConnectionFactory(_ => throw failure); + var fakeTime = new FakeTimeProvider(); + var pool = CreatePool(factory, timeProvider: fakeTime); + using var owner = new SqlConnection(); + + // Act: first failure enters the blocking period (initial 5s wait). + Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); + Assert.Equal(1, factory.CreateConnectionCallCount); + + // Advance past the 5s wait so the exit timer fires (no success -> backoff not reset). + fakeTime.Advance(TimeSpan.FromSeconds(5)); + Assert.False(pool.ErrorOccurred); + + // A new failure re-enters the blocking period using the doubled 10s wait. The factory is + // invoked again, confirming the request reached creation rather than fast-failing. + Assert.Throws(() => TryGetConnectionSync(pool, owner, out _)); + Assert.Equal(2, factory.CreateConnectionCallCount); + Assert.True(pool.ErrorOccurred); + + // Assert: still blocked at the original 5s mark (proves the wait is not 5s)... + fakeTime.Advance(TimeSpan.FromSeconds(5)); + Assert.True(pool.ErrorOccurred); + + // ...and not cleared until the full doubled 10s elapses. + fakeTime.Advance(TimeSpan.FromSeconds(4)); + Assert.True(pool.ErrorOccurred); + fakeTime.Advance(TimeSpan.FromSeconds(1)); + Assert.False(pool.ErrorOccurred); // cleared exactly at 10s, proving the backoff doubled + } + + /// + /// test double whose CreateConnection behavior is + /// supplied per-call (to throw or return a mock connection) and which records how many times + /// the factory was invoked so tests can assert fast-fail (no re-invocation) versus retry. + /// + internal sealed class ConfigurableSqlConnectionFactory : SqlConnectionFactory + { + private readonly Func _createBehavior; + private int _callCount; + + /// + /// The number of times CreateConnection has been invoked. The argument passed to + /// the behavior delegate is the 1-based invocation index. + /// + internal int CreateConnectionCallCount => Volatile.Read(ref _callCount); + + internal ConfigurableSqlConnectionFactory(Func createBehavior) + => _createBehavior = createBehavior; + + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + int call = Interlocked.Increment(ref _callCount); + return _createBehavior(call); + } + } + + /// + /// Minimal stub used as a successful create result. + /// Duplicated locally so this test file remains self-contained, mirroring the helpers in the + /// sibling WaitHandleDbConnectionPool test files. + /// + internal sealed class MockDbConnectionInternal : DbConnectionInternal + { + public override string ServerVersion => "Mock"; + + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) + => throw new NotImplementedException(); + + public override void EnlistTransaction(Transaction? transaction) + { + if (transaction != null) + { + EnlistedTransaction = transaction; + } + } + + protected override void Activate(Transaction? transaction) + { + EnlistedTransaction = transaction; + } + + protected override void Deactivate() + { + } + + internal override void ResetConnection() + { + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs index 52bf9ccb29..7268728c8b 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs @@ -67,19 +67,6 @@ public void Shutdown_DisposesCleanupTimer() Assert.Null(pool._cleanupTimer); } - // Error timer is disposed when present. - [Fact] - public void Shutdown_DisposesErrorTimer_WhenPresent() - { - var pool = CreatePool(); - // Inject a real Timer into _errorTimer to mimic an error-state pool. - pool._errorTimer = new Timer(_ => { }, null, Timeout.Infinite, Timeout.Infinite); - - pool.Shutdown(); - - Assert.Null(pool._errorTimer); - } - // Drains idle stacks. [Fact] public void Shutdown_DrainsIdleStacks() @@ -130,17 +117,6 @@ public void CleanupCallback_AfterShutdown_IsNoOp() Assert.Equal(DbConnectionPoolState.ShuttingDown, pool.State); } - // Error callback after shutdown is a no-op. - [Fact] - public void ErrorCallback_AfterShutdown_IsNoOp() - { - var pool = CreatePool(); - pool.Shutdown(); - - var ex = Record.Exception(() => pool.ErrorCallback(state: null)); - Assert.Null(ex); - } - // Sync caller arriving after shutdown gets a null connection (factory will // see this and return up the retry chain). The pool's TryGetConnection short-circuits // on State != Running. From b23aabe0b6b090c2374bab0d12583bc1f3887818 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Tue, 14 Jul 2026 07:35:11 -0700 Subject: [PATCH 185/298] Introduce Async API counterparts for AE base class (#3673) --- .../SqlColumnEncryptionKeyStoreProvider.xml | 273 ++++++++++++++-- specs/002-async-always-encrypted/spec.md | 292 ++++++++++++++++++ .../ref/Microsoft.Data.SqlClient.cs | 8 + .../SqlColumnEncryptionKeyStoreProvider.cs | 75 +++++ ...mnEncryptionKeyStoreProviderAsyncShould.cs | 289 +++++++++++++++++ 5 files changed, 914 insertions(+), 23 deletions(-) create mode 100644 specs/002-async-always-encrypted/spec.md create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlColumnEncryptionKeyStoreProviderAsyncShould.cs diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml index 401258f9e5..c3990851c5 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml @@ -11,51 +11,159 @@ + + Decrypts the specified encrypted value of a column encryption key. + The encrypted value is expected to be encrypted using the column + master key with the specified key path and using the specified + algorithm. + - The master key path. + The column master key path. The path format is specific to the key + store provider implementation (e.g. a thumbprint for the certificate + store, or a key identifier URL for Azure Key Vault). - The encryption algorithm. + The encryption algorithm name. For example, + RSA_OAEP. The encrypted column encryption key. + + Returns a representing the decrypted + column encryption key. + + + - Decrypts the specified encrypted value of a column encryption key. The encrypted value is expected to be encrypted using the column master key with the specified key path and using the specified algorithm. + Asynchronously decrypts the specified encrypted value of a column + encryption key. The encrypted value is expected to be encrypted using + the column master key with the specified key path and using the + specified algorithm. + + The column master key path. The path format is specific to the key + store provider implementation (e.g. a thumbprint for the certificate + store, or a key identifier URL for Azure Key Vault). + + + The encryption algorithm name. For example, + RSA_OAEP. + + + The encrypted column encryption key. + + + A token to cancel the asynchronous operation. + - Returns . The decrypted column encryption key. + A task that returns a representing + the decrypted column encryption key on completion. - + + + The default implementation first checks the + and returns a canceled task + if cancellation has been requested. Otherwise, it calls the + synchronous + + method and wraps the result in a completed task. If the synchronous + method throws, the exception is caught and a faulted task is + returned rather than throwing synchronously. + + + Derived classes that perform I/O (such as calls to Azure Key Vault) + should override this method with a truly asynchronous + implementation that honors the cancellation token. + + + + + Encrypts a column encryption key using the column master key with + the specified key path and using the specified algorithm. + - The master key path. + The column master key path. The path format is specific to the key + store provider implementation (e.g. a thumbprint for the certificate + store, or a key identifier URL for Azure Key Vault). - The encryption algorithm. + The encryption algorithm name. For example, + RSA_OAEP. - The plaintext column encryption key. + The column encryption key to encrypt. + + Returns a representing the encrypted + column encryption key. + + + - Encrypts a column encryption key using the column master key with the specified key path and using the specified algorithm. + Asynchronously encrypts a column encryption key using the column + master key with the specified key path and using the specified + algorithm. + + The column master key path. The path format is specific to the key + store provider implementation (e.g. a thumbprint for the certificate + store, or a key identifier URL for Azure Key Vault). + + + The encryption algorithm name. For example, + RSA_OAEP. + + + The column encryption key to encrypt. + + + A token to cancel the asynchronous operation. + - Returns . The encrypted column encryption key. + A task that returns a representing + the encrypted column encryption key on completion. - + + + The default implementation first checks the + and returns a canceled task + if cancellation has been requested. Otherwise, it calls the + synchronous + + method and wraps the result in a completed task. If the synchronous + method throws, the exception is caught and a faulted task is + returned rather than throwing synchronously. + + + Derived classes that perform I/O (such as calls to Azure Key Vault) + should override this method with a truly asynchronous + implementation that honors the cancellation token. + + + + + When implemented in a derived class, signs the column master key + metadata with the column master key referenced by the + parameter. + - The column master key path. + The column master key path. The path format is specific to the key + store provider implementation (e.g. a thumbprint for the certificate + store, or a key identifier URL for Azure Key Vault). - to indicate that the column master key supports enclave computations; otherwise, . + to indicate that the column master key + supports enclave computations; otherwise, + . When , the + generated signature covers the enclave-enabled property so that + the metadata can later be verified for enclave use. - - When implemented in a derived class, digitally signs the column master key metadata with the column master key referenced by the parameter. The input values used to generate the signature should be the specified values of the and parameters. - - The signature of the column master key metadata. + Returns the signature of the column master key metadata. The format + is provider-specific. @@ -69,23 +177,142 @@ In all cases. + + + When implemented in a derived class, asynchronously signs the column + master key metadata with the column master key referenced by the + parameter. + + + The column master key path. The path format is specific to the key + store provider implementation (e.g. a thumbprint for the certificate + store, or a key identifier URL for Azure Key Vault). + + + to indicate that the column master key + supports enclave computations; otherwise, + . When , the + generated signature covers the enclave-enabled property so that + the metadata can later be verified for enclave use. + + + A token to cancel the asynchronous operation. + + + A task that, when completed, returns a + representing the signature of the column master key metadata. The + signature format is provider-specific. + + + + The default implementation first checks the + and returns a canceled task + if cancellation has been requested. Otherwise, it calls the + synchronous + + method, which throws a + by default. In + this case, the returned task will be faulted with + rather than + throwing synchronously. + + + Derived classes that perform I/O should override this method with + a truly asynchronous implementation that honors the cancellation + token. + + + Key store providers that wish to use enclaves with + Always Encrypted + should override this method with a truly asynchronous + implementation when the signing operation involves I/O. + + + + + 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 + . + - The column master key path. + The column master key path. The path format is specific to the key + store provider implementation (e.g. a thumbprint for the certificate + store, or a key identifier URL for Azure Key Vault). - Indicates whether the column master key supports enclave computations. + Indicates whether the column master key supports enclave + computations. This value must match what was passed to + + when the signature was generated. - The signature of the column master key metadata. + The signature to verify, as previously returned by + . + The format is provider-specific. + + When implemented in a derived class, the method is expected to + return if the specified signature is valid, + or if the specified signature is not valid. + The default implementation throws + . + + + - 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, asynchronously verifies the + specified signature is valid for the column master key with the + specified key path and the specified enclave behavior. The default + implementation returns a faulted task with + . + + The column master key path. The path format is specific to the key + store provider implementation (e.g. a thumbprint for the certificate + store, or a key identifier URL for Azure Key Vault). + + + Indicates whether the column master key supports enclave + computations. This value must match what was passed to + + when the signature was generated. + + + The signature to verify, as previously returned by + . + The format is provider-specific. + + + A token to cancel the asynchronous operation. + - 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`. + A task that, when completed, returns if the + specified signature is valid, or if it is + not valid. - + + + The default implementation first checks the + and returns a canceled task + if cancellation has been requested. Otherwise, it calls the + synchronous + + method, which throws a + by default. In + this case, the returned task will be faulted with + rather than + throwing synchronously. + + + Derived classes that perform I/O should override this method with + a truly asynchronous implementation that honors the cancellation + token. + + + Gets or sets the lifespan of the decrypted column encryption key in the cache. Once the timespan has elapsed, the decrypted column encryption key is discarded and must be revalidated. diff --git a/specs/002-async-always-encrypted/spec.md b/specs/002-async-always-encrypted/spec.md new file mode 100644 index 0000000000..dea4d526f2 --- /dev/null +++ b/specs/002-async-always-encrypted/spec.md @@ -0,0 +1,292 @@ +# Feature Specification: Async-Friendly Always Encrypted Support + +**Issue**: [#3672](https://github.com/dotnet/SqlClient/issues/3672) +**Feature Branch**: `dev/cheena/async-ae` +**Created**: 2026-05-13 +**Status**: Draft +**Milestone**: 7.1.0-preview2 + +## Summary + +Eliminate sync-over-async in Always Encrypted (AE) code paths by adding async counterparts to all provider base classes and implementations, then rewiring async execution paths in SqlCommand to use them. Currently, async operations like `ExecuteReaderAsync` block ThreadPool threads when AE is enabled because all key decryption, signature verification, and enclave attestation calls are synchronous — including HTTP calls to Azure Key Vault and attestation services. + +## User Scenarios & Testing + +### User Story 1 — Async Key Decryption with Azure Key Vault + +As an application developer using Always Encrypted with Azure Key Vault, I want `ExecuteReaderAsync` to perform column encryption key (CEK) decryption asynchronously via the Azure Key Vault SDK, so that my async code paths do not block ThreadPool threads waiting for HTTP responses from Azure Key Vault. + +**Acceptance Scenarios**: + +1. **Given** a table with AE-encrypted columns and AKV-stored CMK, **When** `ExecuteReaderAsync` is called, **Then** CEK decryption calls `CryptographyClient.UnwrapKeyAsync()` instead of the sync `UnwrapKey()`. +2. **Given** a `CancellationToken` passed to `ExecuteReaderAsync`, **When** the token is cancelled during AKV key decryption, **Then** the operation is cancelled and a `TaskCanceledException` is thrown. +3. **Given** a custom provider that does NOT override async methods, **When** `ExecuteReaderAsync` is called, **Then** the base class fallback wraps the sync method in `Task.FromResult` and completes successfully. + +### User Story 2 — Async Enclave Attestation + +As an application developer using Always Encrypted with secure enclaves, I want enclave attestation to be performed asynchronously, so that HTTP calls to attestation services do not block my async query execution path. + +**Acceptance Scenarios**: + +1. **Given** an enclave-enabled query using Azure Attestation, **When** `ExecuteReaderAsync` is called, **Then** `ConfigurationManager.GetConfigurationAsync()` is used. +2. **Given** an enclave-enabled query using HGS attestation, **When** `ExecuteReaderAsync` is called, **Then** the HTTP call uses `await HttpClient.GetStreamAsync()` instead of `.Result`. + +### User Story 3 — Sync Path Preservation + +All existing sync methods are preserved as-is. The `isAsync` flag in `SqlCommand` determines which path is taken. Zero behavioral change for sync callers. + +### User Story 4 — Custom Provider Backward Compatibility + +Custom providers that only implement sync `DecryptColumnEncryptionKey` continue to work. The base class default wraps the sync call in `Task.FromResult`. If the sync method throws, the returned Task is faulted (not a synchronous throw). + +### Edge Cases + +- **Cache hit for CEK**: Async path returns immediately from cache without invoking the provider. +- **Mixed sync/async on `SemaphoreSlim`**: For sites still using `SemaphoreSlim` (non-`IMemoryCache` caches), mixing `.Wait()` and `.WaitAsync()` is safe per .NET docs. For `IMemoryCache`-backed caches, the async path uses `GetOrCreateAsync` and does not need `SemaphoreSlim`. +- **CancellationToken in default fallback**: The base class wraps sync methods and checks the token before invocation; sync calls themselves are not cancellable. +- **Concurrent cache misses**: Two threads may independently fetch the same key. Last write wins (idempotent). See FR-014 for optional deduplication. +- **Enclave session cache hit**: Returns from cache without performing attestation. +- **`ColumnEncryptionKeyCacheTtl` mutation**: Property mutation must remain inside the semaphore-held section in `SqlSymmetricKeyCache` to avoid races. + +## Design Decisions + +### 1. `Task` for Public APIs + +Public virtual methods use `Task`, not `ValueTask`. The primary beneficiary (AKV) always allocates a Task due to HTTP I/O. `ValueTask` has stricter usage rules that would be error-prone for third-party implementers. + +### 2. `CancellationToken` with Default Value + +Each async method accepts a `CancellationToken cancellationToken = default` parameter. This is a single `virtual` overload — callers can omit the token for convenience, and derived classes override a single method. The default implementation checks the token for cancellation before invoking the synchronous method. + +### 3. Certificate/CNG/CSP Providers Keep Default Fallback + +No async overrides — these perform local crypto (CPU-bound) that doesn't benefit from async. The base class `Task.FromResult` fallback is sufficient. + +### 4. Async Methods Return Tuples Instead of `out` Parameters + +C# async methods cannot have `out` parameters. Internal enclave methods return tuples (e.g., `Task<(SqlEnclaveSession, long, byte[], int)>`). `SqlSecurityUtility.DecryptSymmetricKeyAsync` returns `Task<(SqlClientSymmetricKey Key, SqlEncryptionKeyInfo KeyInfoChosen)>`. + +### 5. `ConfigureAwait(false)` on Every `await` + +Every `await` in library code MUST use `.ConfigureAwait(false)`. Omission causes deadlocks in ASP.NET Framework, WPF, and WinForms hosts. This is a hard correctness requirement enforced in CI. + +### 6. `MemoryCache.GetOrCreateAsync` for Async Cache Patterns + +**API**: [`CacheExtensions.GetOrCreateAsync(IMemoryCache, Object, Func>)`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.cacheextensions.getorcreateasync?view=net-10.0-pp) + +For `IMemoryCache`-backed caches, use `GetOrCreateAsync` with an async factory delegate instead of `SemaphoreSlim` + manual `TryGetValue`/`Set`. This eliminates lock-during-I/O without requiring the check-release-fetch-relock pattern. + +```csharp +var result = await _cache.GetOrCreateAsync(cacheKey, async entry => +{ + entry.SlidingExpiration = _columnEncryptionKeyCacheTtl; + return await provider.DecryptColumnEncryptionKeyAsync( + masterKeyPath, algorithm, encryptedKey, ct) + .ConfigureAwait(false); +}).ConfigureAwait(false); +``` + +**Affected**: `SqlColumnEncryptionAzureKeyVaultProvider._columnEncryptionKeyCache` + +For sites NOT using `IMemoryCache` (custom `Dictionary`/`ConcurrentDictionary` caches), use the check-release-fetch-relock pattern with `SemaphoreSlim`: + +```csharp +await semaphore.WaitAsync(ct).ConfigureAwait(false); +try +{ + if (cache.TryGetValue(key, out var cached)) + return cached; +} +finally { semaphore.Release(); } // release BEFORE I/O + +var value = await FetchAsync(key, ct).ConfigureAwait(false); + +await semaphore.WaitAsync(ct).ConfigureAwait(false); +try { cache.Set(key, value); } +finally { semaphore.Release(); } +return value; +``` + +**Affected**: `SqlSymmetricKeyCache._cacheLock`, `AzureSqlKeyCryptographer._keyDictionarySemaphore` + +### 7. `GetParameterEncryptionDataReaderAsync` Must Be True `async Task` + +Remove the `Task.Run(() => { ... })` wrapper entirely. The method must `await` each I/O step directly using clean `async/await`, not the `AsyncHelper.ContinueTaskWithState` callback pattern. + +## Requirements + +### Functional Requirements + +| ID | Requirement | +|----|-------------| +| FR-001 | Add `virtual` async counterparts for `DecryptColumnEncryptionKey`, `EncryptColumnEncryptionKey`, `SignColumnMasterKeyMetadata`, and `VerifyColumnMasterKeyMetadata` to `SqlColumnEncryptionKeyStoreProvider`. | +| FR-002 | Each async method accepts `CancellationToken cancellationToken = default` as a single virtual overload. Default checks token before sync fallback. | +| FR-003 | Default async implementations wrap sync via `Task.FromResult`; return faulted Tasks (not synchronous throws) on exception. | +| FR-004 | `SqlColumnEncryptionAzureKeyVaultProvider` overrides all 4 async methods with truly async Azure SDK calls. | +| FR-005 | AKV provider propagates `CancellationToken` to all Azure SDK calls. | +| FR-006 | Internal async counterparts added to enclave provider base class and all implementations. | +| FR-007 | Enclave providers with HTTP I/O (Azure Attestation, HGS) implement truly async attestation. | +| FR-008 | Async utility methods added: `SqlSecurityUtility.DecryptSymmetricKeyAsync` (returns tuple), `VerifyColumnMasterKeySignatureAsync`, `SqlSymmetricKeyCache.GetKeyAsync`. All propagate `CancellationToken`. | +| FR-009 | `SqlCommand` async paths use async AE methods when `isAsync=true`. `GetParameterEncryptionDataReaderAsync` becomes true `async Task`. | +| FR-010 | No changes to existing sync code paths or behavior. | +| FR-011 | All new public APIs declared in `src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs`. | +| FR-012 | Async caching: `IMemoryCache` sites use `GetOrCreateAsync`; custom-cache sites use check-release-fetch-relock with `SemaphoreSlim`. No lock held during I/O. | +| FR-013 | Every `await` uses `.ConfigureAwait(false)`. Enforced in CI. | +| FR-014 | Concurrent cache misses tolerated without deadlock. Last write wins. Optional deduplication via `ConcurrentDictionary>`. | +| FR-015 | `lock(enclaveCacheLock)` replaced with `SemaphoreSlim` if async operations must run before session storage. Sync-only paths may retain `lock`. | + +### Key Entities + +| Entity | Scope | Changes | +|--------|-------|---------| +| `SqlColumnEncryptionKeyStoreProvider` | public abstract | +4 virtual async methods with `CancellationToken = default` | +| `SqlColumnEncryptionAzureKeyVaultProvider` | public (separate package) | Overrides all 4 async methods with Azure SDK async calls | +| `AzureSqlKeyCryptographer` | internal (AKV package) | +async counterparts for `AddKey`, `UnwrapKey`, `WrapKey`, `SignData`, `VerifyData` | +| `SqlColumnEncryptionEnclaveProvider` | internal abstract | +4 abstract async methods with tuple returns | +| `EnclaveDelegate` | internal sealed | +async dispatcher methods | +| `SqlSecurityUtility` | internal static | +`DecryptSymmetricKeyAsync`, `VerifyColumnMasterKeySignatureAsync` | +| `SqlSymmetricKeyCache` | internal sealed, singleton | +`GetKeyAsync` with check-release-fetch-relock | + +## Implementation Phases + +### Phase 1: Base Class API (PR #3673 — in progress) + +*Depends on: nothing* + +Add 4 virtual async methods + 4 convenience overloads to `SqlColumnEncryptionKeyStoreProvider`, update ref assemblies and XML docs. + +**Files**: +- `src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionKeyStoreProvider.cs` +- `src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs` +- `doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml` + +### Phase 2: Key Store Provider Implementations + +*Depends on: Phase 1. Parallel with Phase 3.* + +#### 2A. AzureKeyVaultProvider (truly async) + +- `AzureSqlKeyCryptographer.cs` — Add async counterparts using Azure SDK. `AddKeyAsync` uses check-release-fetch-relock on `_keyDictionarySemaphore` (uses `Dictionary`, not `IMemoryCache`). +- `SqlColumnEncryptionAzureKeyVaultProvider.cs` — Override 4 async methods. Use `GetOrCreateAsync` on `_columnEncryptionKeyCache`: + +**Async key-unwrap flow**: +1. `await _columnEncryptionKeyCache.GetOrCreateAsync(cacheKey, async entry => { ... }).ConfigureAwait(false)` +2. Inside factory: `await KeyCryptographer.UnwrapKeyAsync(..., ct).ConfigureAwait(false)` — I/O runs without user-managed locks +3. Configure entry expiration via `ICacheEntry` +4. `MemoryCache` handles cache insertion + +The sync path remains unchanged (`_cacheSemaphore.Wait()` + `GetOrCreate`). `GetOrCreateSignatureVerificationResult` stays sync (CPU-bound RSA). + +#### 2B–2D. CertificateStore, CNG, CSP Providers + +No changes — default base class fallback is sufficient. + +### Phase 3: Enclave Provider Async APIs + +*Depends on: nothing. Parallel with Phase 2.* + +- `SqlColumnEncryptionEnclaveProvider.cs` — 4 internal abstract async methods +- `EnclaveProviderBase.cs` — `GetEnclaveSessionHelperAsync()` +- `EnclaveSessionCache.cs` — Replace `lock` with `SemaphoreSlim` where async operations precede session storage (FR-015) +- `NoneAttestationEnclaveProvider.cs` — Sync wrappers (no real I/O) +- `AzureAttestationBasedEnclaveProvider.cs` — Truly async (`GetConfigurationAsync()`) +- `VirtualSecureModeEnclaveProviderBase.cs` — Abstract `MakeRequestAsync`, `VerifyAttestationInfoAsync` +- `VirtualSecureModeEnclaveProvider.cs` — Truly async (`HttpClient.GetStreamAsync`) +- `EnclaveDelegate.Crypto.cs` — Async dispatcher methods + +### Phase 4: Async Utility Layer + +*Depends on: Phase 1.* + +- `SqlSecurityUtility.cs`: + - `DecryptSymmetricKeyAsync` → returns `Task<(SqlClientSymmetricKey Key, SqlEncryptionKeyInfo KeyInfoChosen)>` + - `GetKeyFromLocalProvidersAsync` — propagates `CancellationToken` + - `VerifyColumnMasterKeySignatureAsync` — propagates `CancellationToken` +- `SqlSymmetricKeyCache.cs`: + - `GetKeyAsync` — check-release-fetch-relock on static `_cacheLock`; propagates `CancellationToken` + +> **Note**: Phases 1–4 deliver async infrastructure. End-to-end benefit requires Phase 5. + +### Phase 5: Async Call-Site Integration + +*Depends on: Phase 4.* + +- `SqlCommand.Encryption.cs`: + 1. `ReadDescribeEncryptionParameterResultsKeysAsync` → `VerifyColumnMasterKeySignatureAsync()` + 2. `ReadDescribeEncryptionParameterResultsMetadataAsync` → `DecryptSymmetricKeyAsync()` (**highest impact**) + 3. `ReadDescribeEncryptionParameterResultsAsync` → orchestrates above + 4. **Refactor `GetParameterEncryptionDataReaderAsync`** into true `async Task` — remove `Task.Run` wrapper + 5. `TryFetchInputParameterEncryptionInfoAsync` → `EnclaveDelegate.GetEnclaveSessionAsync()` + +### Phase 6: Testing & Documentation + +*Spans all phases.* + +**Unit Tests** (`tests/UnitTests/`): Default fallback, cancellation, faulted Tasks +**Functional Tests** (`tests/FunctionalTests/`): `DummyKeyStoreProvider` async overrides +**Manual Tests** (`tests/ManualTests/`): AKV end-to-end, enclave attestation async, encrypted query async +**CI Gate**: Roslyn analyzer or grep check for `.ConfigureAwait(false)` on every `await` +**Documentation**: XML docs, `doc/samples/` async AE examples + +## New Public API Surface + +### SqlColumnEncryptionKeyStoreProvider + +```csharp +public abstract class SqlColumnEncryptionKeyStoreProvider +{ + // Existing (unchanged) + public abstract byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey); + public abstract byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey); + public virtual byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations); + public virtual bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature); + public virtual TimeSpan? ColumnEncryptionKeyCacheTtl { get; set; } + + // NEW: Async with optional CancellationToken (virtual, overridable) + public virtual Task DecryptColumnEncryptionKeyAsync(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey, CancellationToken cancellationToken = default); + public virtual Task EncryptColumnEncryptionKeyAsync(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey, CancellationToken cancellationToken = default); + public virtual Task SignColumnMasterKeyMetadataAsync(string masterKeyPath, bool allowEnclaveComputations, CancellationToken cancellationToken = default); + public virtual Task VerifyColumnMasterKeyMetadataAsync(string masterKeyPath, bool allowEnclaveComputations, byte[] signature, CancellationToken cancellationToken = default); +} +``` + +### SqlColumnEncryptionAzureKeyVaultProvider + +```csharp +public class SqlColumnEncryptionAzureKeyVaultProvider : SqlColumnEncryptionKeyStoreProvider +{ + // Overrides — truly async via Azure SDK + public override Task DecryptColumnEncryptionKeyAsync(..., CancellationToken cancellationToken = default); + public override Task EncryptColumnEncryptionKeyAsync(..., CancellationToken cancellationToken = default); + public override Task SignColumnMasterKeyMetadataAsync(..., CancellationToken cancellationToken = default); + public override Task VerifyColumnMasterKeyMetadataAsync(..., CancellationToken cancellationToken = default); +} +``` + +## Success Criteria + +| ID | Criterion | +|----|-----------| +| SC-001 | `ExecuteReaderAsync` with AE + AKV does NOT block any ThreadPool thread on HTTP I/O. | +| SC-002 | All existing AE tests pass unchanged — zero sync regressions. | +| SC-003 | `Task.Run` is no longer used in `GetParameterEncryptionDataReaderAsync`. | +| SC-004 | `CancellationToken` propagates from `SqlCommand` async methods through to Azure SDK calls. | +| SC-005 | Custom providers with only sync implementations continue to work in async paths. | + +## Assumptions + +- Azure SDK async methods (`UnwrapKeyAsync`, `WrapKeyAsync`, etc.) are stable and production-ready. +- `SemaphoreSlim` supports mixed `.Wait()` / `.WaitAsync()` on the same instance. +- `SqlColumnEncryptionEnclaveProvider` is internal — API changes have no public impact. +- The existing `isAsync` flag in `SqlCommand` determines sync vs async AE paths. +- PR #3673 has not shipped — adding `CancellationToken` is non-breaking. +- Certificate/CNG/CSP providers perform local crypto; `Task.FromResult` is sufficient. +- `Microsoft.Extensions.Caching.Abstractions` (already referenced by AKV provider) provides `GetOrCreateAsync`. + +## Further Considerations + +1. **`ConfigureAwait(false)` CI enforcement**: Add a Roslyn analyzer or grep-based check to prevent regressions. +2. **Third-party provider guidance**: Document how custom providers should override async methods, use `ConfigureAwait(false)`, support cancellation, and avoid lock-during-I/O. +3. **Performance benchmarking**: After Phase 5, quantify ThreadPool savings and latency improvements under load. +4. **In-flight request deduplication**: Optional `ConcurrentDictionary>` for concurrent cold-cache AKV requests (FR-014). +5. **`ValueTask` for internal fast paths**: Internal methods like `SqlSymmetricKeyCache.GetKeyAsync` have high cache-hit rates — `ValueTask` avoids Task allocations. P2 optimization with no API impact. diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs index f0e785a36e..a5e3f1a473 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs @@ -716,12 +716,20 @@ public abstract class SqlColumnEncryptionKeyStoreProvider protected SqlColumnEncryptionKeyStoreProvider() { } /// public abstract byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey); + /// + public virtual System.Threading.Tasks.Task DecryptColumnEncryptionKeyAsync(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey, System.Threading.CancellationToken cancellationToken = default) { throw null; } /// public abstract byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey); + /// + public virtual System.Threading.Tasks.Task EncryptColumnEncryptionKeyAsync(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey, System.Threading.CancellationToken cancellationToken = default) { throw null; } /// public virtual byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations) { throw null; } + /// + public virtual System.Threading.Tasks.Task SignColumnMasterKeyMetadataAsync(string masterKeyPath, bool allowEnclaveComputations, System.Threading.CancellationToken cancellationToken = default) { throw null; } /// public virtual bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { throw null; } + /// + public virtual System.Threading.Tasks.Task VerifyColumnMasterKeyMetadataAsync(string masterKeyPath, bool allowEnclaveComputations, byte[] signature, System.Threading.CancellationToken cancellationToken = default) { throw null; } /// public virtual System.TimeSpan? ColumnEncryptionKeyCacheTtl { get { throw null; } set { } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionKeyStoreProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionKeyStoreProvider.cs index 641e09e8ba..2daa2dbab3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionKeyStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionKeyStoreProvider.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { @@ -16,19 +19,91 @@ public abstract class SqlColumnEncryptionKeyStoreProvider /// public abstract byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey); + /// + public virtual Task DecryptColumnEncryptionKeyAsync(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + try + { + return Task.FromResult(DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, encryptedColumnEncryptionKey)); + } + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) + { + return Task.FromException(e); + } + } + /// public abstract byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey); + /// + public virtual Task EncryptColumnEncryptionKeyAsync(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + try + { + return Task.FromResult(EncryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, columnEncryptionKey)); + } + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) + { + return Task.FromException(e); + } + } + /// public virtual byte[] SignColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations) { throw new NotImplementedException(); } + /// + public virtual Task SignColumnMasterKeyMetadataAsync(string masterKeyPath, bool allowEnclaveComputations, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + try + { + return Task.FromResult(SignColumnMasterKeyMetadata(masterKeyPath, allowEnclaveComputations)); + } + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) + { + return Task.FromException(e); + } + } + /// public virtual bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { throw new NotImplementedException(); } + + /// + public virtual Task VerifyColumnMasterKeyMetadataAsync(string masterKeyPath, bool allowEnclaveComputations, byte[] signature, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + try + { + return Task.FromResult(VerifyColumnMasterKeyMetadata(masterKeyPath, allowEnclaveComputations, signature)); + } + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) + { + return Task.FromException(e); + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlColumnEncryptionKeyStoreProviderAsyncShould.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlColumnEncryptionKeyStoreProviderAsyncShould.cs new file mode 100644 index 0000000000..ae78544227 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AlwaysEncrypted/SqlColumnEncryptionKeyStoreProviderAsyncShould.cs @@ -0,0 +1,289 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.AlwaysEncrypted +{ + /// + /// Tests for the async virtual methods on , + /// verifying default fallback behavior, cancellation, faulted tasks, and convenience overloads. + /// + public class SqlColumnEncryptionKeyStoreProviderAsyncShould + { + #region Default fallback behavior + + /// + /// Verifies that the default async decrypt implementation wraps the sync result via Task.FromResult. + /// + [Fact] + public async Task DecryptColumnEncryptionKeyAsync_DefaultFallback_ReturnsSameResultAsSyncMethod() + { + var provider = new TestKeyStoreProvider(); + byte[] expected = provider.DecryptColumnEncryptionKey("path", "algo", new byte[] { 1, 2, 3 }); + byte[] actual = await provider.DecryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 1, 2, 3 }, CancellationToken.None); + Assert.Equal(expected, actual); + } + + /// + /// Verifies that the default async encrypt implementation wraps the sync result via Task.FromResult. + /// + [Fact] + public async Task EncryptColumnEncryptionKeyAsync_DefaultFallback_ReturnsSameResultAsSyncMethod() + { + var provider = new TestKeyStoreProvider(); + byte[] expected = provider.EncryptColumnEncryptionKey("path", "algo", new byte[] { 4, 5, 6 }); + byte[] actual = await provider.EncryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 4, 5, 6 }, CancellationToken.None); + Assert.Equal(expected, actual); + } + + #endregion + + #region Faulted task behavior + + /// + /// Verifies that when the sync decrypt method throws, the async version returns a faulted Task + /// rather than throwing synchronously. + /// + [Fact] + public async Task DecryptColumnEncryptionKeyAsync_WhenSyncThrows_ReturnsFaultedTask() + { + var provider = new ThrowingKeyStoreProvider(); + Task task = provider.DecryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 1 }, CancellationToken.None); + + Assert.True(task.IsFaulted); + await Assert.ThrowsAsync(() => task); + } + + /// + /// Verifies that when the sync encrypt method throws, the async version returns a faulted Task + /// rather than throwing synchronously. + /// + [Fact] + public async Task EncryptColumnEncryptionKeyAsync_WhenSyncThrows_ReturnsFaultedTask() + { + var provider = new ThrowingKeyStoreProvider(); + Task task = provider.EncryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 1 }, CancellationToken.None); + + Assert.True(task.IsFaulted); + await Assert.ThrowsAsync(() => task); + } + + /// + /// Verifies that the base class SignColumnMasterKeyMetadata throws NotImplementedException, + /// and the async version surfaces this as a faulted Task. + /// + [Fact] + public async Task SignColumnMasterKeyMetadataAsync_DefaultImplementation_ReturnsFaultedTask() + { + var provider = new TestKeyStoreProvider(); + Task task = provider.SignColumnMasterKeyMetadataAsync("path", true, CancellationToken.None); + + Assert.True(task.IsFaulted); + await Assert.ThrowsAsync(() => task); + } + + /// + /// Verifies that the base class VerifyColumnMasterKeyMetadata throws NotImplementedException, + /// and the async version surfaces this as a faulted Task. + /// + [Fact] + public async Task VerifyColumnMasterKeyMetadataAsync_DefaultImplementation_ReturnsFaultedTask() + { + var provider = new TestKeyStoreProvider(); + Task task = provider.VerifyColumnMasterKeyMetadataAsync("path", true, new byte[] { 1 }, CancellationToken.None); + + Assert.True(task.IsFaulted); + await Assert.ThrowsAsync(() => task); + } + + #endregion + + #region Cancellation behavior + + /// + /// Verifies that passing an already-cancelled token returns a cancelled Task immediately. + /// + [Fact] + public void DecryptColumnEncryptionKeyAsync_CancelledToken_ReturnsCancelledTask() + { + var provider = new TestKeyStoreProvider(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + Task task = provider.DecryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 1, 2, 3 }, cts.Token); + + Assert.True(task.IsCanceled); + } + + /// + /// Verifies that passing an already-cancelled token returns a cancelled Task immediately. + /// + [Fact] + public void EncryptColumnEncryptionKeyAsync_CancelledToken_ReturnsCancelledTask() + { + var provider = new TestKeyStoreProvider(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + Task task = provider.EncryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 4, 5, 6 }, cts.Token); + + Assert.True(task.IsCanceled); + } + + /// + /// Verifies that passing an already-cancelled token returns a cancelled Task immediately. + /// + [Fact] + public void SignColumnMasterKeyMetadataAsync_CancelledToken_ReturnsCancelledTask() + { + var provider = new TestKeyStoreProvider(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + Task task = provider.SignColumnMasterKeyMetadataAsync("path", true, cts.Token); + + Assert.True(task.IsCanceled); + } + + /// + /// Verifies that passing an already-cancelled token returns a cancelled Task immediately. + /// + [Fact] + public void VerifyColumnMasterKeyMetadataAsync_CancelledToken_ReturnsCancelledTask() + { + var provider = new TestKeyStoreProvider(); + using var cts = new CancellationTokenSource(); + cts.Cancel(); + + Task task = provider.VerifyColumnMasterKeyMetadataAsync("path", true, new byte[] { 1 }, cts.Token); + + Assert.True(task.IsCanceled); + } + + /// + /// Verifies that a non-cancelled (live) token does not interfere with successful decrypt. + /// + [Fact] + public async Task DecryptColumnEncryptionKeyAsync_LiveToken_CompletesSuccessfully() + { + var provider = new TestKeyStoreProvider(); + using var cts = new CancellationTokenSource(); + + byte[] result = await provider.DecryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 7, 8, 9 }, cts.Token); + Assert.Equal(new byte[] { 7, 8, 9 }, result); + } + + /// + /// Verifies that a non-cancelled (live) token does not interfere with successful encrypt. + /// + [Fact] + public async Task EncryptColumnEncryptionKeyAsync_LiveToken_CompletesSuccessfully() + { + var provider = new TestKeyStoreProvider(); + using var cts = new CancellationTokenSource(); + + byte[] result = await provider.EncryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 7, 8, 9 }, cts.Token); + Assert.Equal(new byte[] { 7, 8, 9 }, result); + } + + #endregion + + #region Default CancellationToken parameter + + /// + /// Verifies calling without CancellationToken (uses default) works for decrypt. + /// + [Fact] + public async Task DecryptColumnEncryptionKeyAsync_DefaultCancellationToken_Works() + { + var provider = new TestKeyStoreProvider(); + byte[] result = await provider.DecryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 1, 2, 3 }); + Assert.NotNull(result); + Assert.Equal(new byte[] { 1, 2, 3 }, result); + } + + /// + /// Verifies calling without CancellationToken (uses default) works for encrypt. + /// + [Fact] + public async Task EncryptColumnEncryptionKeyAsync_DefaultCancellationToken_Works() + { + var provider = new TestKeyStoreProvider(); + byte[] result = await provider.EncryptColumnEncryptionKeyAsync("path", "algo", new byte[] { 4, 5, 6 }); + Assert.NotNull(result); + Assert.Equal(new byte[] { 4, 5, 6 }, result); + } + + /// + /// Verifies calling sign without CancellationToken returns a faulted Task + /// (base class throws NotImplementedException). + /// + [Fact] + public async Task SignColumnMasterKeyMetadataAsync_DefaultCancellationToken_ReturnsFaultedTask() + { + var provider = new TestKeyStoreProvider(); + Task task = provider.SignColumnMasterKeyMetadataAsync("path", true); + + Assert.True(task.IsFaulted); + await Assert.ThrowsAsync(() => task); + } + + /// + /// Verifies calling verify without CancellationToken returns a faulted Task + /// (base class throws NotImplementedException). + /// + [Fact] + public async Task VerifyColumnMasterKeyMetadataAsync_DefaultCancellationToken_ReturnsFaultedTask() + { + var provider = new TestKeyStoreProvider(); + Task task = provider.VerifyColumnMasterKeyMetadataAsync("path", true, new byte[] { 1 }); + + Assert.True(task.IsFaulted); + await Assert.ThrowsAsync(() => task); + } + + #endregion + + #region Test helpers + + /// + /// A test provider that implements sync methods with pass-through behavior (returns input as-is). + /// + private class TestKeyStoreProvider : SqlColumnEncryptionKeyStoreProvider + { + public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) + { + return encryptedColumnEncryptionKey; + } + + public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) + { + return columnEncryptionKey; + } + } + + /// + /// A test provider whose sync methods always throw . + /// + private class ThrowingKeyStoreProvider : SqlColumnEncryptionKeyStoreProvider + { + public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey) + { + throw new InvalidOperationException("Decrypt failed"); + } + + public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey) + { + throw new InvalidOperationException("Encrypt failed"); + } + } + + #endregion + } +} From b0e988bfb7f866ac41c3be05ddd1c570cb0f3375 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:47:01 -0300 Subject: [PATCH 186/298] Remove ARM64 special handling from install-dotnet template (#4442) UseDotNet@2 v271+ now correctly detects win-arm64 and installs the matching SDK/runtime, fixing the long-standing bug that installed x86 on ARM64 hosts: https://github.com/microsoft/azure-pipelines-tasks/issues/20300 This removes our workaround: - install-dotnet.yml: drop the ARM64 branch and the custom PowerShell installer step. Replace the architecture enum (x64/x86/arm64) with a forceX86 boolean, since forcing x86 was the only remaining behavior. - Delete install-dotnet-arm64.ps1 (no longer referenced). - ci-run-tests-job.yml: always pass runtimes in UseDotNet@2 format, remove the isArm64 parameter, and use forceX86 for the x86 install. - ci-run-tests-stage.yml: drop the isArm64 passthroughs. - dotnet-sqlclient-ci-core.yml: drop isArm64 from the arm64 matrix entry (the ARM64 agent is still selected via pool/images). --- .../common/steps/install-dotnet-arm64.ps1 | 282 ------------------ eng/pipelines/common/steps/install-dotnet.yml | 124 ++------ .../templates/jobs/ci-run-tests-job.yml | 15 +- .../templates/stages/ci-run-tests-stage.yml | 2 - eng/pipelines/dotnet-sqlclient-ci-core.yml | 1 - 5 files changed, 34 insertions(+), 390 deletions(-) delete mode 100644 eng/pipelines/common/steps/install-dotnet-arm64.ps1 diff --git a/eng/pipelines/common/steps/install-dotnet-arm64.ps1 b/eng/pipelines/common/steps/install-dotnet-arm64.ps1 deleted file mode 100644 index 95d580543b..0000000000 --- a/eng/pipelines/common/steps/install-dotnet-arm64.ps1 +++ /dev/null @@ -1,282 +0,0 @@ -<# -.SYNOPSIS - This script installs dotnet SDKs and Runtimes for ARM64. - -.DESCRIPTION - Special handling is required for ARM64 due to a bug in UseDotNet@2: - - [BUG]: UseDotNet@2 task installs x86 build - https://github.com/microsoft/azure-pipelines-tasks/issues/20300 - - The downloaded dotnet-install.ps1 script is kept in the $InstallDir to avoid - downloading it multiple times during the pipeline job. - - The following environment variables are set for subsequent steps in the pipeline: - - DOTNET_ROOT: Set to $InstallDir. - PATH: $DOTNET_ROOT is prepended to the PATH environment variable. - -.PARAMETER Debug - True to emit debug messages. Default is false. - -.PARAMETER DryRun - True to perform a dry run of the installation. Default is false. - -.PARAMETER GlobalJson - The path to the global.json file that specifies the exact SDK version to - install. Default is 'global.json'. - -.PARAMETER InstallDir - The directory to install the SDKs and Runtimes into, typically the - pipeline's $(Agent.ToolsDirectory)/dotnet directory. - - The dotnet-install.ps1 script is downloaded into this directory if it is not - already present. - - Default is '.'. - -.PARAMETER Runtimes - The versions of the .NET Runtimes to install. These must be in the runtime - channel format of X.Y expected by the dotnet-install.ps1 script. Default is - an empty array. - -.NOTES - 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. -#> - -param -( - [switch]$Debug, - [switch]$DryRun, - [string]$GlobalJson = "global.json", - [string]$InstallDir = '.', - [string[]]$Runtimes = @() -) - -# Stop on all errors. -$ErrorActionPreference = 'Stop' - -# Maximum number of retry attempts for transient install failures (e.g. -# corrupt downloads, network timeouts). -$maxAttempts = 3 -$retryDelaySec = 10 - -#------------------------------------------------------------------------------ -# Invoke dotnet-install.ps1 with retry logic. On each attempt the script is -# called with the supplied $Params. If a non-zero exit code is returned, or -# if the optional $Verify script-block throws, the attempt is considered failed -# and will be retried after a short delay. - -function Invoke-DotNetInstall -{ - param - ( - [Parameter(Mandatory)] - [string]$Description, - - [Parameter(Mandatory)] - [hashtable]$Params, - - [scriptblock]$Verify = $null - ) - - for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) - { - try - { - Write-Host "$Description (attempt $attempt of $maxAttempts)" - - $global:LASTEXITCODE = 0 - & "$InstallDir/dotnet-install.ps1" -Verbose:$Debug -DryRun:$DryRun @Params - $installSucceeded = $? - - if (-not $installSucceeded) - { - throw "dotnet-install.ps1 failed." - } - - if ($global:LASTEXITCODE -ne 0) - { - throw "dotnet-install.ps1 failed with exit code $global:LASTEXITCODE" - } - - if ($Verify) - { - & $Verify - } - - return - } - catch - { - Write-Warning "Attempt $attempt failed: $_" - - if ($attempt -ge $maxAttempts) - { - throw "$Description failed after $maxAttempts attempts. Last error: $_" - } - - Write-Host "Retrying in $retryDelaySec seconds..." - Start-Sleep -Seconds $retryDelaySec - } - } -} - -#------------------------------------------------------------------------------ -# Emit our command-line arguments. - -if ($Debug) -{ - Write-Host "Command-line arguments:" - Write-Host ($PSBoundParameters | ConvertTo-Json -Depth 1) -} - -#------------------------------------------------------------------------------ -# Download the dotnet-install.ps1 script if not already present. - -if (-not (Test-Path -Path "$InstallDir/dotnet-install.ps1" -PathType Leaf)) -{ - if (-not (Test-Path -PathType Container -Path "$InstallDir")) - { - Write-Host "Creating install dir: $InstallDir ..." - - New-Item -ItemType Directory -Force -Path "$InstallDir" - } - - Write-Host "Downloading dotnet-install.ps1..." - - $params = - @{ - Uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1" - OutFile = "$InstallDir/dotnet-install.ps1" - } - - Invoke-WebRequest @params -Verbose:$Debug - - if ($Debug) - { - Write-Host "Emitting dotnet-install.ps1 help:" - Get-Help "$InstallDir/dotnet-install.ps1" - } -} - -#------------------------------------------------------------------------------ -# Read the SDK versions from global.json. - -$globalJsonContent = Get-Content -Raw -Path "$GlobalJson" | ConvertFrom-Json -$sdkVersion = $globalJsonContent.sdk.version - -if ($Debug) -{ - Write-Host "global.json content:" - Write-Host ($globalJsonContent | ConvertTo-Json -Depth 1) - - Write-Host "SDK version: $sdkVersion" -} - -#------------------------------------------------------------------------------ -# Install the SDK. - -$installParams = -@{ - Architecture = "arm64" - Version = "$sdkVersion" - InstallDir = "$InstallDir" -} - -if ($Debug) -{ - Write-Host "dotnet-install.ps1 parameters:" - Write-Host ($installParams | ConvertTo-Json -Depth 1) -} - -# Verify the SDK was actually installed. dotnet-install.ps1 can silently fail -# with exit code 0 when the package download is corrupt or the size cannot be -# measured. -$verifySdk = - if (-not $DryRun) - { - { - $dotnetExe = Join-Path $InstallDir "dotnet" - $installedSdks = & $dotnetExe --list-sdks 2>&1 - $installedSdksText = [string]::Join("`n", @($installedSdks)) - - Write-Host "Installed SDKs:`n$installedSdksText" - - $sdkPattern = "(?m)^$([regex]::Escape($sdkVersion))\s+\[" - - if (-not [regex]::IsMatch($installedSdksText, $sdkPattern)) - { - throw "SDK $sdkVersion is not present after installation." - } - - Write-Host "Verified SDK $sdkVersion is installed." - } - } - -Invoke-DotNetInstall ` - -Description "Installing .NET SDK version: $sdkVersion" ` - -Params $installParams ` - -Verify $verifySdk - -#------------------------------------------------------------------------------ -# Install the Runtimes, if any. - -foreach ($channel in $Runtimes) -{ - $installParams = - @{ - Architecture = "arm64" - Channel = "$channel" - InstallDir = "$InstallDir" - Quality = "GA" - Runtime = "dotnet" - } - - if ($Debug) - { - Write-Host "dotnet-install.ps1 parameters:" - Write-Host ($installParams | ConvertTo-Json -Depth 1) - } - - # Verify the runtime was actually installed. Use the same guard against - # silent corruption that we use for the SDK. - $verifyRuntime = - if (-not $DryRun) - { - # Capture $channel in a local variable so the script-block closure - # binds to the current iteration value. - $ch = $channel - { - $dotnetExe = Join-Path $InstallDir "dotnet" - $installedRuntimes = & $dotnetExe --list-runtimes 2>&1 - $installedRuntimesText = [string]::Join("`n", @($installedRuntimes)) - - Write-Host "Installed runtimes:`n$installedRuntimesText" - - $runtimePattern = "Microsoft\.NETCore\.App $([regex]::Escape($ch))\." - - if (-not [regex]::IsMatch($installedRuntimesText, $runtimePattern)) - { - throw "Runtime $ch is not present after installation." - } - - Write-Host "Verified runtime $ch is installed." - } - } - - Invoke-DotNetInstall ` - -Description "Installing .NET Runtime GA channel: $channel" ` - -Params $installParams ` - -Verify $verifyRuntime -} - -#------------------------------------------------------------------------------ -# Set the DOTNET_ROOT environment variable, and add the tools dir to the path. -# These values propagate back out to the pipeline and are used by subsequent -# steps. - -Write-Host "##vso[task.setvariable variable=DOTNET_ROOT]$InstallDir" -Write-Host "##vso[task.prependpath]$InstallDir" diff --git a/eng/pipelines/common/steps/install-dotnet.yml b/eng/pipelines/common/steps/install-dotnet.yml index 6ce99316b2..70179279b3 100644 --- a/eng/pipelines/common/steps/install-dotnet.yml +++ b/eng/pipelines/common/steps/install-dotnet.yml @@ -8,21 +8,19 @@ # SDK version is always read from the root global.json file. The Runtimes to # install, if any, may be provided as an array of version strings. # -# Special handling is required for ARM64 due to a bug in UseDotNet@2: -# -# [BUG]: UseDotNet@2 task installs x86 build -# https://github.com/microsoft/azure-pipelines-tasks/issues/20300 +# The UseDotNet@2 task installs the SDK/Runtimes for the agent's native +# architecture, including ARM64. (The historical ARM64 workaround for +# https://github.com/microsoft/azure-pipelines-tasks/issues/20300 is no longer +# needed now that the task correctly detects win-arm64 in v271+.) parameters: - # The architecture to install for. - - name: architecture - type: string - values: - - x64 - - x86 - - arm64 - default: x64 + # True to force installation of the x86 build (e.g. for x86 test runs on an + # x64 host). When false, UseDotNet@2 installs for the agent's native + # architecture. + - name: forceX86 + type: boolean + default: false # True to emit debug information and steps. - name: debug @@ -34,14 +32,8 @@ parameters: type: string default: $(Agent.ToolsDirectory)/dotnet - # The list of .NET Runtimes to install, if any. - # - # When architecture is arm64, these must adhere to the format expected by the - # dotnet-install.ps1 script's -Channel option: - # - # https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script - # - # Otherwise, these must adhere to the format expected by the UseDotNet@2 task: + # The list of .NET Runtimes to install, if any. These must adhere to the + # format expected by the UseDotNet@2 task: # # https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/use-dotnet-v2 # @@ -51,86 +43,34 @@ parameters: steps: - # Use the UseDotNet@2 task for all architectures except ARM64. - - ${{ if ne(parameters.architecture, 'arm64') }}: + # Install the SDK listed in the global.json file. + # + # retryCountOnTaskFailure is set because UseDotNet@2 fails intermittently + # in CI due to transient network/CDN issues when downloading the SDK. + - task: UseDotNet@2 + displayName: Install .NET SDK (global.json) + retryCountOnTaskFailure: 3 + inputs: + installationPath: ${{ parameters.installDir }} + packageType: sdk + useGlobalJson: true + ${{ if parameters.forceX86 }}: + env: + PROCESSOR_ARCHITECTURE: x86 - # Install the SDK listed in the global.json file. - # - # retryCountOnTaskFailure is set because UseDotNet@2 fails intermittently - # in CI due to transient network/CDN issues when downloading the SDK. + # Install the desired Runtimes, if any. + - ${{ each version in parameters.runtimes }}: - task: UseDotNet@2 - displayName: Install .NET SDK (global.json) + displayName: Install .NET ${{ version }} Runtime retryCountOnTaskFailure: 3 inputs: installationPath: ${{ parameters.installDir }} - packageType: sdk - useGlobalJson: true - ${{ if eq(parameters.architecture, 'x86') }}: + packageType: runtime + version: ${{ version }} + ${{ if parameters.forceX86 }}: env: PROCESSOR_ARCHITECTURE: x86 - - # Install the desired Runtimes, if any. - - ${{ each version in parameters.runtimes }}: - - task: UseDotNet@2 - displayName: Install .NET ${{ version }} Runtime - retryCountOnTaskFailure: 3 - inputs: - installationPath: ${{ parameters.installDir }} - packageType: runtime - version: ${{ version }} - ${{ if eq(parameters.architecture, 'x86') }}: - env: - PROCESSOR_ARCHITECTURE: x86 - - # Special handling for ARM64. - - ${{ else }}: - - # Use the install script for ARM64. - # - # retryCountOnTaskFailure provides an outer retry around the script's - # internal retry loop, in case the script download itself or the entire - # step fails due to transient issues. - - task: PowerShell@2 - displayName: Install .NET SDK and Runtimes for ARM64 - retryCountOnTaskFailure: 3 - inputs: - targetType: filePath - pwsh: true - filePath: $(Build.SourcesDirectory)/eng/pipelines/common/steps/install-dotnet-arm64.ps1 - # Arguments: - # - # -Debug: - # This must be a PowerShell $true/$false value. The first '$' is not - # expanded. The ${{ }} evaluates to the string "true" or "false", - # and the whole value then becomes either $true or $false. - # - # -GlobalJson: - # The path to the global.json file to read the SDK version from. - # Double-quoted in case there are spaces in the path. - # - # -InstallDir: - # The directory to install to. Double-quoted in case there are - # spaces in the path. - # - # -Runtimes: - # The list of runtimes to install. This must be a single string with - # each runtime quoted and separated by commas. If the runtimes are - # 8.0 and 9.0, the ${{ join() }} function will result in: - # - # 8.0","9.0 - # - # We enclose the join() in double-quotes to get the exact string we - # want: - # - # "8.0","9.0" - # - arguments: > - -Debug:$${{ parameters.debug }} - -GlobalJson "$(Build.SourcesDirectory)/global.json" - -InstallDir "${{ parameters.installDir }}" - -Runtimes "${{ join('","', parameters.runtimes) }}" - # Report what was installed. - pwsh: dotnet --info displayName: Report installed .NET SDK and Runtimes 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 1b1ea7a2e7..50a2867984 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -146,11 +146,6 @@ parameters: - name: timeout type: number - # True if this is an ARM64 job. - - name: isArm64 - type: boolean - default: false - # True to use managed SNI, where applicable. - name: usemanagedSNI type: boolean @@ -212,13 +207,7 @@ jobs: - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} - ${{ if parameters.isArm64 }}: - architecture: arm64 - # ARM64 needs to specify slightly different runtime version formats. - # See the install-dotnet template docs for more info. - runtimes: ['8.0', '9.0'] - ${{ else }}: - runtimes: [8.x, 9.x] + runtimes: [8.x, 9.x] # Restore dotnet CLI tools (e.g. pwsh, apicompat) before building. - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self @@ -348,7 +337,7 @@ jobs: # Install the .NET SDK and Runtimes for x86. - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: - architecture: x86 + forceX86: true debug: ${{ parameters.debug }} installDir: $(dotnetx86RootPath) runtimes: [8.x, 9.x] diff --git a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml index 86ddc0ca45..4f5fbfe730 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -132,7 +132,6 @@ stages: testSet: ${{ testSet }} configSqlFor: ${{ config.value.configSqlFor }} operatingSystem: ${{ config.value.operatingSystem }} - isArm64: ${{ eq(config.value.isArm64, 'true') }} saPassword: $(saPassword) ${{if ne(config.value.configProperties, '{}') }}: ${{ each x86TF in config.value.configProperties.x86TestTargetFrameworks }}: @@ -170,7 +169,6 @@ stages: testSet: ${{ testSet }} configSqlFor: ${{ config.value.configSqlFor }} operatingSystem: ${{ config.value.operatingSystem }} - isArm64: ${{ eq(config.value.isArm64, 'true') }} saPassword: $(saPassword) ${{if and(eq(usemanagedSNI, false), ne(config.value.configProperties, '{}')) }}: ${{ each x86TF in config.value.configProperties.x86TestTargetFrameworks }}: diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 9ee7c7132d..a51bbee3f5 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -524,7 +524,6 @@ stages: pool: ADO-CI-PUBLIC-ARM64-1ES-EUS-POOL images: Win11_ARM64_Azure_Sql: ADO-WIN11-ARM64 - isArm64: true TargetFrameworks: ${{parameters.targetFrameworks }} netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} buildPlatforms: ${{parameters.buildPlatforms }} From 938d536f9a69494db81fd5b48fd47c7aadbe08f0 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Wed, 15 Jul 2026 12:28:47 -0700 Subject: [PATCH 187/298] Filter fatal exceptions from Task.FromException in async paths (#4437) --- .../Microsoft/Data/SqlClient/SqlBulkCopy.cs | 27 +++++++------------ .../Data/SqlClient/SqlCommand.Reader.cs | 2 +- .../Data/SqlClient/SqlCommand.Xml.cs | 2 +- .../Microsoft/Data/SqlClient/SqlDataReader.cs | 2 +- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 55142268e9..44c74fc3f0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1356,16 +1356,9 @@ private Task ReadFromRowSourceAsync(CancellationToken cts) { _hasMoreRowToCopy = ReadFromRowSource(); // Synchronous calls for DataRows and DataTable won't block. For IDataReader, it may block. } - catch (Exception ex) + catch (Exception ex) when (_isAsyncBulkCopy && ADP.IsCatchableExceptionType(ex)) { - if (_isAsyncBulkCopy) - { - return Task.FromException(ex); - } - else - { - throw; - } + return Task.FromException(ex); } finally { @@ -2530,7 +2523,7 @@ private Task CopyColumnsAsync(int col, TaskCompletionSource source = nul source.SetResult(null); } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -2739,7 +2732,7 @@ private Task CopyRowsAsync(int rowsSoFar, int totalRows, CancellationToken cts, 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) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -2816,7 +2809,7 @@ private Task CopyBatchesAsync(BulkCopySimpleResultSet internalResults, string up } } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -2888,7 +2881,7 @@ private Task CopyBatchesAsyncContinued(BulkCopySimpleResultSet internalResults, return CopyBatchesAsyncContinuedOnSuccess(internalResults, updateBulkCommandText, cts, source); } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -2950,7 +2943,7 @@ private Task CopyBatchesAsyncContinuedOnSuccess(BulkCopySimpleResultSet internal return source.Task; } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -3122,7 +3115,7 @@ private void WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet int } } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { _localColumnMappings = null; @@ -3299,7 +3292,7 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio WriteToServerInternalRestContinuedAsync(internalResults, cts, source); // internalResults is valid here. } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { @@ -3376,7 +3369,7 @@ private Task WriteToServerInternalAsync(CancellationToken ctoken) return resultTask; } } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { if (source != null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs index bffe17baf9..33af6073fb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs @@ -381,7 +381,7 @@ private void BeginExecuteReaderInternalReadStage(TaskCompletionSource co _stateObj.ReadSni(completion); } // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { // Similarly, if an exception occurs put the stateObj back into the pool. // and reset async cache information to allow a second async execute diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs index 447627375e..39d043df12 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs @@ -334,7 +334,7 @@ private void BeginExecuteXmlReaderInternalReadStage(TaskCompletionSource _stateObj.ReadSni(completion); } // @TODO: CER Exception Handling was removed here (see GH#3581) - catch (Exception e) + catch (Exception e) when (ADP.IsCatchableExceptionType(e)) { // Similarly, if an exception occurs put the stateObj back into the pool. // and reset async cache information to allow a second async execute diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 6a0dc09765..cbc83b7406 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -5476,7 +5476,7 @@ private Task InvokeAsyncCall(SqlDataReaderBaseAsyncCallContext context) { task = context.Execute(null, context); } - catch (Exception ex) + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) { task = Task.FromException(ex); } From f1b1d469f44b21b579b66e9904c491e92bd93eba Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 16 Jul 2026 07:53:00 -0300 Subject: [PATCH 188/298] Fix moved step template paths in sqlclient-package.yml pipeline (#4441) --- eng/pipelines/ci/package/sqlclient-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/ci/package/sqlclient-package.yml b/eng/pipelines/ci/package/sqlclient-package.yml index e194e2abef..f1b6d13c17 100644 --- a/eng/pipelines/ci/package/sqlclient-package.yml +++ b/eng/pipelines/ci/package/sqlclient-package.yml @@ -117,12 +117,12 @@ jobs: displayName: '[Debug] Print Environment Variables' # Install the .NET SDK from global.json. - - template: /eng/pipelines/steps/install-dotnet.yml@self + - template: /eng/pipelines/common/steps/install-dotnet.yml@self parameters: debug: ${{ parameters.debug }} # Restore dotnet local tools (pwsh, apicompat, etc.). - - template: /eng/pipelines/steps/restore-dotnet-tools.yml@self + - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self # Clean any pre-existing .nupkg / .snupkg files from the packages/ directory # to ensure we only publish packages produced by this run. From e40ff48256895fb167a90f9bf5f50a52f80c6bc2 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 16 Jul 2026 08:19:49 -0300 Subject: [PATCH 189/298] APIScan remediation: remove obsolete SharedTokenCacheUsername, fix masked CS0618 pragma, migrate obsolete ManagedIdentityCredential ctor (#4421) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ActiveDirectoryAuthenticationProvider.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs index 7a39bb808f..52b1da7ba7 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs @@ -120,7 +120,7 @@ public ActiveDirectoryAuthenticationProvider(ActiveDirectoryAuthenticationProvid /// /// The Entra ID application client id used by this provider instance. Exposed as internal for tests. - /// The client id is used in the redirect URI when WAM broker mode is enabled, so it must match the client id configured + /// The client id is used in the redirect URI when WAM broker mode is enabled, so it must match the client id configured /// in the app registration for the Entra ID application to successfully broker with WAM on Windows. /// internal string ApplicationClientId => _applicationClientId; @@ -182,7 +182,7 @@ public override void BeforeUnload(SqlAuthenticationMethod authentication) // PublicClientAppKey cache (IWin32WindowFunc is part of its equality), so the same logical // identity ends up with two IPublicClientApplication instances depending on which setter // the caller used. Mark [Obsolete] in a future release once we have a migration window. - + /// public void SetIWin32WindowFunc(Func iWin32WindowFunc) => _iWin32WindowFunc = iWin32WindowFunc; #endif @@ -373,7 +373,7 @@ previousPw is byte[] previousPwBytes && { #pragma warning disable CS0618 // Type or member is obsolete result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) - #pragma warning disable CS0618 // Type or member is obsolete + #pragma warning restore CS0618 // Type or member is obsolete .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync(cancellationToken: cts.Token) .ConfigureAwait(false); @@ -873,7 +873,6 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential if (tokenCredentialKey._clientId is not null) { defaultAzureCredentialOptions.ManagedIdentityClientId = tokenCredentialKey._clientId; - defaultAzureCredentialOptions.SharedTokenCacheUsername = tokenCredentialKey._clientId; defaultAzureCredentialOptions.WorkloadIdentityClientId = tokenCredentialKey._clientId; } @@ -901,14 +900,24 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential return new TokenCredentialData(cred, GetHash(secret)); } - TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; - if (tokenCredentialKey._tokenCredentialType == typeof(ManagedIdentityCredential)) { - return new TokenCredentialData(new ManagedIdentityCredential(tokenCredentialKey._clientId, tokenCredentialOptions), GetHash(secret)); + // A null or empty client id indicates a system-assigned managed identity; a + // non-empty client id selects a specific user-assigned managed identity. + ManagedIdentityId managedIdentityId = string.IsNullOrEmpty(tokenCredentialKey._clientId) + ? ManagedIdentityId.SystemAssigned + : ManagedIdentityId.FromUserAssignedClientId(tokenCredentialKey._clientId); + ManagedIdentityCredentialOptions managedIdentityCredentialOptions = new(managedIdentityId) + { + AuthorityHost = new Uri(tokenCredentialKey._authority) + }; + + return new TokenCredentialData(new ManagedIdentityCredential(managedIdentityCredentialOptions), GetHash(secret)); } else if (tokenCredentialKey._tokenCredentialType == typeof(ClientSecretCredential)) { + TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; + return new TokenCredentialData(new ClientSecretCredential(tokenCredentialKey._audience, tokenCredentialKey._clientId, secret, tokenCredentialOptions), GetHash(secret)); } else if (tokenCredentialKey._tokenCredentialType == typeof(WorkloadIdentityCredential)) From e8136dd2604a2578ddcb5b4308ea8326885522b3 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 16 Jul 2026 12:31:23 -0500 Subject: [PATCH 190/298] PR Pipeline | Enable LocalDB Tests (#4444) * Can I just turn this on and it'll work? * :robot: attempt * Disable enumeration at discovery of connection string member data. :robot: suggestion says this should cause conditional theory to run before the memebr data. * Add missing checks for connection strings being set up * Patch up remaining tests that don't check for setup connections before using them * Add comments as to why enumeration is disabled * Add DisableDiscoveryEnumeration to another test it should've been on but it didn't matter b/c the test was marked as flaky. * Add comment for localdb job --- .../pr/jobs/test-sqlclientmanual-job.yml | 13 +++- eng/pipelines/pr/stages/test-stages.yml | 41 ++++++++++ .../pr/steps/configure-sqlserver-step.yml | 12 +++ .../configure-sqlserver-windows-step.yml | 21 ++++++ .../CancellationTokenSourceDisposalTest.cs | 12 ++- .../SQL/DataStreamTest/DataStreamTest.cs | 74 ++++++++++++------- .../MarsSessionPoolingTest.cs | 20 ++--- .../SQL/ParameterTest/OutputParameterTests.cs | 2 +- .../SQL/ParameterTest/ParametersTest.cs | 43 ++++++----- 9 files changed, 178 insertions(+), 60 deletions(-) diff --git a/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml b/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml index e1a5a4abd4..38b49695fe 100644 --- a/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml +++ b/eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml @@ -98,6 +98,10 @@ parameters: - name: isLocalServer type: boolean + # Whether to enable LocalDB when configuring a local Windows SQL Server. + - name: enableLocalDB + type: boolean + # Name of the local DB application name for localdb tests, this value will be stored in the # config.jsonc - name: localDbAppName @@ -112,6 +116,10 @@ parameters: - name: testSet type: string + # Whether integrated security is supported by this test configuration. + - name: supportsIntegratedSecurity + type: boolean + # Name of the user managed identity client ID. This value will be stored in config.jsonc for use # in AKV tests and substituted in for Azure connection string user IDs. - name: userManagedIdentityClientId @@ -153,7 +161,10 @@ jobs: - ${{ if eq(parameters.isLocalServer, true) }}: - template: /eng/pipelines/pr/steps/configure-sqlserver-step.yml@self parameters: + enableLocalDB: ${{ parameters.enableLocalDB }} fileStreamDirectory: ${{ parameters.fileStreamDirectory }} + localDbAppName: ${{ parameters.localDbAppName }} + localDbSharedInstanceName: ${{ parameters.localDbSharedInstanceName }} operatingSystem: ${{ parameters.platformOperatingSystem }} saPassword: $(saPassword) @@ -180,7 +191,7 @@ jobs: $config.LocalDbAppName = "${{ parameters.localDbAppName }}" $config.LocalDbSharedInstanceName = "${{ parameters.localDbSharedInstanceName }}" $config.ManagedIdentitySupported = $true - $config.SupportsIntegratedSecurity = $false + $config.SupportsIntegratedSecurity = [System.Convert]::ToBoolean("${{ parameters.supportsIntegratedSecurity }}") $config.UserManagedIdentityClientId = "${{ parameters.userManagedIdentityClientId }}" if ("${{ parameters.platformOperatingSystem }}" -eq "Windows") { diff --git a/eng/pipelines/pr/stages/test-stages.yml b/eng/pipelines/pr/stages/test-stages.yml index f6023678b7..f28bf6b9e9 100644 --- a/eng/pipelines/pr/stages/test-stages.yml +++ b/eng/pipelines/pr/stages/test-stages.yml @@ -191,13 +191,50 @@ stages: configDisplayName: "localhost" connectionStringNp: ${{ parameters.manualTestConnectionStringNpLocalhost }} connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpLocalhost }} + enableLocalDB: false fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} isLocalServer: true localDbAppName: ${{ parameters.manualTestLocalDbAppName }} localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} + supportsIntegratedSecurity: false testSet: "123" userManagedIdentityClientId: ${{ parameters.manualTestUserManagedIdentityClientId }} + # TestSqlClientManual - LocalDB + # Note: connection strings are explicitly set to empty to only allow the localDB tests to + # run. LocalDB tests require the localdb app name to be provided and supportsIntegratedSecurity + # to be true. If the connection strings (from the library, which as of 7/26 does not use + # integrated auth) are provided with supportsIntegratedSecurity set to true, other tests + # will fail. + - ${{ if eq(platform.operatingSystem, 'Windows') }}: + - template: /eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml@self + parameters: + buildConfiguration: ${{ parameters.buildConfiguration }} + buildSuffix: ${{ parameters.buildSuffix }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + stageNameSecrets: ${{ parameters.stageNameSecrets }} + testResultsArtifactBaseName: ${{ parameters.testResultsArtifactBaseName }} + + platformDisplayName: ${{ platform.displayName }} + platformDotnet: ${{ platform.dotnet }} + platformImage: ${{ platform.image }} + platformOperatingSystem: ${{ platform.operatingSystem }} + poolName: ${{ parameters.poolName }} + + azureKeyVaultTenantId: ${{ parameters.manualTestAzureKeyVaultTenantId }} + azureKeyVaultUrl: ${{ parameters.manualTestAzureKeyVaultUrl }} + configDisplayName: "localdb" + connectionStringNp: "" + connectionStringTcp: "" + enableLocalDB: true + fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} + isLocalServer: true + localDbAppName: ${{ parameters.manualTestLocalDbAppName }} + localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} + supportsIntegratedSecurity: true + testSet: "123" + userManagedIdentityClientId: ${{ parameters.manualTestUserManagedIdentityClientId }} + # TestSqlClientManual - Azure - template: /eng/pipelines/pr/jobs/test-sqlclientmanual-job.yml@self parameters: @@ -218,10 +255,12 @@ stages: configDisplayName: "azure" connectionStringNp: ${{ parameters.manualTestConnectionStringNpAzure }} connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpAzure }} + enableLocalDB: false fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} isLocalServer: false localDbAppName: ${{ parameters.manualTestLocalDbAppName }} localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} + supportsIntegratedSecurity: false testSet: "123" userManagedIdentityClientId: ${{ parameters.manualTestUserManagedIdentityClientId }} @@ -245,10 +284,12 @@ stages: configDisplayName: "localhost_ae" connectionStringNp: ${{ parameters.manualTestConnectionStringNpLocalhost }} connectionStringTcp: ${{ parameters.manualTestConnectionStringTcpLocalhost }} + enableLocalDB: false fileStreamDirectory: ${{ parameters.manualTestFileStreamDirectory }} isLocalServer: true localDbAppName: ${{ parameters.manualTestLocalDbAppName }} localDbSharedInstanceName: ${{ parameters.manualTestLocalDbSharedInstanceName }} + supportsIntegratedSecurity: false testSet: "AE" userManagedIdentityClientId: ${{ parameters.manualTestUserManagedIdentityClientId }} diff --git a/eng/pipelines/pr/steps/configure-sqlserver-step.yml b/eng/pipelines/pr/steps/configure-sqlserver-step.yml index ebae5390e4..dcedb7a427 100644 --- a/eng/pipelines/pr/steps/configure-sqlserver-step.yml +++ b/eng/pipelines/pr/steps/configure-sqlserver-step.yml @@ -6,10 +6,19 @@ parameters: + - name: enableLocalDB + type: boolean + # Path to use for file stream tests. This only applies to Windows test runs. - name: fileStreamDirectory type: string + - name: localDbAppName + type: string + + - name: localDbSharedInstanceName + type: string + - name: operatingSystem type: string @@ -21,7 +30,10 @@ steps: - ${{ if eq(parameters.operatingSystem, 'Windows') }}: - template: /eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml@self parameters: + enableLocalDB: ${{ parameters.enableLocalDB }} fileStreamDirectory: ${{ parameters.fileStreamDirectory }} + localDbAppName: ${{ parameters.localDbAppName }} + localDbSharedInstanceName: ${{ parameters.localDbSharedInstanceName }} saPassword: ${{ parameters.saPassword }} - ${{ elseif eq(parameters.operatingSystem, 'Linux') }}: diff --git a/eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml b/eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml index d1567beaee..8e07120b4b 100644 --- a/eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml +++ b/eng/pipelines/pr/steps/configure-sqlserver-windows-step.yml @@ -10,10 +10,19 @@ parameters: + - name: enableLocalDB + type: boolean + # Path to use for file stream tests. - name: fileStreamDirectory type: string + - name: localDbAppName + type: string + + - name: localDbSharedInstanceName + type: string + # Password to use for the SA account. This should be generated from the generate-secrets-stage. - name: saPassword type: string @@ -113,3 +122,15 @@ steps: Invoke-Sqlcmd -ServerInstance "$machineName" -Database "master" -InputFile $scriptPath displayName: 'Create Northwind Database [Win]' retryCountOnTaskFailure: 1 + + # Start and share the LocalDB instance. + - ${{ if eq(parameters.enableLocalDB, true) }}: + - powershell: | + SqlLocalDB info + SqlLocalDB info ${{ parameters.localDbAppName }} + SqlLocalDB share ${{ parameters.localDbAppName }} ${{ parameters.localDbSharedInstanceName }} + SqlLocalDB start ${{ parameters.localDbAppName }} + SqlLocalDB info ${{ parameters.localDbAppName }} + + sqlcmd -S "(localdb)\.\${{ parameters.localDbSharedInstanceName }}" -q "SELECT @@VERSION" + displayName: 'Enable LocalDB [Win]' diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/CancellationTokenSourceDisposalTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/CancellationTokenSourceDisposalTest.cs index 376a1df7e6..fd1bcced02 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/CancellationTokenSourceDisposalTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/CancellationTokenSourceDisposalTest.cs @@ -31,8 +31,9 @@ public static IEnumerable ConnectionStrings } } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void SequentialStream_DisposedBeforeReaderClose_DoesNotThrow(string connectionString) { using SqlConnection connection = new SqlConnection(connectionString); @@ -58,8 +59,9 @@ public static void SequentialStream_DisposedBeforeReaderClose_DoesNotThrow(strin reader.Close(); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void SequentialTextReader_DisposedBeforeReaderClose_DoesNotThrow(string connectionString) { using SqlConnection connection = new SqlConnection(connectionString); @@ -84,8 +86,9 @@ public static void SequentialTextReader_DisposedBeforeReaderClose_DoesNotThrow(s reader.Close(); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void SequentialStream_DisposedThenReaderAdvanced_DoesNotThrow(string connectionString) { using SqlConnection connection = new SqlConnection(connectionString); @@ -108,8 +111,9 @@ public static void SequentialStream_DisposedThenReaderAdvanced_DoesNotThrow(stri Assert.True(reader.Read()); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void DataReader_MultipleCloseAndDispose_DoesNotThrow(string connectionString) { using SqlConnection connection = new SqlConnection(connectionString); 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 17c760ace9..a3330a371b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -39,177 +39,201 @@ public static IEnumerable ConnectionStrings } } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void RowBuffer_ReadsBufferCorrectly(string connectionString) { RowBuffer(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void InvalidRead_ThrowsInvalidOperationException(string connectionString) { InvalidRead(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void VariantRead_ReadsVariantDataCorrectly(string connectionString) { VariantRead(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void TypeRead_ReadsTypedDataCorrectly(string connectionString) { TypeRead(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void SqlTypeRead_ReadsSqlTypesCorrectly(string connectionString) { SQLTypeRead(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void GetValueOfTRead_ReadsGenericTypesCorrectly(string connectionString) { GetValueOfTRead(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void MultipleResults_ReadsMultipleBatchesCorrectly(string connectionString) { MultipleResults(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void NumericRead_ReadsLargeNumericValues(string connectionString) { NumericRead(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void TimestampRead_ReadsBinaryTimestamp(string connectionString) { TimestampRead(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void OrphanReader_HandlesConnectionCloseCorrectly(string connectionString) { OrphanReader(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void BufferSize_HandlesVariableBufferSizes(string connectionString) { BufferSize(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void ExecuteXmlReader_ReadsXmlDataCorrectly(string connectionString) { ExecuteXmlReaderTest(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void SequentialAccess_ReadsDataSequentially(string connectionString) { SequentialAccess(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void HasRows_ReportsCorrectly(string connectionString) { HasRowsTest(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void CloseConnection_ClosesReaderWithConnection(string connectionString) { CloseConnection(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void OpenConnection_OpensSuccessfully(string connectionString) { OpenConnection(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void SqlCharsBytes_QueriesWithSqlTypes(string connectionString) { SqlCharsBytesTest(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void GetStream_ReturnsStreamCorrectly(string connectionString) { GetStream(connectionString); } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void GetTextReader_ReturnsTextReaderCorrectly(string connectionString) { GetTextReader(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void GetXmlReader_ReturnsXmlReaderCorrectly(string connectionString) { GetXmlReader(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] [Trait("Category", "flaky")] public static void ReadStream_ReadsStreamDataCorrectly(string connectionString) { ReadStream(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void ReadTextReader_ReadsTextCorrectly(string connectionString) { ReadTextReader(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void StreamingBlobDataTypes_ReadsAllBlobTypes(string connectionString) { StreamingBlobDataTypes(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public static void OutOfOrderGetChars_ReadsColumnsOutOfOrder(string connectionString) { OutOfOrderGetChars(connectionString); } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer), nameof(DataTestUtility.IsNotNamedInstance))] - [MemberData(nameof(ConnectionStrings))] + [MemberData(nameof(ConnectionStrings), DisableDiscoveryEnumeration = true)] public void TestXEventsStreaming_StreamsXEventsCorrectly(string connectionString) { TestXEventsStreaming(connectionString); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs index 9f666fc45f..2fca930559 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MARSSessionPoolingTest/MarsSessionPoolingTest.cs @@ -20,7 +20,7 @@ public class MarsSessionPoolingTest // Synapse: Catalog view 'dm_exec_connections' is not supported in this version. - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteScalar_DisposeCommand(CommandType commandType) @@ -48,7 +48,7 @@ public void ExecuteScalar_DisposeCommand(CommandType commandType) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteScalar_CloseConnection(CommandType commandType) @@ -77,7 +77,7 @@ public void ExecuteScalar_CloseConnection(CommandType commandType) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteNonQuery_DisposeCommand(CommandType commandType) @@ -105,7 +105,7 @@ public void ExecuteNonQuery_DisposeCommand(CommandType commandType) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteNonQuery_CloseConnection(CommandType commandType) @@ -134,7 +134,7 @@ public void ExecuteNonQuery_CloseConnection(CommandType commandType) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteReader_CloseReader(CommandType commandType) @@ -163,7 +163,7 @@ public void ExecuteReader_CloseReader(CommandType commandType) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteReader_DisposeReader(CommandType commandType) @@ -193,7 +193,7 @@ public void ExecuteReader_DisposeReader(CommandType commandType) } [Trait("Category", "flaky")] - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteReader_GarbageCollectReader(CommandType commandType) @@ -231,7 +231,7 @@ public void ExecuteReader_GarbageCollectReader(CommandType commandType) [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ // Expected: 5 // Actual: 4 - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteReader_DisposeCommand(CommandType commandType) @@ -272,7 +272,7 @@ public void ExecuteReader_DisposeCommand(CommandType commandType) } } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteReader_CloseConnection(CommandType commandType) @@ -302,7 +302,7 @@ public void ExecuteReader_CloseConnection(CommandType commandType) } [Trait("Category", "flaky")] - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))] [InlineData(CommandType.Text)] [InlineData(CommandType.StoredProcedure)] public void ExecuteReader_NoCloses(CommandType commandType) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs index 8b0322b559..fc55584e36 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/OutputParameterTests.cs @@ -66,7 +66,7 @@ public void InvalidValueInOutputParameter_ShouldSucceed() /// /// Name of a SQL Server collation which encodes text in the given code page. /// ID of the codepage which should be used by SQL Server and the driver to encode and decode text. - [Theory] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(OutputParameterCodePages))] public void CollatedStringInOutputParameter_DecodesSuccessfully(string collation, int codePage) { 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 95d3736d9a..d2cafd3c76 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -485,8 +485,24 @@ public static void SqlDecimalConvertToDecimal_TestOutOfRange(string sqlDecimalVa Assert.Throws(() => rdr.GetDecimal(0)); } - [Theory] - [ClassData(typeof(ConnectionStringsProvider))] + public static TheoryData TestScaledDecimalParameter_Data + { + get + { + TheoryData result = new(); + foreach (string connectionString in DataTestUtility.ConnectionStrings) + { + result.Add(connectionString, false); // Truncate scaled decimal disabled + result.Add(connectionString, true); // Truncate scaled decimal enabled + } + + return result; + } + } + + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [MemberData(nameof(TestScaledDecimalParameter_Data), DisableDiscoveryEnumeration = true)] public static void TestScaledDecimalParameter_CommandInsert(string connectionString, bool truncateScaledDecimal) { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); @@ -516,8 +532,9 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr Assert.True(ValidateInsertedValues(connection, decimalTable.Name, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); } - [Theory] - [ClassData(typeof(ConnectionStringsProvider))] + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [MemberData(nameof(TestScaledDecimalParameter_Data), DisableDiscoveryEnumeration = true)] public static void TestScaledDecimalParameter_BulkCopy(string connectionString, bool truncateScaledDecimal) { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); @@ -548,9 +565,10 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString, } // Synapse: Parse error at line: 2, column: 8: Incorrect syntax near 'TYPE'. + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [Trait("Category", "flaky")] - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsNotAzureSynapse))] - [ClassData(typeof(ConnectionStringsProvider))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [MemberData(nameof(TestScaledDecimalParameter_Data), DisableDiscoveryEnumeration = true)] public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool truncateScaledDecimal) { using LocalAppContextSwitchesHelper appContextSwitchesHelper = new(); @@ -627,19 +645,6 @@ private static bool ValidateInsertedValues(SqlConnection connection, string tabl return !exceptionHit; } - public class ConnectionStringsProvider : IEnumerable - { - public IEnumerator GetEnumerator() - { - foreach (var cnnString in DataTestUtility.ConnectionStrings) - { - yield return new object[] { cnnString, false }; - yield return new object[] { cnnString, true }; - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } #endregion #endregion From cabe2ddf07afe6da6e94571ed3c6dfff2346fcf0 Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 17 Jul 2026 17:27:48 -0700 Subject: [PATCH 191/298] Add connection-creation rate limiting to ChannelDbConnectionPool (#4396) --- Directory.Packages.props | 2 + specs/006-pool-rate-limiting/diagrams.md | 41 + specs/006-pool-rate-limiting/spec.md | 152 +++ .../src/Microsoft.Data.SqlClient.csproj | 2 + .../ConnectionPool/ChannelDbConnectionPool.cs | 238 ++++- .../ConnectionPool/NoOpAcquiredLease.cs | 50 + ...icrosoft.Data.SqlClient.ManualTests.csproj | 2 + .../ChannelDbConnectionPoolTest.cs | 891 +++++++++++++++++- .../Microsoft.Data.SqlClient.UnitTests.csproj | 2 + 9 files changed, 1315 insertions(+), 65 deletions(-) create mode 100644 specs/006-pool-rate-limiting/diagrams.md create mode 100644 specs/006-pool-rate-limiting/spec.md create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/NoOpAcquiredLease.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 59839f2351..76142810ca 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -150,6 +150,7 @@ + @@ -157,6 +158,7 @@ + diff --git a/specs/006-pool-rate-limiting/diagrams.md b/specs/006-pool-rate-limiting/diagrams.md new file mode 100644 index 0000000000..f05951d61d --- /dev/null +++ b/specs/006-pool-rate-limiting/diagrams.md @@ -0,0 +1,41 @@ +# Rate limiting comparison + +## Existing rate limiting + +```mermaid +flowchart TD + Start([Open request]) --> WaitAny["WaitHandle.WaitAny
(blocking, no queue)"] + + WaitAny -->|idle available| S0["PoolSemaphore
Semaphore 0..MAX"] + WaitAny -->|error state| S1["ErrorEvent
ManualResetEvent"] + WaitAny -->|permit to open one conn| S2["CreationSemaphore
Semaphore 1,1"] + + S0 -->|got connection| Done([Return connection]) + S2 --> Open["Open physical connection"] + Open --> Release["Semaphore.Release 1"] + Release -->|got connection| Done + + classDef prim fill:#bfdbfe,stroke:#1e3a8a,color:#111 + class WaitAny,S0,S1,S2,Open,Release prim +``` + +## New rate limiting + +```mermaid +flowchart TD + Start([Open request]) --> Idle["Idle channel
TryRead
(non-blocking)"] + + Idle -->|got connection| Done([Return connection]) + Idle -->|empty| Limiter["ConcurrencyLimiter
AttemptAcquire 1
(non-blocking)"] + + Limiter -->|acquired lease| Open["Open physical connection"] + Limiter -->|not acquired| Channel["Idle channel
await ReadAsync
(FIFO queued)"] + + Open --> Lease["RateLimitLease.Dispose"] + Lease --> |got connection| Done + Channel -->|loop on wake signal| Idle + Channel --> |got connection| Done + + classDef prim fill:#bfdbfe,stroke:#1e3a8a,color:#111 + class Idle,Limiter,Open,Lease,Channel prim +``` \ No newline at end of file diff --git a/specs/006-pool-rate-limiting/spec.md b/specs/006-pool-rate-limiting/spec.md new file mode 100644 index 0000000000..d373c7d681 --- /dev/null +++ b/specs/006-pool-rate-limiting/spec.md @@ -0,0 +1,152 @@ +# Feature Specification: Pool Rate Limiting and Blocking Period + +**Feature Branch**: `dev/mdaigle/pool-rate-limit` +**Created**: 2026-05-19 +**Status**: Draft +**Input**: ADO Work Item 37824 — "Implement connection open rate limiting" + +## Description + +Add rate limiting to `ChannelDbConnectionPool` to control how many physical connections can be +created concurrently. Without throttling, a burst of concurrent requests can trigger a login +storm against SQL Server. The implementation uses +`System.Threading.RateLimiting.ConcurrencyLimiter` from the BCL — no custom rate limiting +primitives are defined. + +This feature also adds the `PoolBlockingPeriod` error state (fast-fail after a connection +creation failure) with exponential backoff recovery, matching the existing +`WaitHandleDbConnectionPool` behavior. + +> Time spent waiting for the rate limiter counts against the caller's overall `ConnectTimeout` +> budget. `ReplaceConnection` (when implemented) MUST bypass the rate limiter: it already holds +> a pool slot and must not deadlock. + +## User Scenarios & Testing + +### User Story 1 — Throttled Connection Creation Under Burst Demand (P1) + +The pool limits the number of simultaneous physical connection creation attempts. Callers that +cannot immediately create a connection do not queue on the limiter; they fall back to waiting on +the idle channel, where they are satisfied either by a returned connection or by a best-effort +wake when another caller releases its permit. The idle channel preserves FIFO order for returned +connections, but rate-limit retries are best-effort rather than strictly ordered. All waiting is +subject to the caller's `ConnectTimeout`. + +**Acceptance Scenarios**: + +1. **Given** the pool has no idle connections and many callers request connections simultaneously, + **When** the concurrency limit is reached, **Then** additional callers wait until an in-flight + creation completes before starting their own. +2. **Given** a caller is waiting for the rate limiter, **When** its `ConnectTimeout` elapses, + **Then** the caller receives a timeout error without ever attempting to create a connection. +3. **Given** the rate limiter has available capacity, **When** a caller requests a new connection, + **Then** the create proceeds immediately with no added latency. +4. **Given** a connection creation completes (success or failure), **When** the `RateLimitLease` + is disposed, **Then** the next waiting caller is allowed to proceed. + +--- + +### User Story 2 — Blocking Period Fast-Fail on Connection Failure (P1) + +When a connection creation attempt fails because the server is unreachable, the pool enters an +error state and immediately fails subsequent requests for a limited period, returning the cached +error. This prevents cascading timeouts when the server is down. + +**Acceptance Scenarios**: + +1. **Given** a creation failure has occurred and blocking period is enabled, **When** a new + connection is requested within the blocking window, **Then** the request fails immediately + with the cached error. +2. **Given** a creation failure has occurred and blocking period is enabled, **When** the + blocking window expires, **Then** the next request attempts fresh connection creation. +3. **Given** `PoolBlockingPeriod=NeverBlock`, **When** a creation failure occurs, **Then** each + subsequent request independently attempts creation (no fast-fail). +4. **Given** `PoolBlockingPeriod=Auto` connecting to an Azure SQL endpoint and a failure occurs, + **Then** no blocking period is applied (same as `NeverBlock`). +5. **Given** `PoolBlockingPeriod=Auto` connecting to an on-premises SQL Server and a failure + occurs, **Then** the blocking period is applied (same as `AlwaysBlock`). + +--- + +### User Story 3 — Error State Recovery with Exponential Backoff (P2) + +While in the error state the pool waits using exponential backoff (5s → 10s → 20s → 30s → 60s +cap) before allowing the next attempt. Once an attempt after the backoff succeeds, the error +state clears and backoff resets. + +**Acceptance Scenarios**: + +1. **Given** the pool is in error state, **When** the backoff timer fires and the next caller's + attempt succeeds, **Then** the error state is cleared and subsequent requests attempt normal + creation. +2. **Given** the pool is in error state, **When** the backoff timer fires and the next caller's + attempt fails, **Then** the backoff interval increases (up to the 60s cap) and the pool + re-enters the error state. +3. **Given** the pool is in error state, **When** the error is cleared, **Then** the cached + exception, the error flag, and the backoff interval are all reset. + +--- + +### User Story 4 — Rate Limiting Counts Against Connection Timeout (P2) + +Time spent waiting for rate limiter capacity counts against the caller's overall +`ConnectTimeout` budget. + +**Acceptance Scenarios**: + +1. **Given** a caller's timeout is 15s and the caller waits 10s for rate limiting, **When** the + rate limiter releases, **Then** the remaining budget for connection creation is 5s. +2. **Given** a caller's timeout expires while waiting for the rate limiter, **When** the timeout + fires, **Then** the caller receives a timeout error and is removed from the limiter queue. + +--- + +### User Story 5 — Rate Limiting Built on a Concurrency Limiter (P3) + +The pool supports an optional `System.Threading.RateLimiting.ConcurrencyLimiter` to throttle +concurrent physical connection creation. This is the only limiter type the pool currently needs +(pooling against on-prem SQL Server), so the pool takes a concrete `ConcurrencyLimiter?` rather +than the abstract `RateLimiter` base. Support for other limiter types can be added later if a +concrete need arises. When no limiter is supplied (`null`), no rate limiting is applied. + +**Acceptance Scenarios**: + +1. **Given** the pool is configured with a `ConcurrencyLimiter`, **When** connections + are created, **Then** the limiter throttles concurrent creation to the configured maximum. +2. **Given** no limiter is supplied (`null`), **When** connections are created, **Then** the + pool applies no rate limiting. + +--- + +## Functional Requirements + +- **FR-001**: The pool MUST limit the number of concurrent physical connection creation attempts + to a configurable maximum. +- **FR-002**: Callers that cannot immediately create a connection due to rate limiting MUST fall + back to waiting on the idle channel until capacity becomes available (via a returned connection + or a best-effort wake when a permit is released) or their timeout expires. Rate-limit retries are + best-effort and not strictly FIFO-ordered. +- **FR-003**: Time spent waiting for rate limiter capacity MUST count against the caller's + overall connection timeout budget. +- **FR-004**: When a connection creation attempt completes (success or failure), the + `RateLimitLease` MUST be disposed so the next waiting caller can proceed. +- **FR-005**: The pool MUST support three `PoolBlockingPeriod` modes: `Auto`, `AlwaysBlock`, and + `NeverBlock`. +- **FR-006**: When the blocking period is enabled, the pool MUST enter an error state after a + creation failure and immediately fail subsequent requests with the cached error. +- **FR-007**: When the blocking period is disabled, the pool MUST NOT enter an error state; + each request MUST independently attempt creation. +- **FR-008**: While in error state, the backoff MUST use exponential growth starting at 5s, + doubling each attempt, capped at 60s. +- **FR-009**: When an attempt succeeds, the pool MUST clear the error state and reset the + backoff to its initial value. +- **FR-010**: The `ErrorOccurred` property MUST return `true` when in the error state and + `false` otherwise. +- **FR-011**: `ClearPool` MUST clear the error state in addition to invalidating pooled + connections. +- **FR-012**: The rate limiter MUST be an optional `System.Threading.RateLimiting.ConcurrencyLimiter`. + When no limiter is supplied (`null`), the pool MUST apply no rate limiting. Support for other + `RateLimiter` types is intentionally out of scope for now and may be added later if needed. +- **FR-013**: When a limiter is supplied, it MUST be a + `System.Threading.RateLimiting.ConcurrencyLimiter` configured with the desired maximum number + of concurrent connection creation attempts. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 06eaf9e914..92c648c283 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -309,6 +309,7 @@ + @@ -321,6 +322,7 @@ + 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 index f5d758ebb7..298c38b9db 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -8,6 +8,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Channels; +using System.Threading.RateLimiting; using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; @@ -47,6 +48,13 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// /// The trade-off is slightly higher memory overhead per pool instance due to the channel infrastructure, /// but this is generally offset by the performance benefits in async-heavy workloads. + /// + /// + /// Comments in this file reference requirements by tag (e.g. FR-001). These tags are + /// defined in the feature spec at specs/006-pool-rate-limiting/spec.md (see the + /// "Functional Requirements" section); consult it for the authoritative description of each + /// requirement. + /// /// internal sealed class ChannelDbConnectionPool : IDbConnectionPool, IDisposable { @@ -98,6 +106,27 @@ internal sealed class ChannelDbConnectionPool : IDbConnectionPool, IDisposable /// . /// private int _shutdownInitiated; + + /// + /// Optional concurrency limiter that throttles the number of concurrent physical connection + /// creation attempts. When null, no rate limiting is applied. A non-null limiter is + /// supplied at pool construction time; there is no default. Callers fast-fail against + /// the limiter and fall back to the idle-channel wait when no permit is available. + /// Scope note: the permit-release wake is signaled on this pool's own idle channel, so a + /// limiter is expected to be scoped to a single pool. Sharing one limiter across multiple + /// pools is not supported, because a permit released by another pool would not wake waiters + /// parked here and they could stall until their timeout expires. + /// Lifetime note: the pool does not own this limiter and never disposes it; the caller that + /// constructs the limiter owns its lifetime (it may outlive the pool). + /// + private readonly ConcurrencyLimiter? _connectionCreationRateLimiter; + + /// + /// Encapsulates the blocking-period error state for this pool: cached exception, exponential + /// backoff timer, and synchronization. Created only when blocking period is enabled for + /// this pool group. See . + /// + private readonly BlockingPeriodErrorState? _errorState; #endregion /// @@ -107,7 +136,9 @@ internal ChannelDbConnectionPool( SqlConnectionFactory connectionFactory, DbConnectionPoolGroup connectionPoolGroup, DbConnectionPoolIdentity identity, - DbConnectionPoolProviderInfo connectionPoolProviderInfo) + DbConnectionPoolProviderInfo connectionPoolProviderInfo, + ConcurrencyLimiter? connectionCreationRateLimiter = null, + TimeProvider? timeProvider = null) { ConnectionFactory = connectionFactory; PoolGroup = connectionPoolGroup; @@ -117,9 +148,17 @@ internal ChannelDbConnectionPool( AuthenticationContexts = new(); MaxPoolSize = Convert.ToUInt32(PoolGroupOptions.MaxPoolSize); TransactedConnectionPool = new(this); + _connectionCreationRateLimiter = connectionCreationRateLimiter; _connectionSlots = new(MaxPoolSize); _idleChannel = new(); + if (PoolGroup.IsBlockingPeriodEnabled()) + { + // timeProvider is injected only by tests so the blocking-period exit timer can be + // driven deterministically; in production it is null and the error state falls back + // to TimeProvider.System. + _errorState = new BlockingPeriodErrorState(_instanceId, timeProvider: timeProvider); + } // Pruning is only useful when the pool can grow beyond MinPoolSize. // If min >= max, the pool is fixed-size and pruning would never activate. @@ -147,8 +186,7 @@ public ConcurrentDictionary< public int IdleCount => _idleChannel.Count; /// - /// This will be implemented later when we add support for the pool blocking period after errors. For now, it always returns false. - public bool ErrorOccurred => false; + public bool ErrorOccurred => _errorState?.HasError ?? false; /// public int Id => _instanceId; @@ -192,6 +230,10 @@ public void Clear() SqlClientEventSource.Log.TryPoolerTraceEvent( " {0}, Clearing.", Id); + // Clearing the pool implies the caller wants a clean slate, so abandon any cached + // error state. FR-011. + _errorState?.Clear(); + Interlocked.Increment(ref _clearGeneration); // If another thread is already draining, skip the drain. The generation counter has @@ -324,6 +366,19 @@ public void Shutdown() " {0}, Pruner.Dispose threw, continuing shutdown: {1}", Id, ex); } + // Dispose the error state so its exit timer is released. Otherwise a timer scheduled + // during the blocking period would keep this pool reachable and continue firing + // callbacks/logging after shutdown. + try + { + _errorState?.Dispose(); + } + catch (Exception ex) + { + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, _errorState.Dispose threw, continuing shutdown: {1}", Id, ex); + } + // Complete the channel writer so: // - no further idle connections can be enqueued (TryWrite returns false), and // - in-flight / future async waiters on ReadAsync fault with ChannelClosedException. @@ -496,13 +551,16 @@ public bool TryGetConnection( } /// - /// Opens a new internal connection to the database. + /// Opens a new internal connection to the database, throttled by the pool's rate limiter. /// /// The owning connection. /// The cancellation token to cancel the operation. /// The overall timeout budget. Passed through to the physical connection /// so it uses the remaining budget rather than starting a fresh timeout. - /// A task representing the asynchronous operation, with a result of the new internal connection. + /// The new internal connection, or null if the pool has no available slot or the + /// rate limiter is currently saturated. In the latter case the caller should fall back to + /// the idle-channel wait; the rate limiter will write a null to the idle channel when a + /// permit is released so the waiter can retry. /// /// Thrown when the cancellation token is cancelled before the connection operation completes. /// @@ -513,50 +571,142 @@ public bool TryGetConnection( { cancellationToken.ThrowIfCancellationRequested(); - // Opening a connection can be a slow operation and we don't want to hold a lock for the duration. - // Instead, we reserve a connection slot prior to attempting to open a new connection and release the slot - // in case of an exception. - - var result = _connectionSlots.Add( - createCallback: () => - { - // https://github.com/dotnet/SqlClient/issues/3459 - // TODO: This blocks the thread for several network calls! - // When running async, the blocked thread is one allocated from the managed thread pool (due to - // use of Task.Run in TryGetConnection). This is why it's critical for async callers to - // pre-provision threads in the managed thread pool. Our options are limited because - // DbConnectionInternal doesn't support an async open. It's better to block this thread and keep - // throughput high than to queue all of our opens onto a single worker thread. Add an async path - // when this support is added to DbConnectionInternal. - // TODO: ultimately, the connection factory should also accept our cancellation token. - var connection = ConnectionFactory.CreatePooledConnection( - owningConnection, - this, - timeout); + // Fast-fail in the error state. FR-006. + _errorState?.ThrowIfActive(); - if (connection is not null) + try + { + // Reserve a pool slot up front so we don't pay the rate-limit cost only to + // discover the pool is full. Add() reserves synchronously and returns null + // immediately if no slot is available; the rate-limit check only happens inside + // the createCallback, which runs after the reservation succeeds. + DbConnectionInternal? connection = _connectionSlots.Add( + createCallback: () => { - connection.ClearGeneration = _clearGeneration; - } + // Fast-fail rate-limit attempt when a limiter is configured. + // AttemptAcquire returns synchronously and does not queue: if no permit + // is available right now, the lease comes back with IsAcquired == false. + // We deliberately do not block here so the caller can fall back to + // waiting on the idle channel, where it can be satisfied either by a + // returning connection or by a null poke from another caller releasing + // its rate-limit lease (see finally below). We prefer to recycle existing + // connections rather than queue on the rate limit. When no limiter is + // configured we substitute a no-op acquired lease. + // FR-001, FR-002, FR-003. + + RateLimitLease lease = _connectionCreationRateLimiter?.AttemptAcquire(1) ?? NoOpAcquiredLease.Instance; + + // Tracks whether we are leaving this block via an exception rather than a + // normal return. It is read in the finally below to decide whether to poke + // the idle channel: every return path sets it to false first, so by the time + // the finally runs it is true only when an exception is propagating. This + // lets us skip the wake on exception paths, where cleanupCallback already + // pokes, and avoid a redundant double wake. + bool faulted = true; + try + { + if (!lease.IsAcquired) + { + // TODO: When we fail to acquire a lease, surface the lease metadata + // (e.g. RateLimitMetadataName.RetryAfter, ReasonPhrase) in the error + // path so the user can identify why the lease was denied. + faulted = false; + return null; + } + + cancellationToken.ThrowIfCancellationRequested(); + + // https://github.com/dotnet/SqlClient/issues/3459 + // TODO: This blocks the thread for several network calls! + // When running async, the blocked thread is one allocated from the managed thread pool (due to + // use of Task.Run in TryGetConnection). This is why it's critical for async callers to + // pre-provision threads in the managed thread pool. Our options are limited because + // DbConnectionInternal doesn't support an async open. It's better to block this thread and keep + // throughput high than to queue all of our opens onto a single worker thread. Add an async path + // when this support is added to DbConnectionInternal. + // TODO: ultimately, the connection factory should also accept our cancellation token. + var newConnection = ConnectionFactory.CreatePooledConnection( + owningConnection, + this, + timeout); + + if (newConnection is not null) + { + newConnection.ClearGeneration = _clearGeneration; + } + + faulted = false; + return newConnection; + } + finally + { + // Capture the acquired state before disposing: the poke condition below + // reads it, and accessing a lease after Dispose is fragile even if the + // current ConcurrencyLimiter lease happens to keep IsAcquired stable. + bool leaseAcquired = lease.IsAcquired; + + // Release the permit back to the limiter (no-op for the default lease) + // BEFORE signaling a waiter. Otherwise a woken waiter could consume the + // null poke and retry its acquire before the permit is actually returned, + // fail to acquire, and fall back to waiting with no subsequent signal - + // stalling connection creation even though the limiter has capacity. + // When no limiter is configured this is NoOpAcquiredLease.Instance, whose + // Dispose is an idempotent no-op, so disposing the shared singleton here + // is safe. + lease.Dispose(); + + // After releasing, signal a waiter on the idle channel that they may now + // retry an open. We only poke on non-faulted completion: on exception paths + // the cleanupCallback below already writes a wake, so poking here too would + // produce a redundant double wake. We also only poke when a limiter is + // configured (a waiter only falls back to the idle channel due to rate + // limiting in that case) and the pool can still grow; if we're at + // MaxPoolSize, only a connection return can satisfy a waiter. FR-004. This + // is best-effort; releasing a lease doesn't guarantee the rate limiter + // immediately has an available permit, but the waiter we wake will fall + // back to waiting again if not. + if (!faulted && + leaseAcquired && + _connectionCreationRateLimiter is not null && + _connectionSlots.ReservationCount < MaxPoolSize) + { + _idleChannel.TryWrite(null); + } + } + }, + cleanupCallback: (newConnection) => + { + // If we fail to open a connection, we need to write a null to the idle channel to + // wake up any waiters + _idleChannel.TryWrite(null); + newConnection?.Dispose(); + }); - return connection; - }, - cleanupCallback: (newConnection) => + if (connection is not null) { - // If we fail to open a connection, we need to write a null to the idle channel to - // wake up any waiters - _idleChannel?.TryWrite(null); - newConnection?.Dispose(); - }); + // A new connection was added to the pool. If we've grown past MinPoolSize, + // start the pruning timer so idle connections can be reclaimed. + Pruner?.UpdateTimer(); - if (result is not null) - { - // A new connection was added to the pool. If we've grown past MinPoolSize, - // start the pruning timer so idle connections can be reclaimed. - Pruner?.UpdateTimer(); + // A successful creation clears error/backoff state + // FR-009. + _errorState?.Clear(); + } + + return connection; } + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex) && ex is not OperationCanceledException) + { + // Enter the blocking period error state on creation failure if configured. + // We deliberately exclude OperationCanceledException: that is thrown when the + // caller's own timeout/cancellation budget expires while waiting, which is + // client-side contention rather than a physical connection creation failure and + // must not poison the pool into fast-fail/backoff for other callers. + // FR-006, FR-007. + _errorState?.Enter(ex); - return result; + throw; + } } /// @@ -686,7 +836,9 @@ private async Task GetInternalConnection( connection ??= GetIdleConnection(); - // If we didn't find an idle connection, try to open a new one. + // If we didn't find an idle connection, try to open a new one. This may + // return null if the pool is full or the rate limiter is currently saturated; + // in either case the caller falls through to the idle-channel wait below. connection ??= OpenNewInternalConnection( owningConnection, cancellationToken, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/NoOpAcquiredLease.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/NoOpAcquiredLease.cs new file mode 100644 index 0000000000..079066b08a --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/NoOpAcquiredLease.cs @@ -0,0 +1,50 @@ +// 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.Threading.RateLimiting; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + /// + /// A no-op that is always acquired and performs no work on + /// dispose. Used as a stand-in when no rate limiter is configured so the open path can + /// treat the lease as unconditional. Stateless and safe to share across all callers; access + /// the singleton via . + /// + internal sealed class NoOpAcquiredLease : RateLimitLease + { + /// + /// The shared singleton instance. Because it is stateless and its + /// releases nothing, the same instance is handed to every caller that opens without a + /// configured limiter and may be disposed any number of times, on any thread, without + /// affecting other callers. + /// + public static readonly NoOpAcquiredLease Instance = new(); + + private NoOpAcquiredLease() + { + } + + public override bool IsAcquired => true; + + public override IEnumerable MetadataNames => Array.Empty(); + + public override bool TryGetMetadata(string metadataName, out object? metadata) + { + metadata = null; + return false; + } + + protected override void Dispose(bool disposing) + { + // No resources to release. This is intentionally idempotent: the shared Instance is + // handed to every no-limiter open, so Dispose may be called repeatedly and + // concurrently with no effect. + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj index f3e4b61b45..be691606ce 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTests.csproj @@ -98,6 +98,7 @@ + @@ -124,6 +125,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index 914e63019a..505d750b1a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Data.Common; using System.Threading; +using System.Threading.RateLimiting; using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; @@ -18,16 +19,34 @@ namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool { + /// + /// Unit tests for covering connection acquisition, + /// timeouts, reuse, pool clearing, blocking-period behavior, and timeout-budget propagation. + /// public class ChannelDbConnectionPoolTest { private static readonly SqlConnectionFactory SuccessfulConnectionFactory = new SuccessfulSqlConnectionFactory(); private static readonly SqlConnectionFactory TimeoutConnectionFactory = new TimeoutSqlConnectionFactory(); + /// + /// Creates a with configurable test dependencies so + /// individual tests can focus on the behavior under test without repeating setup logic. + /// + /// The factory used to create physical connections. + /// Optional pool identity override. + /// Optional pool group override. + /// Optional pool options override. + /// Optional provider info override. + /// Optional concurrency limiter controlling physical connection creation. + /// Optional time provider so tests can drive the blocking-period exit timer deterministically. + /// A configured instance for testing. private ChannelDbConnectionPool ConstructPool(SqlConnectionFactory connectionFactory, DbConnectionPoolIdentity? identity = null, DbConnectionPoolGroup? dbConnectionPoolGroup = null, DbConnectionPoolGroupOptions? poolGroupOptions = null, - DbConnectionPoolProviderInfo? connectionPoolProviderInfo = null) + DbConnectionPoolProviderInfo? connectionPoolProviderInfo = null, + ConcurrencyLimiter? connectionCreationRateLimiter = null, + TimeProvider? timeProvider = null) { poolGroupOptions ??= new DbConnectionPoolGroupOptions( poolByIdentity: false, @@ -47,10 +66,16 @@ private ChannelDbConnectionPool ConstructPool(SqlConnectionFactory connectionFac connectionFactory, dbConnectionPoolGroup, identity ?? DbConnectionPoolIdentity.NoIdentity, - connectionPoolProviderInfo ?? new DbConnectionPoolProviderInfo() + connectionPoolProviderInfo ?? new DbConnectionPoolProviderInfo(), + connectionCreationRateLimiter, + timeProvider ); } + /// + /// Verifies that requesting connections from an empty pool causes the pool to create new + /// physical connections until the requested count is reached. + /// [Theory] [InlineData(1)] [InlineData(5)] @@ -75,11 +100,14 @@ out DbConnectionInternal? internalConnection Assert.NotNull(internalConnection); } - // Assert Assert.Equal(numConnections, pool.Count); } + /// + /// Verifies that asynchronous requests against an empty pool create new physical + /// connections and complete through the provided task completion source. + /// [Theory] [InlineData(1)] [InlineData(5)] @@ -106,11 +134,14 @@ out DbConnectionInternal? internalConnection Assert.NotNull(await tcs.Task); } - // Assert Assert.Equal(numConnections, pool.Count); } + /// + /// Verifies that a synchronous request against an exhausted pool fails with the pooled-open + /// timeout once the caller's timeout budget has already expired. + /// [Fact] public void GetConnectionMaxPoolSize_ShouldTimeoutAfterPeriod() { @@ -126,6 +157,7 @@ public void GetConnectionMaxPoolSize_ShouldTimeoutAfterPeriod() out DbConnectionInternal? internalConnection ); + // Assert Assert.True(completed); Assert.NotNull(internalConnection); } @@ -154,6 +186,10 @@ out DbConnectionInternal? internalConnection Assert.Equal(pool.PoolGroupOptions.MaxPoolSize, pool.Count); } + /// + /// Verifies that an asynchronous request against an exhausted pool completes with the + /// pooled-open timeout once the caller's timeout budget has already expired. + /// [Fact] public async Task GetConnectionAsyncMaxPoolSize_ShouldTimeoutAfterPeriod() { @@ -169,6 +205,7 @@ public async Task GetConnectionAsyncMaxPoolSize_ShouldTimeoutAfterPeriod() out DbConnectionInternal? internalConnection ); + // Assert Assert.True(completed); Assert.NotNull(internalConnection); } @@ -195,6 +232,10 @@ out DbConnectionInternal? internalConnection Assert.Equal(pool.PoolGroupOptions.MaxPoolSize, pool.Count); } + /// + /// Verifies that a waiting synchronous caller reuses a connection that is returned to an + /// exhausted pool instead of creating a new physical connection. + /// [Fact] public async Task GetConnectionMaxPoolSize_ShouldReuseAfterConnectionReleased() { @@ -218,16 +259,15 @@ out DbConnectionInternal? firstConnection out DbConnectionInternal? internalConnection ); + // Assert Assert.True(completed); Assert.NotNull(internalConnection); } - TaskCompletionSource tcs = new(); - // Act var task = Task.Run(() => { - var exceeded = pool.TryGetConnection( + pool.TryGetConnection( new SqlConnection(""), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), @@ -242,6 +282,10 @@ out DbConnectionInternal? extraConnection Assert.Equal(firstConnection, extraConnection); } + /// + /// Verifies that a waiting asynchronous caller reuses a connection that is returned to an + /// exhausted pool instead of creating a new physical connection. + /// [Fact] public async Task GetConnectionAsyncMaxPoolSize_ShouldReuseAfterConnectionReleased() { @@ -265,6 +309,7 @@ out DbConnectionInternal? firstConnection out DbConnectionInternal? internalConnection ); + // Assert Assert.True(completed); Assert.NotNull(internalConnection); } @@ -272,7 +317,7 @@ out DbConnectionInternal? internalConnection TaskCompletionSource taskCompletionSource = new(); // Act - var exceeded = pool.TryGetConnection( + pool.TryGetConnection( new SqlConnection(""), taskCompletionSource, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), @@ -285,6 +330,10 @@ out DbConnectionInternal? recycledConnection Assert.Equal(firstConnection, recycledConnection); } + /// + /// Verifies that synchronous waiters are served in request order when the pool is full, + /// ensuring the first queued request receives the next returned connection. + /// [Fact] [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3730")] public async Task GetConnectionMaxPoolSize_ShouldRespectOrderOfRequest() @@ -309,6 +358,7 @@ out DbConnectionInternal? firstConnection out DbConnectionInternal? internalConnection ); + // Assert Assert.True(completed); Assert.NotNull(internalConnection); } @@ -354,6 +404,10 @@ out DbConnectionInternal? failedConnection await Assert.ThrowsAsync(async () => await failedTask); } + /// + /// Verifies that asynchronous waiters are served in request order when the pool is full, + /// ensuring the first queued request receives the next returned connection. + /// [Fact] [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3730")] public async Task GetConnectionAsyncMaxPoolSize_ShouldRespectOrderOfRequest() @@ -378,6 +432,7 @@ out DbConnectionInternal? firstConnection out DbConnectionInternal? internalConnection ); + // Assert Assert.True(completed); Assert.NotNull(internalConnection); } @@ -386,7 +441,7 @@ out DbConnectionInternal? internalConnection TaskCompletionSource failedCompletionSource = new(); // Act - var exceeded = pool.TryGetConnection( + pool.TryGetConnection( new SqlConnection(""), recycledTaskCompletionSource, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), @@ -396,7 +451,7 @@ out DbConnectionInternal? recycledConnection // Gives time for the recycled connection to be queued before the failed request is initiated. await Task.Delay(1000); - var exceeded2 = pool.TryGetConnection( + pool.TryGetConnection( new SqlConnection("Timeout=1"), failedCompletionSource, TimeoutTimer.StartNew(TimeSpan.FromSeconds(1)), @@ -411,6 +466,10 @@ out DbConnectionInternal? failedConnection await Assert.ThrowsAsync(async () => failedConnection = await failedCompletionSource.Task); } + /// + /// Verifies that a connection returned to the idle channel is reused by a subsequent + /// request instead of allocating a new internal connection. + /// [Fact] public void ConnectionsAreReused() { @@ -447,6 +506,10 @@ out DbConnectionInternal? internalConnection2 Assert.Same(internalConnection1, internalConnection2); } + /// + /// Verifies that synchronous connection creation failures propagate the pooled-open timeout + /// exception from the connection factory. + /// [Fact] public void GetConnectionTimeout_ShouldThrowTimeoutException() { @@ -469,6 +532,10 @@ out DbConnectionInternal? internalConnection Assert.Equal(ADP.PooledOpenTimeout().Message, ex.Message); } + /// + /// Verifies that asynchronous connection creation failures propagate the pooled-open timeout + /// exception through the caller's task completion source. + /// [Fact] public async Task GetConnectionAsyncTimeout_ShouldThrowTimeoutException() { @@ -494,13 +561,18 @@ out DbConnectionInternal? internalConnection Assert.Equal(ADP.PooledOpenTimeout().Message, ex.Message); } + /// + /// Verifies under concurrent synchronous load that the pool never grows beyond its + /// configured maximum size and continues to serve requests safely. + /// [Fact] public void StressTest() { - //Arrange + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); ConcurrentBag tasks = new(); + // Act for (int i = 1; i < pool.PoolGroupOptions.MaxPoolSize * 3; i++) { var t = Task.Run(() => @@ -524,16 +596,23 @@ out DbConnectionInternal? internalConnection } Task.WaitAll(tasks.ToArray()); + + // Assert Assert.True(pool.Count <= pool.PoolGroupOptions.MaxPoolSize, "Pool size exceeded max pool size after stress test."); } + /// + /// Verifies under concurrent asynchronous load that the pool never grows beyond its + /// configured maximum size and continues to serve requests safely. + /// [Fact] public void StressTestAsync() { - //Arrange + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); ConcurrentBag tasks = new(); + // Act for (int i = 1; i < pool.PoolGroupOptions.MaxPoolSize * 3; i++) { var t = Task.Run(async () => @@ -555,58 +634,102 @@ out DbConnectionInternal? internalConnection } Task.WaitAll(tasks.ToArray()); + + // Assert Assert.True(pool.Count <= pool.PoolGroupOptions.MaxPoolSize, "Pool size exceeded max pool size after stress test."); } #region Property Tests + /// + /// Verifies that the pool exposes the instance it was + /// constructed with. + /// [Fact] public void TestConnectionFactory() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.Equal(SuccessfulConnectionFactory, pool.ConnectionFactory); } + /// + /// Verifies that a newly constructed pool starts with zero tracked connections. + /// [Fact] public void TestCount() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.Equal(0, pool.Count); } + /// + /// Verifies that a newly constructed pool reports no blocking-period error by default. + /// [Fact] public void TestErrorOccurred() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.False(pool.ErrorOccurred); } + /// + /// Verifies that the pool assigns a positive instance identifier at construction time. + /// [Fact] public void TestId() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.True(pool.Id >= 1); } + /// + /// Verifies that the pool exposes the identity object it was constructed with. + /// [Fact] public void TestIdentity() { + // Arrange var identity = DbConnectionPoolIdentity.GetCurrent(); var pool = ConstructPool(SuccessfulConnectionFactory, identity); + + // Act & Assert Assert.Equal(identity, pool.Identity); } + /// + /// Verifies that a newly constructed pool begins in the running state. + /// [Fact] public void TestIsRunning() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.True(pool.IsRunning); } + /// + /// Verifies that the pool exposes the configured load-balance timeout from its pool group + /// options. + /// [Fact] public void TestLoadBalanceTimeout() { + // Arrange var poolGroupOptions = new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, @@ -617,12 +740,19 @@ public void TestLoadBalanceTimeout() idleTimeout: 0 ); var pool = ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); + + // Act & Assert Assert.Equal(poolGroupOptions.LoadBalanceTimeout, pool.LoadBalanceTimeout); } + /// + /// Verifies that the pool exposes the exact instance it + /// was constructed with. + /// [Fact] public void TestPoolGroup() { + // Arrange var dbConnectionPoolGroup = new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), @@ -635,12 +765,19 @@ public void TestPoolGroup() hasTransactionAffinity: true, idleTimeout: 0)); var pool = ConstructPool(SuccessfulConnectionFactory, dbConnectionPoolGroup: dbConnectionPoolGroup); + + // Act & Assert Assert.Equal(dbConnectionPoolGroup, pool.PoolGroup); } + /// + /// Verifies that the pool exposes the exact + /// instance it was constructed with. + /// [Fact] public void TestPoolGroupOptions() { + // Arrange var poolGroupOptions = new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, @@ -650,34 +787,61 @@ public void TestPoolGroupOptions() hasTransactionAffinity: true, idleTimeout: 0); var pool = ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); + + // Act & Assert Assert.Equal(poolGroupOptions, pool.PoolGroupOptions); } + /// + /// Verifies that the pool exposes the provider info object it was constructed with. + /// [Fact] public void TestProviderInfo() { + // Arrange var connectionPoolProviderInfo = new DbConnectionPoolProviderInfo(); var pool = ConstructPool(SuccessfulConnectionFactory, connectionPoolProviderInfo: connectionPoolProviderInfo); + + // Act & Assert Assert.Equal(connectionPoolProviderInfo, pool.ProviderInfo); } + /// + /// Verifies that the pool state getter reports + /// immediately after construction. + /// [Fact] public void TestStateGetter() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.Equal(DbConnectionPoolState.Running, pool.State); } + /// + /// Verifies that the pool state remains after + /// construction when no shutdown has been requested. + /// [Fact] public void TestStateSetter() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.Equal(DbConnectionPoolState.Running, pool.State); } + /// + /// Verifies that the pool exposes whether load balancing is enabled based on its configured + /// pool group options. + /// [Fact] public void TestUseLoadBalancing() { + // Arrange var poolGroupOptions = new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, @@ -687,6 +851,8 @@ public void TestUseLoadBalancing() hasTransactionAffinity: true, idleTimeout: 0); var pool = ConstructPool(SuccessfulConnectionFactory, poolGroupOptions: poolGroupOptions); + + // Act & Assert Assert.Equal(poolGroupOptions.UseLoadBalancing, pool.UseLoadBalancing); } @@ -694,41 +860,71 @@ public void TestUseLoadBalancing() #region Not Implemented Method Tests + /// + /// Verifies that remains + /// unimplemented and throws . + /// [Fact] public void TestPutObjectFromTransactedPool() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.Throws(() => pool.PutObjectFromTransactedPool(null!)); } + /// + /// Verifies that + /// remains unimplemented and throws . + /// [Fact] public void TestReplaceConnection() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.Throws(() => pool.ReplaceConnection(null!, null!, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)))); } + /// + /// Verifies that + /// remains unimplemented and throws . + /// [Fact] public void TestTransactionEnded() { + // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); + + // Act & Assert Assert.Throws(() => pool.TransactionEnded(null!, null!)); } #endregion #region Pool Clear Tests + /// + /// Verifies that clearing an empty pool is a no-op and leaves the pool in a valid state. + /// [Fact] public void Clear_EmptyPool_DoesNotThrow() { // Arrange var pool = ConstructPool(SuccessfulConnectionFactory); - // Act & Assert - Should complete without error + // Act pool.Clear(); + + // Assert Assert.Equal(0, pool.Count); } + /// + /// Verifies that clearing a pool with only idle connections destroys them immediately and + /// leaves the pool empty. + /// [Fact] public void Clear_MultipleIdleConnections_AllAreDestroyed() { @@ -763,6 +959,10 @@ out internalConnections[i] Assert.Equal(0, pool.Count); } + /// + /// Verifies that clearing the pool does not immediately destroy a connection that is still + /// checked out by a caller. + /// [Fact] public void Clear_BusyConnection_NotDestroyedImmediately() { @@ -787,6 +987,10 @@ out DbConnectionInternal? busyConnection Assert.Equal(0, busyConnection.ClearGeneration); } + /// + /// Verifies that a busy connection checked out during + /// is destroyed when it is later returned because its generation is stale. + /// [Fact] public void Clear_BusyConnectionReturned_IsDestroyed() { @@ -816,6 +1020,10 @@ out DbConnectionInternal? busyConnection Assert.Equal(0, pool.Count); } + /// + /// Verifies that clearing a pool with both busy and idle connections destroys only the idle + /// connections immediately and defers busy-connection cleanup until return. + /// [Fact] public void Clear_MixedBusyAndIdle_OnlyIdleDestroyedImmediately() { @@ -856,6 +1064,10 @@ out DbConnectionInternal? idleConnection Assert.Equal(0, pool.Count); } + /// + /// Verifies that connections created after a clear are stamped with the new generation and + /// are pooled and reused normally. + /// [Fact] public void Clear_NewConnectionsAfterClear_ArePooledNormally() { @@ -905,6 +1117,10 @@ out DbConnectionInternal? reusedConnection Assert.Equal(1, reusedConnection!.ClearGeneration); } + /// + /// Verifies that repeated clear operations do not corrupt pool state and that each clear + /// increments the pool generation as expected. + /// [Fact] public void Clear_MultipleClearCalls_DoNotCorruptState() { @@ -1157,10 +1373,22 @@ private static void BackdateReturnedTime(DbConnectionInternal connection, TimeSp #endregion #region Test classes + + /// + /// Test connection factory that always succeeds and captures the timeout budget passed in by + /// the pool so timeout propagation can be asserted. + /// internal class SuccessfulSqlConnectionFactory : SqlConnectionFactory { + /// + /// Gets the last timeout budget passed through by the pool to the factory. + /// internal TimeoutTimer? CapturedTimeout { get; private set; } + /// + /// Creates a successful stub internal connection and records the timeout budget used for + /// the creation attempt. + /// protected override DbConnectionInternal CreateConnection( SqlConnectionOptions options, ConnectionPoolKey poolKey, @@ -1174,8 +1402,16 @@ protected override DbConnectionInternal CreateConnection( } } + /// + /// Test connection factory that always throws the pooled-open timeout to exercise failure + /// paths in the pool. + /// internal class TimeoutSqlConnectionFactory : SqlConnectionFactory { + /// + /// Throws the pooled-open timeout exception to simulate a failed physical connection + /// creation. + /// protected override DbConnectionInternal CreateConnection( SqlConnectionOptions options, ConnectionPoolKey poolKey, @@ -1188,6 +1424,10 @@ protected override DbConnectionInternal CreateConnection( } } + /// + /// Minimal test double used by the pool tests to avoid + /// involving a real provider-specific connection implementation. + /// internal class StubDbConnectionInternal : DbConnectionInternal { #region Not Implemented Members @@ -1223,6 +1463,10 @@ internal override void ResetConnection() } #endregion + /// + /// Verifies that constructing the pool with a zero max pool size fails with the expected + /// capacity validation error. + /// [Fact] public void Constructor_WithZeroMaxPoolSize_ThrowsArgumentOutOfRangeException() { @@ -1242,7 +1486,7 @@ public void Constructor_WithZeroMaxPoolSize_ThrowsArgumentOutOfRangeException() poolGroupOptions ); - // Act & Assert + // Act var exception = Assert.Throws(() => new ChannelDbConnectionPool( SuccessfulConnectionFactory, @@ -1250,15 +1494,20 @@ public void Constructor_WithZeroMaxPoolSize_ThrowsArgumentOutOfRangeException() DbConnectionPoolIdentity.NoIdentity, new DbConnectionPoolProviderInfo() )); - + + // Assert Assert.Equal("fixedCapacity", exception.ParamName); Assert.Contains("Capacity must be greater than zero", exception.Message); } + /// + /// Verifies that large but valid max pool sizes pass capacity validation and either succeed + /// or fail only due to memory pressure rather than argument validation. + /// [Fact] public void Constructor_WithLargeMaxPoolSize() { - // Arrange - Test that Int32.MaxValue is accepted as a valid pool size + // Arrange var poolGroupOptions = new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, @@ -1276,7 +1525,7 @@ public void Constructor_WithLargeMaxPoolSize() try { - // Act & Assert - This should not throw ArgumentOutOfRangeException, but may throw OutOfMemoryException + // Act var pool = new ChannelDbConnectionPool( SuccessfulConnectionFactory, dbConnectionPoolGroup, @@ -1284,6 +1533,7 @@ public void Constructor_WithLargeMaxPoolSize() new DbConnectionPoolProviderInfo() ); + // Assert Assert.NotNull(pool); Assert.Equal(0, pool.Count); } @@ -1295,12 +1545,14 @@ public void Constructor_WithLargeMaxPoolSize() } } + /// + /// Verifies that small valid max pool sizes construct successfully and produce usable pool + /// instances. + /// [Fact] public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() { - // Arrange - Test various small pool sizes that should work correctly - - // Test with pool size of 1 + // Arrange var poolGroupOptions1 = new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, @@ -1316,7 +1568,7 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() poolGroupOptions1 ); - // Act & Assert - Pool size of 1 should work + // Act var pool1 = new ChannelDbConnectionPool( SuccessfulConnectionFactory, dbConnectionPoolGroup1, @@ -1324,10 +1576,11 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() new DbConnectionPoolProviderInfo() ); + // Assert Assert.NotNull(pool1); Assert.Equal(0, pool1.Count); - // Test with pool size of 2 + // Arrange var poolGroupOptions2 = new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, @@ -1343,6 +1596,7 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() poolGroupOptions2 ); + // Act var pool2 = new ChannelDbConnectionPool( SuccessfulConnectionFactory, dbConnectionPoolGroup2, @@ -1350,10 +1604,602 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() new DbConnectionPoolProviderInfo() ); + // Assert Assert.NotNull(pool2); Assert.Equal(0, pool2.Count); } + #region Rate Limiting And Blocking Period Tests + + /// + /// Verifies that a connection creation failure enters the blocking-period error state when + /// blocking is enabled for the pool, and stays out of it when blocking is disabled. The + /// blocking policy is driven by the connection string's Pool Blocking Period: + /// Default/Auto enable blocking for a non-Azure host (localhost), AlwaysBlock forces it on, + /// and NeverBlock suppresses it. FR-006, FR-007. + /// + [Theory] + [InlineData("", true)] // Default (unspecified) => Auto => blocks for localhost + [InlineData("Pool Blocking Period=Auto;", true)] // Auto => blocks for non-Azure host + [InlineData("Pool Blocking Period=NeverBlock;", false)] + [InlineData("Pool Blocking Period=AlwaysBlock;", true)] + public void ErrorOccurred_OnFailure_FollowsBlockingPeriod(string blockingPeriodClause, bool expectErrorOccurred) + { + // Arrange + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions($"Data Source=localhost;{blockingPeriodClause}"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 4, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0)); + var pool = ConstructPool(TimeoutConnectionFactory, dbConnectionPoolGroup: dbConnectionPoolGroup); + + // Act + Assert.False(pool.ErrorOccurred); + + Assert.Throws(() => + pool.TryGetConnection(new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _)); + + // Assert + Assert.Equal(expectErrorOccurred, pool.ErrorOccurred); + } + + /// + /// Verifies that once the pool enters the blocking period, subsequent synchronous requests + /// fail fast with the cached exception without attempting another physical open. + /// + [Fact] + public void ErrorOccurred_BlockingEnabled_SubsequentRequestFastFails() + { + // Arrange + var factory = new CountingTimeoutConnectionFactory(); + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 4, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0)); + var pool = ConstructPool(factory, dbConnectionPoolGroup: dbConnectionPoolGroup); + + // Act + var first = Assert.Throws(() => + pool.TryGetConnection(new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _)); + Assert.True(pool.ErrorOccurred); + Assert.Equal(1, factory.CreateCount); + + // FR-006: subsequent requests inside the blocking window must fail fast with the + // cached exception without attempting another physical open. + var second = Assert.Throws(() => + pool.TryGetConnection(new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _)); + + // Assert - the second request reused the cached exception and did not invoke + // CreateConnection again while the pool remained in the error state. + Assert.Equal(first.Message, second.Message); + Assert.True(pool.ErrorOccurred); + Assert.Equal(1, factory.CreateCount); + } + + /// + /// Verifies that clearing the pool while in the blocking-period error state resets the + /// externally visible error indicator. + /// + [Fact] + public void Clear_InErrorState_ResetsErrorOccurred() + { + // Arrange + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 4, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0)); + var pool = ConstructPool(TimeoutConnectionFactory, dbConnectionPoolGroup: dbConnectionPoolGroup); + + Assert.Throws(() => + pool.TryGetConnection(new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _)); + Assert.True(pool.ErrorOccurred); + + // Act - FR-011: Clear must reset the error state. + pool.Clear(); + + // Assert + Assert.False(pool.ErrorOccurred); + } + + /// + /// Verifies the pool's blocking-period recovery path end-to-end: a creation failure enters + /// the blocking period (fast-failing subsequent requests without touching the factory), and + /// once the backoff interval elapses the pool leaves the blocking period so a later create + /// succeeds and clears the error state. The blocking-period exit timer is driven with a + /// so the test is deterministic and does not wait on + /// wall-clock time. FR-006, FR-009. + /// + [Fact] + public void Failure_ThenBlockingPeriodExpiry_AllowsSuccessfulCreate() + { + // Arrange + var factory = new ToggleFailureConnectionFactory(); + var fakeTime = new FakeTimeProvider(); + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 4, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0)); + var pool = ConstructPool(factory, dbConnectionPoolGroup: dbConnectionPoolGroup, timeProvider: fakeTime); + + // The first create fails and enters the blocking period. + factory.FailNextCreate = true; + Assert.Throws(() => + pool.TryGetConnection(new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _)); + Assert.True(pool.ErrorOccurred); + + // Even though creates would now succeed, requests issued inside the blocking window + // still fast-fail with the cached exception without reaching the factory. Flipping the + // factory to succeed and still observing a throw proves the create path never ran. + factory.FailNextCreate = false; + Assert.Throws(() => + pool.TryGetConnection(new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _)); + Assert.True(pool.ErrorOccurred); + + // Advancing past the initial backoff fires the exit timer, ending the blocking period. + // FakeTimeProvider invokes the timer callback synchronously, so the error state clears + // before the next line runs. + fakeTime.Advance(TimeSpan.FromSeconds(5)); + Assert.False(pool.ErrorOccurred); + + // Act - a create after the blocking period succeeds and leaves the pool healthy. + var completed = pool.TryGetConnection(new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out var conn); + + // Assert + Assert.True(completed); + Assert.NotNull(conn); + Assert.False(pool.ErrorOccurred); + } + + /// + /// Verifies that an available rate-limiter permit allows the pool to create a physical + /// connection immediately and that the permit is released after the open completes. + /// + [Fact] + public void RateLimiter_PermitAvailable_CreatesPhysicalConnection() + { + // Arrange + var factory = new CountingSuccessfulConnectionFactory(); + using var rateLimiter = new ConcurrencyLimiter( + new ConcurrencyLimiterOptions { PermitLimit = 1, QueueLimit = 0 }); + var pool = ConstructPool( + factory, + connectionCreationRateLimiter: rateLimiter); + + // Act + bool completed = pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? connection); + + // Assert + Assert.True(completed); + Assert.NotNull(connection); + Assert.Equal(1, factory.CreateCount); + // The single permit was acquired for the open and released afterwards, so it is + // available again once the connection has been handed back to the caller. + Assert.Equal(1, rateLimiter.GetStatistics()!.CurrentAvailablePermits); + } + + /// + /// Verifies that a successful physical open releases its rate-limiter lease so that a + /// subsequent open can acquire the same permit. With a single-permit limiter, a leaked + /// lease would deny the second open and force connection reuse, leaving CreateCount at 1. + /// + [Fact] + public void RateLimiter_SuccessfulCreate_ReleasesLeaseForNextCreate() + { + // Arrange + var factory = new CountingSuccessfulConnectionFactory(); + using var rateLimiter = new ConcurrencyLimiter( + new ConcurrencyLimiterOptions { PermitLimit = 1, QueueLimit = 0 }); + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 2, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0); + var pool = ConstructPool( + factory, + poolGroupOptions: poolGroupOptions, + connectionCreationRateLimiter: rateLimiter); + + // Act + // Two distinct owners so neither open can be satisfied by reusing the other's + // connection - each must acquire a fresh permit and create a physical connection. + bool firstCompleted = pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? firstConnection); + bool secondCompleted = pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? secondConnection); + + // Assert + Assert.True(firstCompleted); + Assert.True(secondCompleted); + Assert.NotNull(firstConnection); + Assert.NotNull(secondConnection); + Assert.NotSame(firstConnection, secondConnection); + // The second create only succeeds if the first release returned the single permit. + Assert.Equal(2, factory.CreateCount); + Assert.Equal(1, rateLimiter.GetStatistics()!.CurrentAvailablePermits); + } + + /// + /// Verifies the FR-004 wake path: a caller blocked purely because the rate limiter denied + /// its permit is woken when a different caller releases its lease, and then creates its own + /// physical connection (rather than reusing one, since the permit holder never returns its + /// connection). Exercises both the sync and async idle-channel wait mechanisms. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysicalConnection(bool async) + { + // Arrange + using var createGate = new ManualResetEventSlim(initialState: false); + var factory = new GatedSuccessfulConnectionFactory(createGate); + using var rateLimiter = new ConcurrencyLimiter( + new ConcurrencyLimiterOptions { PermitLimit = 1, QueueLimit = 0 }); + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + // Room to grow so the release actually pokes a waiter (ReservationCount < MaxPoolSize). + maxPoolSize: 2, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0); + var pool = ConstructPool( + factory, + poolGroupOptions: poolGroupOptions, + connectionCreationRateLimiter: rateLimiter); + + Task Open(SqlConnection owner) + { + if (async) + { + // The async path dispatches the open onto the thread pool and completes the TCS, + // so this returns immediately while creation proceeds on another thread. + var tcs = new TaskCompletionSource(); + pool.TryGetConnection(owner, tcs, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _); + return tcs.Task!; + } + + return Task.Run(() => + { + pool.TryGetConnection( + owner, + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? c); + return c; + }); + } + + // Act + // Caller A acquires the only permit and blocks inside creation, holding the permit. + SqlConnection ownerA = new(); + Task requestA = Open(ownerA); + Assert.True( + factory.FirstCreateStarted.Wait(TimeSpan.FromSeconds(5)), + "Timed out waiting for the first open to begin physical creation."); + + // Caller B is denied a permit (A holds it) and must fall back to the idle-channel wait. + // A denied AttemptAcquire increments the limiter's TotalFailedLeases, so watching that + // counter rise is how we confirm B was refused a permit and is now parked on the idle + // channel waiting for a wakeup (rather than still racing to acquire). + long failedLeasesBefore = rateLimiter.GetStatistics()!.TotalFailedLeases; + Task requestB = Open(new SqlConnection()); + Assert.True( + SpinWait.SpinUntil( + () => rateLimiter.GetStatistics()!.TotalFailedLeases > failedLeasesBefore, + TimeSpan.FromSeconds(5)), + "Timed out waiting for the second request to be denied by the rate limiter."); + + // Releasing A's create lets it finish and dispose its lease, which pokes the idle + // channel to wake B. B then finds the permit available and creates its own connection. + createGate.Set(); + + DbConnectionInternal? connectionA = await requestA; + DbConnectionInternal? connectionB = await requestB; + + // Assert + Assert.NotNull(connectionA); + Assert.NotNull(connectionB); + // B was woken by the lease-release poke and created a fresh connection; A never returned + // its connection, so this cannot be reuse. + Assert.NotSame(connectionA, connectionB); + Assert.Equal(2, factory.CreateCount); + Assert.Equal(1, rateLimiter.GetStatistics()!.CurrentAvailablePermits); + } + + /// + /// Verifies that when the rate limiter denies a new physical open, the caller falls back + /// to waiting for an existing connection to be returned instead of forcing a second create. + /// + [Fact] + public async Task RateLimiter_PermitDenied_ReusesReturnedConnection() + { + // Arrange + var factory = new CountingSuccessfulConnectionFactory(); + using var rateLimiter = new ConcurrencyLimiter( + new ConcurrencyLimiterOptions { PermitLimit = 1, QueueLimit = 0 }); + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 2, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0); + var pool = ConstructPool( + factory, + poolGroupOptions: poolGroupOptions, + connectionCreationRateLimiter: rateLimiter); + SqlConnection firstOwner = new(); + + // The first open acquires and releases the single permit, creating a physical + // connection that the second request can later reuse. + pool.TryGetConnection( + firstOwner, + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? firstConnection); + Assert.NotNull(firstConnection); + Assert.Equal(1, factory.CreateCount); + + // Externally hold the only permit so the pool's next AttemptAcquire is denied and the + // waiting request must fall back to waiting for a returned connection. + using RateLimitLease heldLease = rateLimiter.AttemptAcquire(1); + Assert.True(heldLease.IsAcquired); + long failedLeasesBefore = rateLimiter.GetStatistics()!.TotalFailedLeases; + + // Act + Task waitingRequest = Task.Run(() => + { + pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? queuedConnection); + return queuedConnection; + }); + + Assert.True( + SpinWait.SpinUntil( + () => rateLimiter.GetStatistics()!.TotalFailedLeases > failedLeasesBefore, + TimeSpan.FromSeconds(5)), + "Timed out waiting for the second request to be denied by the rate limiter."); + + pool.ReturnInternalConnection(firstConnection!, firstOwner); + DbConnectionInternal? reusedConnection = await waitingRequest; + + // Assert + Assert.Same(firstConnection, reusedConnection); + Assert.Equal(1, factory.CreateCount); + } + + /// + /// Verifies that failed connection attempts release any acquired rate-limiter lease so the + /// pool does not starve future callers after repeated failures. + /// + [Fact] + public async Task RateLimiter_LeaseDisposedOnFailure_DoesNotStarvePool() + { + // Arrange + // If the rate limiter lease were not disposed on failure, after N failures (where N is + // the limiter's permit count) every subsequent request would deadlock. Verify that we + // can keep getting failures back without ever blocking the thread pool. + using var rateLimiter = new ConcurrencyLimiter( + new ConcurrencyLimiterOptions { PermitLimit = 4, QueueLimit = 0 }); + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;Pool Blocking Period=NeverBlock;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: 0, + maxPoolSize: 4, + creationTimeout: 15, + loadBalanceTimeout: 0, + hasTransactionAffinity: true, + idleTimeout: 0)); + var pool = ConstructPool( + TimeoutConnectionFactory, + dbConnectionPoolGroup: dbConnectionPoolGroup, + connectionCreationRateLimiter: rateLimiter); + + // Act & Assert + for (int i = 0; i < 8; i++) + { + await Assert.ThrowsAsync(async () => + { + var tcs = new TaskCompletionSource(); + pool.TryGetConnection(new SqlConnection(), tcs, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _); + await tcs.Task; + }); + } + + // Every failed open must have released its permit; otherwise the pool would starve. + Assert.True( + SpinWait.SpinUntil( + () => rateLimiter.GetStatistics()!.CurrentAvailablePermits == 4, + TimeSpan.FromSeconds(5)), + "Rate limiter did not release all permits after failed opens."); + } + + /// + /// Test connection factory that can be toggled between failure and success to exercise pool + /// recovery behavior after blocking-period entry. + /// + internal class ToggleFailureConnectionFactory : SqlConnectionFactory + { + /// + /// Gets or sets whether the next connection creation attempt should fail. + /// + public bool FailNextCreate { get; set; } + + /// + /// Creates a stub connection or throws the pooled-open timeout based on + /// . + /// + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + if (FailNextCreate) + { + throw ADP.PooledOpenTimeout(); + } + + return new StubDbConnectionInternal(); + } + } + + /// + /// Test connection factory that always throws the pooled-open timeout and records how many + /// physical connection creations the pool attempted, so blocking-period tests can assert + /// that subsequent requests fail fast without invoking another open. + /// + internal sealed class CountingTimeoutConnectionFactory : SqlConnectionFactory + { + /// + /// Gets the number of times the pool asked the factory to create a physical connection. + /// + internal int CreateCount { get; private set; } + + /// + /// Increments the creation counter and throws the pooled-open timeout exception to + /// simulate a failed physical connection creation. + /// + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + CreateCount++; + throw ADP.PooledOpenTimeout(); + } + } + + /// + /// Test connection factory that returns a successful stub connection each time and records + /// how many physical connection creations the pool attempted, so rate-limiting tests can + /// assert how often the pool actually opened a connection. + /// + internal sealed class CountingSuccessfulConnectionFactory : SqlConnectionFactory + { + /// + /// Gets the number of times the pool asked the factory to create a physical connection. + /// + internal int CreateCount { get; private set; } + + /// + /// Creates a successful stub internal connection and increments the creation counter. + /// + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + CreateCount++; + return new StubDbConnectionInternal(); + } + } + + /// + /// Test connection factory that blocks inside its first physical creation until an external + /// gate is released, so a test can hold a rate-limiter permit in-flight while orchestrating + /// a second caller. Counts creations and signals when the first creation begins. + /// + internal sealed class GatedSuccessfulConnectionFactory : SqlConnectionFactory + { + private readonly ManualResetEventSlim _createGate; + private int _createCount; + + internal GatedSuccessfulConnectionFactory(ManualResetEventSlim createGate) + { + _createGate = createGate; + } + + /// + /// Gets the number of times the pool asked the factory to create a physical connection. + /// + internal int CreateCount => Volatile.Read(ref _createCount); + + /// + /// Signaled when the first physical creation begins, before it blocks on the gate. + /// + internal ManualResetEventSlim FirstCreateStarted { get; } = new(initialState: false); + + /// + /// Creates a successful stub connection. The first creation signals that it has started + /// and then blocks on the gate, holding whatever rate-limiter permit it acquired until + /// the test releases it; subsequent creations complete immediately. + /// + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + if (Interlocked.Increment(ref _createCount) == 1) + { + FirstCreateStarted.Set(); + _createGate.Wait(); + } + + return new StubDbConnectionInternal(); + } + } + + #endregion + #region Connection Timeout Awareness Tests /// @@ -1482,3 +2328,4 @@ public void GetConnection_TimeoutTimerReflectsPoolWaitTime() #endregion } } + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index d3405ed113..04afcf3910 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -101,6 +101,7 @@ + @@ -118,6 +119,7 @@ + From b7f6386c5447e5c5aeafe6d167de3eee3e646ae7 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:35:17 -0300 Subject: [PATCH 192/298] - Renamed the top-level pipeline file to match our transition plan. (#4454) - Other minor cleanup for consistent stage/job/step names. --- ....yml => sqlclient-ci-package-pipeline.yml} | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) rename eng/pipelines/ci/package/{sqlclient-package.yml => sqlclient-ci-package-pipeline.yml} (82%) diff --git a/eng/pipelines/ci/package/sqlclient-package.yml b/eng/pipelines/ci/package/sqlclient-ci-package-pipeline.yml similarity index 82% rename from eng/pipelines/ci/package/sqlclient-package.yml rename to eng/pipelines/ci/package/sqlclient-ci-package-pipeline.yml index f1b6d13c17..ada32431b2 100644 --- a/eng/pipelines/ci/package/sqlclient-package.yml +++ b/eng/pipelines/ci/package/sqlclient-ci-package-pipeline.yml @@ -7,35 +7,27 @@ # This pipeline builds all packages using the build.proj Pack target with ReferenceType=Package, # then publishes the resulting .nupkg and .snupkg files as a single pipeline artifact. # -# It runs: -# - On pushes to GitHub main and ADO internal/main (batched) -# - Nightly at 00:00 UTC on both branches +# It runs nightly at 00:20 UTC on GitHub dotnet/SqlClient main and ADO.Net dotnet-sqlclient +# internal/main. # # On internal/main the strong-name signing key is downloaded and used to sign assemblies during the # build. # -# GOTCHA: This pipeline definition is triggered by GitHub _and_ ADO CI. We distinguish the two via -# branch filters: +# GOTCHA: This pipeline definition is triggered by GitHub _and_ ADO.NET CI. We distinguish the two +# via branch filters: # # - Only the GitHub repo has a 'main' branch. # - Only the ADO repo has an 'internal/main' branch. name: $(DayOfYear)$(Rev:rr) -# Do not trigger this pipeline for PRs. +# Do not trigger this pipeline for PRs or pushes. pr: none +trigger: none -# Trigger on pushes to main and internal/main, batching concurrent commits. -trigger: - batch: true - branches: - include: - - main - - internal/main - -# Nightly schedule at 00:00 UTC. +# Nightly schedule at 00:20 UTC. schedules: - - cron: '0 0 * * *' + - cron: '20 0 * * *' displayName: Nightly Build branches: include: @@ -92,13 +84,13 @@ variables: # downloaded by DownloadSecureFile@1; on public builds it expands to empty. - name: signingKeyArg ${{ if eq(variables.isInternalBuild, true) }}: - value: '-p:SigningKeyPath="$(keyFile.secureFilePath)"' + value: -p:SigningKeyPath="$(driverKeyFile.secureFilePath)" ${{ else }}: value: '' jobs: - - job: pack_all_packages - displayName: Pack All Packages + - job: build_nuget_packages + displayName: Build NuGet Packages pool: ${{ if eq(variables.isInternalBuild, true) }}: @@ -124,29 +116,29 @@ jobs: # Restore dotnet local tools (pwsh, apicompat, etc.). - template: /eng/pipelines/common/steps/restore-dotnet-tools.yml@self - # Clean any pre-existing .nupkg / .snupkg files from the packages/ directory - # to ensure we only publish packages produced by this run. + # Clean any pre-existing .nupkg / .snupkg files from the packages/ directory to ensure we only + # publish packages produced by this run. - pwsh: | Write-Host 'Cleaning packages/ directory...' Remove-Item -Force "$(Build.SourcesDirectory)/packages/*.nupkg" -ErrorAction SilentlyContinue Remove-Item -Force "$(Build.SourcesDirectory)/packages/*.snupkg" -ErrorAction SilentlyContinue Write-Host 'Done.' - displayName: Clean packages/ directory + displayName: Clean Packages Directory # On internal builds, download the strong-name signing key. - ${{ if eq(variables.isInternalBuild, true) }}: - task: DownloadSecureFile@1 - displayName: Download Signing Key + displayName: Download Driver Signing Key inputs: secureFile: netfxKeypair.snk - name: keyFile + name: driverKeyFile # Run the Pack target via build.proj. - task: DotNetCoreCLI@2 - displayName: build.proj - Pack (ReferenceType=Package) + displayName: Build & Pack inputs: command: build - projects: '$(Build.SourcesDirectory)/build.proj' + projects: $(Build.SourcesDirectory)/build.proj arguments: >- -t:Pack -p:Configuration=${{ parameters.buildConfiguration }} @@ -161,12 +153,12 @@ jobs: Write-Host 'Packages produced:' Get-ChildItem "$(Build.SourcesDirectory)/packages/*.nupkg" -ErrorAction SilentlyContinue | Format-Table Name, Length -AutoSize -Wrap Get-ChildItem "$(Build.SourcesDirectory)/packages/*.snupkg" -ErrorAction SilentlyContinue | Format-Table Name, Length -AutoSize -Wrap - displayName: List produced packages + displayName: List Packages # Publish all .nupkg and .snupkg files from packages/ as a pipeline artifact. - task: PublishPipelineArtifact@1 - displayName: Publish NuGet Packages + displayName: Publish Packages inputs: - targetPath: '$(Build.SourcesDirectory)/packages' - artifactName: Packages + targetPath: $(Build.SourcesDirectory)/packages + artifactName: SqlClient-Driver-Packages publishLocation: pipeline From c736094beb62eff384b20f15b79b86bb641022bb Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:58:38 -0300 Subject: [PATCH 193/298] Add PackageValidator tool for NuGet package inspection and validation (#4407) --- BUILDGUIDE.md | 2 +- build.proj | 89 +++- src/Microsoft.Data.SqlClient.slnx | 8 +- tools/PackageValidator/Directory.Build.props | 10 + .../PackageValidator/Directory.Packages.props | 11 + tools/PackageValidator/README.md | 226 ++++++++++ tools/PackageValidator/global.json | 15 + .../PackageValidator/src/AssemblyInspector.cs | 309 ++++++++++++++ .../PackageValidator/src/BinaryClassifier.cs | 48 +++ tools/PackageValidator/src/HumanReporter.cs | 239 +++++++++++ tools/PackageValidator/src/Json.cs | 40 ++ tools/PackageValidator/src/Models.Common.cs | 139 +++++++ tools/PackageValidator/src/Models.Report.cs | 238 +++++++++++ .../src/NativeVersionReader.cs | 114 ++++++ .../PackageValidator/src/PackageInspector.cs | 160 ++++++++ .../src/PackageValidator.csproj | 22 + tools/PackageValidator/src/PortablePdb.cs | 205 ++++++++++ tools/PackageValidator/src/Program.cs | 385 ++++++++++++++++++ tools/PackageValidator/src/SymbolResolver.cs | 270 ++++++++++++ tools/PackageValidator/src/Validator.cs | 350 ++++++++++++++++ .../src/VersionExpectations.cs | 92 +++++ tools/PackageValidator/src/VersionRange.cs | 299 ++++++++++++++ .../test/AssemblyInspectorTests.cs | 113 +++++ .../test/BinaryClassifierTests.cs | 48 +++ .../test/PackageValidator.Test.csproj | 23 ++ tools/PackageValidator/test/ValidatorTests.cs | 365 +++++++++++++++++ .../test/VersionExpectationsTests.cs | 184 +++++++++ .../test/VersionRangeTests.cs | 134 ++++++ 28 files changed, 4133 insertions(+), 5 deletions(-) create mode 100644 tools/PackageValidator/Directory.Build.props create mode 100644 tools/PackageValidator/Directory.Packages.props create mode 100644 tools/PackageValidator/README.md create mode 100644 tools/PackageValidator/global.json create mode 100644 tools/PackageValidator/src/AssemblyInspector.cs create mode 100644 tools/PackageValidator/src/BinaryClassifier.cs create mode 100644 tools/PackageValidator/src/HumanReporter.cs create mode 100644 tools/PackageValidator/src/Json.cs create mode 100644 tools/PackageValidator/src/Models.Common.cs create mode 100644 tools/PackageValidator/src/Models.Report.cs create mode 100644 tools/PackageValidator/src/NativeVersionReader.cs create mode 100644 tools/PackageValidator/src/PackageInspector.cs create mode 100644 tools/PackageValidator/src/PackageValidator.csproj create mode 100644 tools/PackageValidator/src/PortablePdb.cs create mode 100644 tools/PackageValidator/src/Program.cs create mode 100644 tools/PackageValidator/src/SymbolResolver.cs create mode 100644 tools/PackageValidator/src/Validator.cs create mode 100644 tools/PackageValidator/src/VersionExpectations.cs create mode 100644 tools/PackageValidator/src/VersionRange.cs create mode 100644 tools/PackageValidator/test/AssemblyInspectorTests.cs create mode 100644 tools/PackageValidator/test/BinaryClassifierTests.cs create mode 100644 tools/PackageValidator/test/PackageValidator.Test.csproj create mode 100644 tools/PackageValidator/test/ValidatorTests.cs create mode 100644 tools/PackageValidator/test/VersionExpectationsTests.cs create mode 100644 tools/PackageValidator/test/VersionRangeTests.cs diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 5acf76cbd5..12a91935cc 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -92,7 +92,7 @@ projects they depend on. | `BuildSqlClientWindows` | Builds the Windows-specific implementation binaries of Microsoft.Data.SqlClient | | `BuildSqlServer` | Builds Microsoft.SqlServer.Server | | `BuildTests` | Builds all test projects for all supported OS combinations | -| `BuildTools` | Builds auxiliary tool/app projects | +| `BuildTools` | Builds auxiliary tool/app projects and their test projects | | `Clean` | Removes build and test output directories | A selection of parameters for build targets in `build.proj` can be found below: diff --git a/build.proj b/build.proj index f2ee21f77c..7a8ce075a7 100644 --- a/build.proj +++ b/build.proj @@ -1474,10 +1474,15 @@ $(RepoRoot)doc/apps/AzureSqlConnector/AzureSqlConnector.csproj $(RepoRoot)tools/PackageCompatibility/src/PackageCompatibility.csproj + $(RepoRoot)tools/PackageCompatibility/test/PackageCompatibility.Test.csproj + $(RepoRoot)tools/PackageCompatibility + $(RepoRoot)tools/PackageValidator/src/PackageValidator.csproj + $(RepoRoot)tools/PackageValidator/test/PackageValidator.Test.csproj + $(RepoRoot)tools/PackageValidator - - + + @@ -1507,4 +1512,84 @@ + + + + + "$(DotnetPath)dotnet" build "$(PackageValidatorProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(PackageCompatibilityTestProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + "$(DotnetPath)dotnet" build "$(PackageValidatorTestProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + "$(DotnetPath)dotnet" test "$(PackageCompatibilityTestProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + + + + + + "$(DotnetPath)dotnet" test "$(PackageValidatorTestProjectPath)" + -p:Configuration=$(Configuration) + + $([System.Text.RegularExpressions.Regex]::Replace($(DotnetCommand), "\s+", " ")) + + + + + + diff --git a/src/Microsoft.Data.SqlClient.slnx b/src/Microsoft.Data.SqlClient.slnx index c0c12132e7..62e0612395 100644 --- a/src/Microsoft.Data.SqlClient.slnx +++ b/src/Microsoft.Data.SqlClient.slnx @@ -214,6 +214,10 @@ - - + + + + + + diff --git a/tools/PackageValidator/Directory.Build.props b/tools/PackageValidator/Directory.Build.props new file mode 100644 index 0000000000..425707ecad --- /dev/null +++ b/tools/PackageValidator/Directory.Build.props @@ -0,0 +1,10 @@ + + + + + + + true + + + diff --git a/tools/PackageValidator/Directory.Packages.props b/tools/PackageValidator/Directory.Packages.props new file mode 100644 index 0000000000..f3a6395bc4 --- /dev/null +++ b/tools/PackageValidator/Directory.Packages.props @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tools/PackageValidator/README.md b/tools/PackageValidator/README.md new file mode 100644 index 0000000000..f09ded9db4 --- /dev/null +++ b/tools/PackageValidator/README.md @@ -0,0 +1,226 @@ +# PackageValidator + +A standalone .NET 10 command-line tool that inspects one or more NuGet packages (`.nupkg`) and +validates their **versions, signing, and symbols**. + +PackageValidator is a first-class maintainer tool for the SqlClient family of packages. It reads +assemblies purely through **metadata** — it never loads them into the runtime — which makes it: + +- **Cross-platform.** It runs on any OS that hosts .NET 10. +- **Framework-agnostic.** It can read assemblies built for target frameworks the host does not run + (for example, inspecting `net462` binaries from a Linux host). +- **Side-effect free.** No module initializers or assembly-load events are ever + executed. + +It is designed to be run both interactively (to inspect a package) and in CI (to **gate** a build +with `--fail-on` and assert expected versions with `--expect-*`). + +## What it checks + +For every package it opens, PackageValidator reports and validates: + +- **Package identity** — id, version, dependency groups, and NuGet signature, read from the embedded + `.nuspec`. +- **Per-assembly versions** — `AssemblyVersion`, `FileVersion`, `InformationalVersion`, culture, + public key token, strong-name signing status, and target framework. +- **Native binaries** — Win32 version-resource information and architecture. +- **Binary classification** — implementation / reference / satellite / native, so symbol coverage is + judged only over implementation assemblies. +- **Symbols** — sibling `.snupkg` matching by debug GUID with portable-PDB checksum verification, + embedded-symbol detection, and orphan/mismatch reporting. +- **Intrinsic rules** — severity-tagged findings for missing/mismatched symbols, + unsigned/delay-signed assemblies, dependency inconsistencies, and more. +- **Expected versions** — optional `--expect-*` assertions for inter-package version-match + validation. + +## Building and running + +The tool lives under `tools/PackageValidator` with its own `global.json` and `Directory.*.props`, +isolated from the main driver build. + +```bash +# From tools/PackageValidator/src +dotnet run -- ... [options] + +# Or build once and invoke the produced binary +dotnet build -c Release +./bin/Release/net10.0/PackageValidator ... [options] +``` + +It is also wired into the repository's `build.proj`: + +- `BuildPackageValidator` / `BuildPackageValidatorTest` — build the tool and tests. +- `TestPackageValidator` — run the test suite. +- `BuildTools` — builds the tools and their test projects. + +## Usage + +```bash +PackageValidator ... [options] +``` + +| Argument / option | Description | +| --- | --- | +| `...` | One or more `.nupkg` files, or directories scanned recursively for `.nupkg` files. | +| `--json` | Emit machine-readable JSON instead of the human-readable layout. | +| `--no-snupkg` | Do not look for or process sibling `.snupkg` symbol packages (embedded symbols are still evaluated). | +| `--fail-on ` | Exit non-zero when a finding matches the given value. Repeatable. | +| `--expect-package-version [id=]VALUE` | Confirm the package version equals `VALUE`. Repeatable. | +| `--expect-file-version [id=]VALUE` | Confirm every assembly's file version equals `VALUE`. Repeatable. | +| `--expect-assembly-version [id=]VALUE` | Confirm every assembly's assembly version equals `VALUE`. Repeatable. | + +### `--fail-on` values + +`--fail-on` accepts a **severity** (`error`, `warning`, `info`), the token `any`, +or one of the following finding **categories**: + +- `version-inconsistency` +- `missing-symbols` +- `symbol-mismatch` +- `symbol-checksum-mismatch` +- `symbol-orphan` +- `symbol-duplicate` +- `delay-signed` +- `unsigned` +- `package-unsigned` +- `dependency-inconsistency` +- `unexpected-package-version` +- `unexpected-file-version` +- `unexpected-assembly-version` + +### `--expect-*` values + +Each `--expect-*` value is either `VALUE` (applies to every package in the run) or `id=VALUE` +(applies only to the package whose id is `id`). A specific id overrides a bare `VALUE` (wildcard), +and because the options repeat, a family-wide expectation can coexist with a per-package override — +for example, assert one family file version while letting `Microsoft.SqlServer.Server` differ. A +missing version counts as a mismatch, so an assertion never silently passes. + +### Exit codes + +| Code | Meaning | +| --- | --- | +| `0` | Success; no gating findings. | +| `1` | Runtime/inspection error. | +| `2` | A `--fail-on` gate was tripped. | + +(Argument-parsing errors return a non-zero `System.CommandLine` code.) + +The same reference details are printed in a free-form **Notes** section appended to `--help`. + +## Examples + +Inspect a single package (human-readable): + +```bash +PackageValidator Microsoft.Data.SqlClient.7.1.0-preview1.nupkg +``` + +Emit JSON for tooling: + +```bash +PackageValidator Microsoft.Data.SqlClient.7.1.0-preview1.nupkg --json +``` + +Gate a directory of packages against an expected family file version, with a per-package override, +and fail on any error: + +```bash +PackageValidator \ + --expect-file-version *=7.1.0.17604 \ + --expect-file-version Microsoft.SqlServer.Server=1.1.0.17604 \ + --fail-on error +``` + +> **Symbol packages from nuget.org:** the flat-container feed only serves `.nupkg` files. To +> exercise the `.snupkg` path, download the symbol package from the gallery's symbol endpoint +> (`https://www.nuget.org/api/v2/symbolpackage/{Id}/{Version}`) and save it beside the `.nupkg` with +> the same base name (for example `microsoft.data.sqlclient.5.2.2.snupkg`). + +## Source layout + +```text +tools/PackageValidator/ +├── global.json # Pins the .NET SDK used to build the tool +├── Directory.Build.props # Shared MSBuild properties for the tool + tests +├── Directory.Packages.props # Central package versions (CPM) +├── src/ +│ ├── PackageValidator.csproj # The console app project (net10.0) +│ ├── Program.cs # Entry point: CLI parsing, orchestration, exit codes +│ ├── PackageInspector.cs # Opens a .nupkg and builds a PackageReport +│ ├── AssemblyInspector.cs # Metadata-only inspection of a single .dll entry +│ ├── NativeVersionReader.cs # Win32 version-resource + architecture for native DLLs +│ ├── BinaryClassifier.cs # Maps a DLL path to its role (impl/ref/satellite/native) +│ ├── SymbolResolver.cs # Cross-checks the sibling .snupkg against assemblies +│ ├── PortablePdb.cs # Low-level portable-PDB GUID + checksum reading +│ ├── VersionExpectations.cs # Parses/resolves --expect-* assertions by package id +│ ├── VersionRange.cs # SemVer 2.0-aware NuGet version-range evaluator +│ ├── Validator.cs # The intrinsic rules engine + finding categories +│ ├── HumanReporter.cs # Renders a run to human-readable text +│ ├── Json.cs # Source-generated JSON serialization context +│ ├── Models.Common.cs # Shared enums + value types (BinaryKind, Severity, Finding, …) +│ └── Models.Report.cs # Report DTOs (ValidationRun, PackageReport, BinaryReport, …) +└── test/ + ├── PackageValidator.Test.csproj + ├── AssemblyInspectorTests.cs + ├── BinaryClassifierTests.cs + ├── ValidatorTests.cs + ├── VersionExpectationsTests.cs + └── VersionRangeTests.cs +``` + +### File responsibilities + +| File | Responsibility | +| --- | --- | +| `Program.cs` | Console entry point. Defines the `System.CommandLine` surface (arguments, options, and the `--help` Notes section), enumerates input paths, drives inspection and validation, selects the output renderer, and maps results to process exit codes. | +| `PackageInspector.cs` | Opens a `.nupkg` (a zip archive), reads the `.nuspec` for id/version/dependencies, detects the `.signature.p7s` signature entry, inspects every `.dll` entry, and invokes the symbol resolver — producing a `PackageReport`. | +| `AssemblyInspector.cs` | Inspects a single `.dll` archive entry via `PEReader`/`MetadataReader` without loading it. Extracts assembly name, versions, culture, public key token, strong-name status, target framework, and debug (CodeView) GUID/checksums. Falls back to native reporting for non-managed DLLs. | +| `NativeVersionReader.cs` | Reads Win32 version-resource fields (file/product version, product name) and architecture from native binaries. Stages bytes to a temp file for `FileVersionInfo`, falling back to architecture-only when staging fails. | +| `BinaryClassifier.cs` | Classifies a managed DLL's package path into a `BinaryKind` (implementation under `lib/` or `runtimes//lib/`, reference under `ref/`, satellite `*.resources.dll`, or other). | +| `SymbolResolver.cs` | Locates the sibling `.snupkg`, indexes its PDBs by debug GUID and path, matches each assembly (authoritative GUID match with checksum verification, or path-based mismatch detection), and reports orphaned/duplicate symbol files. Loads PDB bytes on demand to keep peak memory low. | +| `PortablePdb.cs` | Low-level portable-PDB helpers: read the debug GUID, detect legacy Windows PDBs, and verify a PDB against an assembly's recorded checksum (locating the `#Pdb` stream and zeroing the PDB id before hashing). | +| `VersionExpectations.cs` | Parses the `--expect-package/file/assembly-version` specs into per-id (and wildcard) expectations and resolves the applicable expected value for a given package, enabling inter-package version-match validation. | +| `VersionRange.cs` | Evaluates whether a concrete version satisfies a NuGet dependency range, following SemVer 2.0 precedence (prerelease ordering; build metadata ignored). Used by dependency-consistency checks. | +| `Validator.cs` | Defines the stable finding `Categories` and applies the intrinsic validation rules, producing per-package and cross-package `Finding` instances (symbols, signing, dependency and expected-version consistency). | +| `HumanReporter.cs` | Renders a `ValidationRun` to standard output in the human-readable layout: per-package details and findings, cross-package findings, and the run summary. | +| `Json.cs` | Source-generated `JsonSerializerContext` for trim/AOT-friendly, indented, camel-cased JSON output (nulls omitted, enums as strings). | +| `Models.Common.cs` | Shared enums and value types used across the tool: `BinaryKind`, `SigningStatus`, `Severity`, `Finding`, `NativeVersionInfo`, `PdbChecksum`, and related value types. | +| `Models.Report.cs` | The report DTO graph serialized to JSON and rendered as text: `ValidationRun`, `ValidationSummary`, `PackageReport`, `BinaryReport`, `SymbolPackageInfo`, and related types. | + +## Related public tools + +No single public tool combines everything PackageValidator does, but each area it covers has +established, general-purpose analogs. This tool intentionally folds them into one dependency-light, +cross-platform CLI tailored to the SqlClient package family. + +| Area | Public tools | Relationship | +| --- | --- | --- | +| Package inspection & health rules | [NuGet Package Explorer](https://github.com/NuGetPackageExplorer/NuGetPackageExplorer), [`dotnet validate`](https://github.com/meziantou/Meziantou.Framework) (Meziantou) | Closest all-round analogs: inspect `.nupkg` metadata, layout, symbols, and run package-hygiene rules. | +| Package/assembly signatures | `dotnet nuget verify`, `signtool`, `sn.exe` | Verify NuGet, Authenticode, and strong-name signing state. PackageValidator reports the same strong-name/NuGet-signature state inline. | +| API surface & compatibility | [`Microsoft.DotNet.ApiCompat`](https://github.com/dotnet/sdk), SDK `PackageValidation` (``), [`PublicApiAnalyzers`](https://github.com/dotnet/roslyn-analyzers) | Catch breaking API/TFM changes across versions — complementary to, not overlapping with, this tool's version checks. | +| Assembly/metadata inspection | [ILSpy / `ilspycmd`](https://github.com/icsharpcode/ILSpy), `dotnet-ildasm`, [`AsmSpy`](https://github.com/mikehadlow/AsmSpy) | Metadata-only inspection (same `System.Reflection.Metadata` approach); AsmSpy specifically reports version conflicts across binaries. | +| Symbols / Source Link | [`sourcelink`](https://github.com/dotnet/sourcelink), `Microsoft.SourceLink.*` | Verify portable-PDB checksums and Source Link — the same portable-PDB checksum verification this tool performs against the sibling `.snupkg`. | + +**What makes PackageValidator distinct:** + +- Metadata-only reading (like ILSpy) so it can inspect `net462` assemblies from a Linux host, with + no assembly loading. +- Symbol matching by debug GUID **and** portable-PDB checksum (like sourcelink), including + orphan/mismatch detection against the sibling `.snupkg`. +- Strong-name and NuGet signature state reported inline (like `sn` / `nuget verify`). +- **Inter-package version-match assertions** (`--expect-*`) — asserting a whole *family* of packages + carries consistent versions, which the general-purpose tools above do not do out of the box. +- CI gating via `--fail-on` severity/category selectors. + +## Tests + +The `test/` project is an xUnit v3 suite running on Microsoft.Testing.Platform. It covers +public-key-token computation, binary classification, SemVer 2.0 range evaluation (including +prerelease ordering and malformed-input rejection), the rules engine, and expected-version +assertions. + +```bash +# From tools/PackageValidator/test +dotnet test +``` diff --git a/tools/PackageValidator/global.json b/tools/PackageValidator/global.json new file mode 100644 index 0000000000..a4b67c1ed4 --- /dev/null +++ b/tools/PackageValidator/global.json @@ -0,0 +1,15 @@ +{ + "test": { + // xUnit v3 runs on Microsoft.Testing.Platform rather than the older VSTest runner. + // On .NET 10, dotnet test must therefore be opted into MTP mode for this test project. + "runner": "Microsoft.Testing.Platform" + }, + "sdk": { + // global.json lookup stops at the nearest file and does not merge with the repo-root file. + // This subtree therefore needs to repeat the root SDK settings so commands run from + // tools/PackageValidator keep using the same SDK behavior. + "version": "10.0.300", + "rollForward": "patch", + "allowPrerelease": false + } +} diff --git a/tools/PackageValidator/src/AssemblyInspector.cs b/tools/PackageValidator/src/AssemblyInspector.cs new file mode 100644 index 0000000000..988e4dcceb --- /dev/null +++ b/tools/PackageValidator/src/AssemblyInspector.cs @@ -0,0 +1,309 @@ +// 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.IO.Compression; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Security.Cryptography; +using System.Text; + +namespace PackageValidator; + +/// +/// Inspects a single .dll archive entry and extracts its identity, version, signing, and +/// debug information without loading the assembly into the runtime. +/// +internal static class AssemblyInspector +{ + /// + /// Inspects a single .dll archive entry. + /// + /// The .dll entry within the package archive. + /// + /// A . For native or non-assembly DLLs this is a lightweight report + /// with set to . + /// + public static BinaryReport Inspect(ZipArchiveEntry entry) + { + // PEReader requires a seekable stream, but the zip entry stream is forward-only, so copy the + // bytes into a seekable buffer first. + byte[] bytes; + using (var buffer = new MemoryStream()) + { + using (Stream stream = entry.Open()) + { + stream.CopyTo(buffer); + } + bytes = buffer.ToArray(); + } + + try + { + using var pe = new PEReader(new MemoryStream(bytes, writable: false)); + + // Native DLLs (for example the SNI runtimes) have no CLI metadata at all; report their + // Win32 version-resource information instead of managed identity. + if (!pe.HasMetadata) + { + return BinaryReport.Native(entry.FullName, NativeVersionReader.Read(bytes)); + } + + MetadataReader reader = pe.GetMetadataReader(); + + // A managed module that is not itself an assembly (no manifest) carries no + // version/strong-name identity. + if (!reader.IsAssembly) + { + return new BinaryReport + { + Path = entry.FullName, + Kind = BinaryKind.Other, + IsManagedAssembly = false, + }; + } + + AssemblyDefinition asm = reader.GetAssemblyDefinition(); + + // The assembly version and culture come straight from the assembly manifest row. + string name = reader.GetString(asm.Name); + string assemblyVersion = asm.Version.ToString(); + string culture = asm.Culture.IsNil ? "neutral" : reader.GetString(asm.Culture); + if (string.IsNullOrEmpty(culture)) + { + culture = "neutral"; + } + + // The public key (if the assembly is strong-name signed) yields the public key token. + byte[] publicKey = reader.GetBlobBytes(asm.PublicKey); + string? publicKeyToken = ComputePublicKeyToken(publicKey); + + // The strong-name signed CorFlag distinguishes a fully signed assembly from a + // delay-signed one (which carries a public key but was never signed). + SigningStatus signing = DetermineSigningStatus(pe, publicKeyToken is not null); + + // Read the debug directory to learn which PDB (by GUID) this assembly was built with, + // whether it already carries an embedded portable PDB, and any recorded PDB checksums. + (Guid? codeViewGuid, bool hasEmbeddedPdb, List? checksums) = ReadDebugInfo(pe); + + // File, informational, and target-framework versions are assembly-level custom + // attributes, so scan for them by attribute type name. + string? fileVersion = null; + string? informationalVersion = null; + string? targetFramework = null; + foreach (CustomAttributeHandle handle in asm.GetCustomAttributes()) + { + CustomAttribute attribute = reader.GetCustomAttribute(handle); + switch (GetAttributeTypeName(reader, attribute)) + { + case "System.Reflection.AssemblyFileVersionAttribute": + fileVersion = DecodeStringArgument(reader, attribute); + break; + case "System.Reflection.AssemblyInformationalVersionAttribute": + informationalVersion = DecodeStringArgument(reader, attribute); + break; + case "System.Runtime.Versioning.TargetFrameworkAttribute": + targetFramework = DecodeStringArgument(reader, attribute); + break; + } + } + + // Compose the canonical strong-name display name in the same shape AssemblyName.FullName + // would produce, using "null" when the assembly is not strong-name signed. + string strongName = + $"{name}, Version={assemblyVersion}, Culture={culture}, " + + $"PublicKeyToken={publicKeyToken ?? "null"}"; + + return new BinaryReport + { + Path = entry.FullName, + Kind = BinaryClassifier.Classify(entry.FullName), + IsManagedAssembly = true, + AssemblyName = name, + AssemblyVersion = assemblyVersion, + FileVersion = fileVersion, + InformationalVersion = informationalVersion, + TargetFramework = targetFramework, + Culture = culture, + PublicKeyToken = publicKeyToken, + SigningStatus = signing, + StrongName = strongName, + CodeViewGuid = codeViewGuid, + Checksums = checksums, + HasEmbeddedSymbols = hasEmbeddedPdb, + }; + } + catch (BadImageFormatException) + { + // The entry was not a valid PE image (for example a renamed data file). Report it as + // native rather than failing the whole package inspection. + return BinaryReport.Native(entry.FullName); + } + } + + /// + /// Determines the strong-name signing state of an assembly from its CLI header flags. + /// + /// The PE reader positioned over the assembly. + /// Whether the assembly manifest declares a public key. + /// The inferred . + private static SigningStatus DetermineSigningStatus(PEReader pe, bool hasPublicKey) + { + if (!hasPublicKey) + { + return PackageValidator.SigningStatus.Unsigned; + } + + CorHeader? cor = pe.PEHeaders.CorHeader; + bool strongNameSigned = cor is not null && (cor.Flags & CorFlags.StrongNameSigned) != 0; + return strongNameSigned + ? PackageValidator.SigningStatus.Signed + : PackageValidator.SigningStatus.DelaySigned; + } + + /// + /// Reads an assembly's debug directory to determine the PDB it was built against, whether it + /// embeds a portable PDB, and any recorded PDB checksums. + /// + /// The PE reader positioned over the assembly. + /// The CodeView GUID, the embedded-PDB flag, and the recorded checksums (if any). + private static (Guid? CodeViewGuid, bool HasEmbeddedPdb, List? Checksums) ReadDebugInfo(PEReader pe) + { + Guid? codeViewGuid = null; + bool hasEmbeddedPdb = false; + List? checksums = null; + + foreach (DebugDirectoryEntry entry in pe.ReadDebugDirectory()) + { + switch (entry.Type) + { + case DebugDirectoryEntryType.CodeView: + try + { + codeViewGuid = pe.ReadCodeViewDebugDirectoryData(entry).Guid; + } + catch (BadImageFormatException) + { + // Malformed CodeView record; leave the GUID unknown. + } + break; + + case DebugDirectoryEntryType.EmbeddedPortablePdb: + hasEmbeddedPdb = true; + break; + + case DebugDirectoryEntryType.PdbChecksum: + try + { + PdbChecksumDebugDirectoryData data = pe.ReadPdbChecksumDebugDirectoryData(entry); + (checksums ??= []).Add(new PdbChecksum + { + Algorithm = data.AlgorithmName, + Hash = data.Checksum.ToArray(), + }); + } + catch (BadImageFormatException) + { + // Malformed checksum record; skip it. + } + break; + } + } + + return (codeViewGuid, hasEmbeddedPdb, checksums); + } + + /// + /// Computes the 8-byte public key token (as a lowercase hex string) from a strong-name public + /// key blob. + /// + /// The raw public key bytes from the assembly manifest. + /// + /// The 16-character hex public key token, or when the assembly is not + /// strong-name signed (empty public key). + /// + public static string? ComputePublicKeyToken(byte[] publicKey) + { + if (publicKey is null || publicKey.Length == 0) + { + return null; + } + + // The token is defined by the CLI spec as the low 8 bytes of the SHA-1 hash of the public + // key, emitted in reverse (little-endian) order. + byte[] hash = SHA1.HashData(publicKey); + var token = new byte[8]; + for (int i = 0; i < 8; i++) + { + token[i] = hash[hash.Length - 1 - i]; + } + + var sb = new StringBuilder(16); + foreach (byte b in token) + { + sb.Append(b.ToString("x2")); + } + return sb.ToString(); + } + + /// + /// Resolves the full type name of the attribute that a custom attribute instance constructs. + /// + private static string? GetAttributeTypeName(MetadataReader reader, CustomAttribute attribute) + { + switch (attribute.Constructor.Kind) + { + case HandleKind.MemberReference: + MemberReference memberRef = reader.GetMemberReference((MemberReferenceHandle)attribute.Constructor); + return GetTypeName(reader, memberRef.Parent); + + case HandleKind.MethodDefinition: + MethodDefinition methodDef = reader.GetMethodDefinition((MethodDefinitionHandle)attribute.Constructor); + return GetTypeName(reader, methodDef.GetDeclaringType()); + + default: + return null; + } + } + + /// + /// Builds the fully qualified name of a type referenced by a type reference or definition handle. + /// + private static string? GetTypeName(MetadataReader reader, EntityHandle handle) + { + switch (handle.Kind) + { + case HandleKind.TypeReference: + TypeReference typeRef = reader.GetTypeReference((TypeReferenceHandle)handle); + return Combine(reader.GetString(typeRef.Namespace), reader.GetString(typeRef.Name)); + + case HandleKind.TypeDefinition: + TypeDefinition typeDef = reader.GetTypeDefinition((TypeDefinitionHandle)handle); + return Combine(reader.GetString(typeDef.Namespace), reader.GetString(typeDef.Name)); + + default: + return null; + } + + static string Combine(string ns, string name) => + string.IsNullOrEmpty(ns) ? name : $"{ns}.{name}"; + } + + /// + /// Decodes the value of a custom attribute whose constructor takes a single string argument. + /// + private static string? DecodeStringArgument(MetadataReader reader, CustomAttribute attribute) + { + BlobReader blob = reader.GetBlobReader(attribute.Value); + + // Every custom attribute value blob begins with a fixed 0x0001 prolog; anything else means + // the blob is malformed or not the single-string shape expected here. + if (blob.RemainingBytes < 2 || blob.ReadUInt16() != 0x0001) + { + return null; + } + + return blob.ReadSerializedString(); + } +} diff --git a/tools/PackageValidator/src/BinaryClassifier.cs b/tools/PackageValidator/src/BinaryClassifier.cs new file mode 100644 index 0000000000..3ff4151913 --- /dev/null +++ b/tools/PackageValidator/src/BinaryClassifier.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. + +namespace PackageValidator; + +/// +/// Classifies a DLL path into the role it plays inside a NuGet package. +/// +internal static class BinaryClassifier +{ + /// + /// Classifies a managed DLL by its path within the package. + /// + /// The DLL path within the package archive (forward-slash separated). + /// The inferred (never ). + public static BinaryKind Classify(string path) + { + string normalized = path.Replace('\\', '/'); + string fileName = System.IO.Path.GetFileName(normalized); + + // Satellite resource assemblies are named ".resources.dll" and live under a culture + // subfolder. The name suffix alone is a reliable discriminator. + if (fileName.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase)) + { + return BinaryKind.Satellite; + } + + // Reference assemblies are shipped under ref/ and carry no executable IL. + if (normalized.StartsWith("ref/", StringComparison.OrdinalIgnoreCase) + || normalized.Contains("/ref/", StringComparison.OrdinalIgnoreCase)) + { + return BinaryKind.Reference; + } + + // Implementation assemblies live under lib/ or runtimes//lib/. Restrict the nested + // match to runtimes/ paths so unrelated folders (for example build/lib/) are not treated + // as implementation assemblies. + if (normalized.StartsWith("lib/", StringComparison.OrdinalIgnoreCase) + || (normalized.StartsWith("runtimes/", StringComparison.OrdinalIgnoreCase) + && normalized.Contains("/lib/", StringComparison.OrdinalIgnoreCase))) + { + return BinaryKind.Implementation; + } + + return BinaryKind.Other; + } +} diff --git a/tools/PackageValidator/src/HumanReporter.cs b/tools/PackageValidator/src/HumanReporter.cs new file mode 100644 index 0000000000..f26295691b --- /dev/null +++ b/tools/PackageValidator/src/HumanReporter.cs @@ -0,0 +1,239 @@ +// 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 PackageValidator; + +/// +/// Renders a to standard output in a human-readable layout. +/// +internal static class HumanReporter +{ + /// + /// Writes the full run: each package's details and findings, then any cross-package findings and + /// the run summary. + /// + /// The validation run to render. + public static void Print(ValidationRun run) + { + for (int i = 0; i < run.Packages.Count; i++) + { + if (i > 0) + { + Console.WriteLine(new string('=', 78)); + Console.WriteLine(); + } + + PrintPackage(run.Packages[i]); + } + + if (run.CrossPackageFindings is { Count: > 0 } cross) + { + Console.WriteLine(new string('=', 78)); + Console.WriteLine(); + Console.WriteLine("Cross-package findings:"); + foreach (Finding finding in cross) + { + Console.WriteLine($" {FormatFinding(finding)}"); + } + Console.WriteLine(); + } + + PrintSummary(run.Summary); + } + + /// + /// Writes a single package's details and findings. + /// + /// The package report to render. + private static void PrintPackage(PackageReport report) + { + SymbolPackageInfo sym = report.SymbolPackage; + + Console.WriteLine($"Package file: {report.PackageFile}"); + Console.WriteLine($"NuGet package id: {report.PackageId ?? "(unknown)"}"); + Console.WriteLine($"NuGet version: {report.PackageVersion ?? "(unknown)"}"); + Console.WriteLine($"Package signed: {(report.IsSigned ? "yes" : "no")}"); + Console.WriteLine($"Symbol package: {DescribeSymbolPackage(sym)}"); + + PrintDependencies(report.DependencyGroups); + Console.WriteLine(); + + if (report.Binaries.Count == 0) + { + Console.WriteLine("No .dll files found in package."); + } + else + { + foreach (BinaryReport asm in report.Binaries) + { + PrintBinary(asm, sym); + } + + PrintSymbolSummary(sym); + } + + PrintFindings(report.Findings); + Console.WriteLine(); + } + + /// Writes a package's declared dependencies. + private static void PrintDependencies(List? groups) + { + if (groups is not { Count: > 0 }) + { + return; + } + + Console.WriteLine("Dependencies:"); + foreach (DependencyGroup group in groups) + { + string tfm = group.TargetFramework ?? "(all frameworks)"; + Console.WriteLine($" [{tfm}]"); + if (group.Dependencies.Count == 0) + { + Console.WriteLine(" (none)"); + continue; + } + foreach (DependencyInfo dependency in group.Dependencies) + { + Console.WriteLine($" {dependency.Id} {dependency.VersionRange ?? "(any)"}"); + } + } + } + + /// Writes a single binary's details. + private static void PrintBinary(BinaryReport asm, SymbolPackageInfo sym) + { + Console.WriteLine($"{asm.Path} [{asm.Kind}]"); + + if (!asm.IsManagedAssembly) + { + Console.WriteLine(" (native / unmanaged DLL - no managed metadata)"); + if (asm.NativeVersion is { } native) + { + Console.WriteLine($" File version: {native.FileVersion ?? "(none)"}"); + Console.WriteLine($" Product version: {native.ProductVersion ?? "(none)"}"); + Console.WriteLine($" Product name: {native.ProductName ?? "(none)"}"); + Console.WriteLine($" Architecture: {native.Architecture ?? "(unknown)"}"); + } + Console.WriteLine(); + return; + } + + Console.WriteLine($" Assembly version: {asm.AssemblyVersion}"); + Console.WriteLine($" File version: {asm.FileVersion ?? "(none)"}"); + Console.WriteLine($" Informational version: {asm.InformationalVersion ?? "(none)"}"); + Console.WriteLine($" Target framework: {asm.TargetFramework ?? "(none)"}"); + Console.WriteLine($" Public key token: {asm.PublicKeyToken ?? "(none)"}"); + Console.WriteLine($" Signing status: {asm.SigningStatus?.ToString() ?? "(unknown)"}"); + Console.WriteLine($" Strong name: {asm.StrongName}"); + Console.WriteLine($" Debug (PDB) id: {asm.DebugId ?? "(none)"}"); + Console.WriteLine($" Embedded symbols: {FormatTriState(asm.HasEmbeddedSymbols)}"); + + if (sym.Status == "present") + { + Console.WriteLine($" Symbol package PDB: {DescribeSymbolPackagePdb(asm)}"); + } + + Console.WriteLine(); + } + + /// Renders the package header's symbol-package line. + private static string DescribeSymbolPackage(SymbolPackageInfo sym) => sym.Status switch + { + "present" => sym.File!, + "missing" => "(no sibling .snupkg found beside the package)", + "skipped" => "(symbol package processing disabled via --no-snupkg)", + _ => sym.Status, + }; + + /// Describes whether the symbol package supplies a matching PDB for an assembly. + private static string DescribeSymbolPackagePdb(BinaryReport asm) + { + if (asm.HasSymbolPackageSymbols != true) + { + return "none"; + } + + if (asm.SymbolPackageSymbolsMatch != true) + { + return $"{asm.SymbolPackageFile} (MISMATCH - symbols are from a different build)"; + } + + string checksum = asm.SymbolPackageVerifiedByChecksum switch + { + true => "checksum verified", + false => "CHECKSUM MISMATCH", + null => "GUID match, checksum not verified", + }; + return $"{asm.SymbolPackageFile} (matches assembly; {checksum})"; + } + + /// Writes the trailing symbol-coverage summary block. + private static void PrintSymbolSummary(SymbolPackageInfo sym) + { + Console.WriteLine("Symbol summary:"); + Console.WriteLine($" Implementation assemblies have symbols: {FormatTriState(sym.AllImplementationAssembliesHaveSymbols)}"); + + switch (sym.Status) + { + case "present": + Console.WriteLine($" All symbol-package symbols match: {FormatTriState(sym.AllSymbolsMatch)}"); + if (sym.OrphanSymbolFiles is { Count: > 0 } orphans) + { + Console.WriteLine(" Symbol files with no matching assembly:"); + foreach (string orphan in orphans) + { + Console.WriteLine($" {orphan}"); + } + } + break; + + case "missing": + Console.WriteLine(" Symbol package: none found beside the package (embedded symbols still evaluated)."); + break; + + case "skipped": + Console.WriteLine(" Symbol package: processing disabled via --no-snupkg (embedded symbols still evaluated)."); + break; + } + + Console.WriteLine(); + } + + /// Writes a package's findings, grouped under a heading. + private static void PrintFindings(List? findings) + { + if (findings is not { Count: > 0 }) + { + Console.WriteLine("Findings: none"); + return; + } + + Console.WriteLine("Findings:"); + foreach (Finding finding in findings) + { + Console.WriteLine($" {FormatFinding(finding)}"); + } + } + + /// Formats a single finding as a one-line entry. + private static string FormatFinding(Finding finding) => + $"[{finding.Severity.ToString().ToUpperInvariant()}] {finding.Category}: {finding.Target} - {finding.Message}"; + + /// Writes the run-level summary. + private static void PrintSummary(ValidationSummary summary) + { + Console.WriteLine("Run summary:"); + Console.WriteLine($" Packages inspected: {summary.PackageCount}"); + Console.WriteLine($" Errors: {summary.ErrorCount}"); + Console.WriteLine($" Warnings: {summary.WarningCount}"); + Console.WriteLine($" Info: {summary.InfoCount}"); + Console.WriteLine($" Gate: {(summary.Failed ? "FAILED" : "passed")}"); + } + + /// Formats a nullable boolean as yes/no/n/a. + private static string FormatTriState(bool? value) => + value switch { true => "yes", false => "no", null => "n/a" }; +} diff --git a/tools/PackageValidator/src/Json.cs b/tools/PackageValidator/src/Json.cs new file mode 100644 index 0000000000..959ca83cf1 --- /dev/null +++ b/tools/PackageValidator/src/Json.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace PackageValidator; + +/// +/// Source-generated for the validation run output, providing +/// trim- and AOT-friendly serialization with indented, camel-cased output that omits null members +/// and writes enums as strings. +/// +[JsonSourceGenerationOptions( + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + UseStringEnumConverter = true)] +[JsonSerializable(typeof(ValidationRun))] +internal sealed partial class JsonContext : JsonSerializerContext +{ + private static JsonSerializerOptions? s_serializerOptions; + + /// + /// Gets serializer options based on this context's generated metadata but with relaxed escaping, + /// so characters that are safe inside a JSON string (such as +) are emitted literally + /// rather than as \uXXXX escapes. + /// + /// + /// Built lazily rather than in a field initializer to avoid a static-initialization ordering + /// dependency on the source-generated instance. + /// + public static JsonSerializerOptions SerializerOptions => + s_serializerOptions ??= new(Default.Options) + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; +} diff --git a/tools/PackageValidator/src/Models.Common.cs b/tools/PackageValidator/src/Models.Common.cs new file mode 100644 index 0000000000..317b579a55 --- /dev/null +++ b/tools/PackageValidator/src/Models.Common.cs @@ -0,0 +1,139 @@ +// 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.Text.Json.Serialization; + +namespace PackageValidator; + +/// +/// Classifies a DLL by the role it plays inside a NuGet package. Symbol-coverage expectations +/// differ by kind: only assemblies are expected to ship symbols, +/// while reference assemblies and satellite resource assemblies legitimately have none. +/// +internal enum BinaryKind +{ + /// An implementation assembly under lib/ or runtimes/.../lib/. + Implementation, + + /// A reference (compile-time) assembly under ref/. + Reference, + + /// A satellite resource assembly (*.resources.dll in a culture subfolder). + Satellite, + + /// A native (unmanaged) binary, such as an SNI runtime. + Native, + + /// A managed DLL that does not fit any of the other categories. + Other, +} + +/// +/// The strong-name signing state of a managed assembly. +/// +internal enum SigningStatus +{ + /// The assembly has no public key and is not strong-name signed. + Unsigned, + + /// + /// The assembly carries a public key but the strong-name signed flag is not set, meaning it was + /// delay-signed and never completed signing. + /// + DelaySigned, + + /// The assembly carries a public key and the strong-name signed flag is set. + Signed, +} + +/// +/// The severity of a validation finding. +/// +internal enum Severity +{ + /// Informational; surfaced for visibility but not necessarily a problem. + Info, + + /// A potential problem that may warrant attention. + Warning, + + /// A definite problem. + Error, +} + +/// +/// A single validation result produced by the rules engine. +/// +internal sealed class Finding +{ + /// Gets the severity of the finding. + public required Severity Severity { get; init; } + + /// Gets the stable category key (for example "missing-symbols") used by --fail-on. + public required string Category { get; init; } + + /// Gets the target the finding applies to (a package file, an assembly path, etc.). + public required string Target { get; init; } + + /// Gets the human-readable explanation of the finding. + public required string Message { get; init; } +} + +/// +/// A NuGet package dependency declared in the .nuspec. +/// +internal sealed class DependencyInfo +{ + /// Gets the dependency package id. + public required string Id { get; init; } + + /// Gets the declared version range, or if none was specified. + public string? VersionRange { get; init; } +} + +/// +/// A group of dependencies, optionally scoped to a single target framework. +/// +internal sealed class DependencyGroup +{ + /// Gets the target framework moniker for this group, or for the framework-agnostic group. + public string? TargetFramework { get; init; } + + /// Gets the dependencies declared in this group. + public required List Dependencies { get; init; } +} + +/// +/// Win32 version-resource information read from a native binary. +/// +internal sealed class NativeVersionInfo +{ + /// Gets the FileVersion string from the Win32 version resource, if present. + public string? FileVersion { get; init; } + + /// Gets the ProductVersion string from the Win32 version resource, if present. + public string? ProductVersion { get; init; } + + /// Gets the ProductName string from the Win32 version resource, if present. + public string? ProductName { get; init; } + + /// Gets the processor architecture of the native image (for example "x64"), if known. + public string? Architecture { get; init; } +} + +/// +/// A PDB checksum recorded in an assembly's debug directory, used to verify a matched PDB byte-for-byte. +/// +internal sealed class PdbChecksum +{ + /// Gets the hash algorithm name (for example "SHA256"). + public required string Algorithm { get; init; } + + /// Gets the checksum bytes, excluded from JSON output to keep it compact. + [JsonIgnore] + public required byte[] Hash { get; init; } + + /// Gets the checksum as a lowercase hex string for display. + public string HashHex => Convert.ToHexStringLower(Hash); +} diff --git a/tools/PackageValidator/src/Models.Report.cs b/tools/PackageValidator/src/Models.Report.cs new file mode 100644 index 0000000000..23979d551b --- /dev/null +++ b/tools/PackageValidator/src/Models.Report.cs @@ -0,0 +1,238 @@ +// 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.Text.Json.Serialization; + +namespace PackageValidator; + +/// +/// The top-level result of a validation run over one or more NuGet packages. +/// +internal sealed class ValidationRun +{ + /// Gets the per-package reports, ordered by package file name. + public required List Packages { get; init; } + + /// + /// Gets findings that span multiple packages (for example dependency version inconsistencies + /// between packages in the same batch), or when there are none. + /// + public List? CrossPackageFindings { get; set; } + + /// Gets the run-level summary counts. + public required ValidationSummary Summary { get; set; } +} + +/// +/// Aggregate counts describing the outcome of a validation run. +/// +internal sealed class ValidationSummary +{ + /// Gets the number of packages inspected. + public required int PackageCount { get; init; } + + /// Gets the total number of findings across the run. + public required int ErrorCount { get; set; } + + /// Gets the total number of findings across the run. + public required int WarningCount { get; set; } + + /// Gets the total number of findings across the run. + public required int InfoCount { get; set; } + + /// + /// Gets a value indicating whether the run failed its configured --fail-on gate. + /// when no gate was configured or nothing matched it. + /// + public bool Failed { get; set; } +} + +/// +/// The result of inspecting and validating a single NuGet package: its identity, dependencies, +/// signature state, a report for each DLL it contains, the state of its sibling symbol package, +/// and the findings produced by the rules engine. +/// +internal sealed class PackageReport +{ + /// Gets the file name of the inspected .nupkg. + public required string PackageFile { get; init; } + + /// Gets the package id from the .nuspec, or if unavailable. + public string? PackageId { get; init; } + + /// Gets the package version from the .nuspec, or if unavailable. + public string? PackageVersion { get; init; } + + /// Gets a value indicating whether the package carries a NuGet author/repository signature. + public bool IsSigned { get; init; } + + /// Gets the dependency groups declared in the .nuspec, or if none. + public List? DependencyGroups { get; init; } + + /// Gets the per-DLL reports, ordered by their path within the package. + public required List Binaries { get; init; } + + /// Gets the state and findings of the sibling .snupkg symbol package. + public required SymbolPackageInfo SymbolPackage { get; init; } + + /// Gets the validation findings for this package, or when there are none. + public List? Findings { get; set; } +} + +/// +/// Package-level summary of the sibling .snupkg symbol package and how well its contents +/// correspond to the assemblies in the main package. +/// +internal sealed class SymbolPackageInfo +{ + /// + /// Gets the processing state: "present" (a sibling symbol package was found and checked), + /// "missing" (no sibling symbol package exists), or "skipped" (processing was + /// disabled via --no-snupkg). + /// + public required string Status { get; init; } + + /// Gets the symbol package file name, or unless is "present". + public string? File { get; init; } + + /// + /// Gets whether every implementation assembly has usable symbols (embedded, or a matching + /// symbol-package PDB). when there are no implementation assemblies. + /// + public bool? AllImplementationAssembliesHaveSymbols { get; init; } + + /// + /// Gets whether every assembly with symbol-package symbols has symbols that match its build. + /// when no symbol package was processed or none of its symbols apply. + /// + public bool? AllSymbolsMatch { get; init; } + + /// + /// Gets the symbol package PDB paths that did not correspond to any assembly, or + /// when there are none. + /// + public List? OrphanSymbolFiles { get; init; } +} + +/// +/// Version, identity, signing, and symbol information for a single DLL found inside a NuGet +/// package. For native or non-assembly DLLs, the managed fields are and +/// carries any Win32 version-resource data instead. +/// +internal sealed class BinaryReport +{ + /// Gets the path of the DLL within the package archive. + public required string Path { get; init; } + + /// Gets the role this DLL plays inside the package. + public required BinaryKind Kind { get; init; } + + /// + /// Gets a value indicating whether the DLL is a managed assembly with a manifest. When + /// , the managed version fields are . + /// + public bool IsManagedAssembly { get; init; } + + /// Gets the simple assembly name from the manifest. + public string? AssemblyName { get; init; } + + /// Gets the four-part assembly version (the .NET assembly version). + public string? AssemblyVersion { get; init; } + + /// Gets the value of , if present. + public string? FileVersion { get; init; } + + /// Gets the value of , if present. + public string? InformationalVersion { get; init; } + + /// Gets the value of , if present. + public string? TargetFramework { get; init; } + + /// Gets the assembly culture, or "neutral" for culture-independent assemblies. + public string? Culture { get; init; } + + /// Gets the strong-name public key token, or if unsigned. + public string? PublicKeyToken { get; init; } + + /// Gets the strong-name signing state of the assembly. + public SigningStatus? SigningStatus { get; init; } + + /// Gets the full strong-name display name (name, version, culture, public key token). + public string? StrongName { get; init; } + + /// Gets the Win32 version-resource information for a native binary, if any. + public NativeVersionInfo? NativeVersion { get; init; } + + /// + /// Gets the assembly's CodeView debug GUID used to match it to a PDB. Excluded from serialized + /// output; the formatted is emitted instead. + /// + [JsonIgnore] + public Guid? CodeViewGuid { get; init; } + + /// Gets the assembly's debug (PDB) identity GUID as a string, or if absent. + public string? DebugId => CodeViewGuid?.ToString(); + + /// + /// Gets the PDB checksums recorded in the assembly's debug directory, used to verify a matched + /// PDB. Excluded from JSON; exposes a display-friendly view. + /// + [JsonIgnore] + public List? Checksums { get; init; } + + /// Gets the PDB checksum algorithms recorded for this assembly, for display, or if none. + public List? PdbChecksums => + Checksums is { Count: > 0 } ? Checksums.Select(c => $"{c.Algorithm}:{c.HashHex}").ToList() : null; + + /// + /// Gets a value indicating whether the assembly embeds its own portable PDB. Always evaluated, + /// independent of symbol-package processing. + /// + public bool? HasEmbeddedSymbols { get; init; } + + /// + /// Gets a value indicating whether usable symbols are available for this assembly, either + /// embedded or via a matching symbol-package PDB. + /// + public bool? HasSymbols { get; set; } + + /// + /// Gets a value indicating whether the symbol package contains a PDB for this assembly. + /// when no symbol package was processed. + /// + public bool? HasSymbolPackageSymbols { get; set; } + + /// + /// Gets a value indicating whether the symbol-package PDB matches this assembly's build (by + /// debug GUID). when the symbol package has no PDB for this assembly or + /// was not processed. + /// + public bool? SymbolPackageSymbolsMatch { get; set; } + + /// + /// Gets a value indicating whether the matched symbol-package PDB was verified byte-for-byte + /// against the assembly's recorded PDB checksum. when a checksum matched, + /// when a checksum was available but did not match, and + /// when no checksum could be evaluated. + /// + public bool? SymbolPackageVerifiedByChecksum { get; set; } + + /// Gets the matching PDB's path within the symbol package, if any. + public string? SymbolPackageFile { get; set; } + + /// + /// Creates a report for a native or non-assembly DLL, recording its path, native version info, + /// and marking it as unmanaged. + /// + /// The DLL's path within the package archive. + /// Win32 version-resource information, if any was read. + /// A with set to . + public static BinaryReport Native(string path, NativeVersionInfo? nativeVersion = null) => new() + { + Path = path, + Kind = BinaryKind.Native, + IsManagedAssembly = false, + NativeVersion = nativeVersion, + }; +} diff --git a/tools/PackageValidator/src/NativeVersionReader.cs b/tools/PackageValidator/src/NativeVersionReader.cs new file mode 100644 index 0000000000..8eeced4cb0 --- /dev/null +++ b/tools/PackageValidator/src/NativeVersionReader.cs @@ -0,0 +1,114 @@ +// 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.Reflection.PortableExecutable; + +namespace PackageValidator; + +/// +/// Reads Win32 version-resource information from native (unmanaged) binaries. +/// +internal static class NativeVersionReader +{ + /// + /// Reads the Win32 version resource and architecture from a native binary's bytes. + /// + /// The full bytes of the native DLL. + /// + /// A , or if no version-resource or + /// architecture data could be read. + /// + public static NativeVersionInfo? Read(byte[] bytes) + { + string? architecture = ReadArchitecture(bytes); + + string? fileVersion = null; + string? productVersion = null; + string? productName = null; + + // FileVersionInfo needs a real file path; it parses the PE version resource on all + // platforms, so write the bytes to a temp file, read, and clean up. + string tempPath = Path.Combine(Path.GetTempPath(), $"pkgval-{Guid.NewGuid():N}.dll"); + try + { + File.WriteAllBytes(tempPath, bytes); + FileVersionInfo info = FileVersionInfo.GetVersionInfo(tempPath); + fileVersion = NullIfEmpty(info.FileVersion); + productVersion = NullIfEmpty(info.ProductVersion); + productName = NullIfEmpty(info.ProductName); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or System.Security.SecurityException) + { + // Could not stage or read the temp file; fall back to architecture only. + } + finally + { + TryDelete(tempPath); + } + + if (fileVersion is null && productVersion is null && productName is null && architecture is null) + { + return null; + } + + return new NativeVersionInfo + { + FileVersion = fileVersion, + ProductVersion = productVersion, + ProductName = productName, + Architecture = architecture, + }; + } + + /// + /// Reads the processor architecture from a PE image's COFF header. + /// + /// The full bytes of the binary. + /// A short architecture name, or if it cannot be determined. + private static string? ReadArchitecture(byte[] bytes) + { + try + { + using var pe = new PEReader(new MemoryStream(bytes, writable: false)); + return pe.PEHeaders.CoffHeader.Machine switch + { + Machine.Amd64 => "x64", + Machine.I386 => "x86", + Machine.Arm64 => "arm64", + Machine.Arm => "arm", + Machine.IA64 => "ia64", + Machine.Unknown => null, + var other => other.ToString().ToLowerInvariant(), + }; + } + catch (BadImageFormatException) + { + return null; + } + } + + /// Returns for empty or whitespace strings, otherwise the trimmed value. + private static string? NullIfEmpty(string? value) + { + string? trimmed = value?.Trim(); + return string.IsNullOrEmpty(trimmed) ? null : trimmed; + } + + /// Deletes a file, ignoring any failure. + private static void TryDelete(string path) + { + try + { + if (File.Exists(path)) + { + File.Delete(path); + } + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or System.Security.SecurityException) + { + // Best-effort cleanup. + } + } +} diff --git a/tools/PackageValidator/src/PackageInspector.cs b/tools/PackageValidator/src/PackageInspector.cs new file mode 100644 index 0000000000..3fb0c86427 --- /dev/null +++ b/tools/PackageValidator/src/PackageInspector.cs @@ -0,0 +1,160 @@ +// 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.IO.Compression; +using System.Xml.Linq; + +namespace PackageValidator; + +/// +/// Opens a NuGet package and builds a describing its metadata, +/// dependencies, signature state, contained binaries, and symbol-package state. +/// +internal static class PackageInspector +{ + /// The archive entry name of a NuGet package signature. + private const string SignatureEntryName = ".signature.p7s"; + + /// + /// Inspects a NuGet package. + /// + /// The path to the .nupkg file on disk. + /// + /// When , a like-named .snupkg beside the package is located and + /// cross-checked against the assemblies; when , symbol-package processing + /// is skipped (embedded symbols are still evaluated). + /// + /// A populated . + public static PackageReport Inspect(string packagePath, bool processSnupkg) + { + // A .nupkg is a zip archive; open it read-only and walk its entries. + using ZipArchive archive = ZipFile.OpenRead(packagePath); + + // Package-level identity and dependencies come from the embedded .nuspec manifest. + (string? id, string? version, List? dependencyGroups) = ReadNuspec(archive); + + // A NuGet signature is stored as a top-level .signature.p7s archive entry. + bool isSigned = archive.Entries.Any( + e => e.FullName.Equals(SignatureEntryName, StringComparison.OrdinalIgnoreCase)); + + // Inspect every .dll entry regardless of where it sits in the package (lib/, ref/, + // runtimes/, etc.). Native DLLs are tolerated and reported as unmanaged. + var binaries = new List(); + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (!entry.FullName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + binaries.Add(AssemblyInspector.Inspect(entry)); + } + + // Sort by archive path so output is deterministic across runs and platforms. + binaries.Sort((a, b) => string.CompareOrdinal(a.Path, b.Path)); + + // Cross-check the sibling symbol package (if any) against the binaries just discovered. This + // mutates the per-binary symbol fields in place. + SymbolPackageInfo symbolPackage = SymbolResolver.Resolve(packagePath, binaries, processSnupkg); + + return new PackageReport + { + PackageFile = Path.GetFileName(packagePath), + PackageId = id, + PackageVersion = version, + IsSigned = isSigned, + DependencyGroups = dependencyGroups, + Binaries = binaries, + SymbolPackage = symbolPackage, + }; + } + + /// + /// Reads the package id, version, and dependencies from the .nuspec manifest. + /// + /// The opened NuGet package archive. + /// The package id, version, and dependency groups, each of which may be . + private static (string? Id, string? Version, List? Dependencies) ReadNuspec(ZipArchive archive) + { + // The canonical manifest sits at the archive root, so prefer a top-level .nuspec. + ZipArchiveEntry? nuspec = archive.Entries.FirstOrDefault( + e => e.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase) + && !e.FullName.Contains('/')); + + // Fall back to any .nuspec anywhere in the package for non-standard layouts. + nuspec ??= archive.Entries.FirstOrDefault( + e => e.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase)); + + if (nuspec is null) + { + return (null, null, null); + } + + using Stream stream = nuspec.Open(); + XDocument doc = XDocument.Load(stream); + + // .nuspec files declare an XML namespace that varies by schema version. Matching on + // LocalName keeps this robust regardless of which namespace URI is in use. + XElement? metadata = doc.Root?.Elements() + .FirstOrDefault(e => e.Name.LocalName == "metadata"); + + string? id = metadata?.Elements().FirstOrDefault(e => e.Name.LocalName == "id")?.Value?.Trim(); + string? version = metadata?.Elements().FirstOrDefault(e => e.Name.LocalName == "version")?.Value?.Trim(); + List? dependencies = ReadDependencies(metadata); + + return (id, version, dependencies); + } + + /// + /// Parses the <dependencies> element of a .nuspec, supporting both the flat + /// (ungrouped) and grouped-by-target-framework layouts. + /// + /// The <metadata> element, or . + /// The parsed dependency groups, or if none are declared. + private static List? ReadDependencies(XElement? metadata) + { + XElement? dependencies = metadata?.Elements() + .FirstOrDefault(e => e.Name.LocalName == "dependencies"); + if (dependencies is null) + { + return null; + } + + var groups = new List(); + + // Flat dependencies (no ) apply to all target frameworks. + List ungrouped = dependencies.Elements() + .Where(e => e.Name.LocalName == "dependency") + .Select(ReadDependency) + .ToList(); + if (ungrouped.Count > 0) + { + groups.Add(new DependencyGroup { TargetFramework = null, Dependencies = ungrouped }); + } + + // Grouped dependencies are scoped to a target framework. + foreach (XElement group in dependencies.Elements().Where(e => e.Name.LocalName == "group")) + { + string? tfm = group.Attribute("targetFramework")?.Value?.Trim(); + List deps = group.Elements() + .Where(e => e.Name.LocalName == "dependency") + .Select(ReadDependency) + .ToList(); + groups.Add(new DependencyGroup { TargetFramework = tfm, Dependencies = deps }); + } + + return groups.Count == 0 ? null : groups; + } + + /// + /// Reads a single <dependency> element. + /// + /// The dependency element. + /// The parsed . + private static DependencyInfo ReadDependency(XElement element) => new() + { + Id = element.Attribute("id")?.Value?.Trim() ?? "(unknown)", + VersionRange = element.Attribute("version")?.Value?.Trim(), + }; +} diff --git a/tools/PackageValidator/src/PackageValidator.csproj b/tools/PackageValidator/src/PackageValidator.csproj new file mode 100644 index 0000000000..68c0a26b92 --- /dev/null +++ b/tools/PackageValidator/src/PackageValidator.csproj @@ -0,0 +1,22 @@ + + + + Exe + net10.0 + enable + enable + latest + PackageValidator + PackageValidator + true + + + + + + + + + + + diff --git a/tools/PackageValidator/src/PortablePdb.cs b/tools/PackageValidator/src/PortablePdb.cs new file mode 100644 index 0000000000..90a3a66a35 --- /dev/null +++ b/tools/PackageValidator/src/PortablePdb.cs @@ -0,0 +1,205 @@ +// 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.Reflection.Metadata; +using System.Security.Cryptography; + +namespace PackageValidator; + +/// +/// Low-level helpers for reading identity and checksum information out of portable PDB byte +/// streams, used to match a symbol-package PDB to the assembly it belongs to. +/// +internal static class PortablePdb +{ + /// The metadata header signature BSJB, marking the start of a portable PDB blob. + private const uint MetadataSignature = 0x424A5342; + + /// The length in bytes of the PDB id (a 16-byte GUID followed by a 4-byte stamp). + private const int PdbIdLength = 20; + + /// + /// Reads the debug GUID from a portable PDB. + /// + /// The full PDB byte content. + /// The PDB's debug GUID, or if the bytes are not a readable portable PDB. + public static Guid? TryReadGuid(byte[] pdb) + { + try + { + using var stream = new MemoryStream(pdb, writable: false); + using MetadataReaderProvider provider = MetadataReaderProvider.FromPortablePdbStream(stream); + MetadataReader reader = provider.GetMetadataReader(); + + // The PDB id begins with the 16-byte GUID that matches the assembly's CodeView GUID; the + // trailing bytes are a timestamp/stamp we do not need here. + DebugMetadataHeader? header = reader.DebugMetadataHeader; + if (header is not null && header.Id.Length >= 16) + { + return new Guid(header.Id.AsSpan(0, 16)); + } + } + catch (BadImageFormatException) + { + // Not a portable PDB (for example a Windows PDB); it cannot be matched by GUID. + } + + return null; + } + + /// + /// Determines whether the given bytes look like a legacy Windows (MSF) PDB rather than a + /// portable PDB. Windows PDBs cannot be matched by the portable-PDB GUID scheme. + /// + /// The PDB byte content. + /// if the bytes begin with the Windows PDB MSF signature. + public static bool IsWindowsPdb(byte[] pdb) + { + // Windows PDBs begin with the ASCII magic "Microsoft C/C++ MSF 7.00\r\n\x1aDS". + ReadOnlySpan magic = "Microsoft C/C++ MSF 7.00"u8; + return pdb.Length >= magic.Length && pdb.AsSpan(0, magic.Length).SequenceEqual(magic); + } + + /// + /// Verifies a portable PDB against a checksum recorded in the owning assembly's debug directory. + /// + /// The full PDB byte content. + /// The checksum (algorithm and expected hash) recorded by the assembly. + /// + /// when the computed hash matches the expected hash; + /// when it does not; when the checksum could not be evaluated (unknown + /// algorithm or an unparseable PDB layout). + /// + /// + /// The recorded checksum is computed over the PDB content with its 20-byte PDB id zeroed, because + /// for deterministic builds the id is derived from the checksum and written afterward. This + /// method therefore locates the #Pdb stream, zeroes the id in a working copy, and hashes + /// that copy with the named algorithm. + /// + public static bool? TryVerifyChecksum(byte[] pdb, PdbChecksum checksum) + { + int idOffset = FindPdbIdOffset(pdb); + if (idOffset < 0 || idOffset + PdbIdLength > pdb.Length) + { + return null; + } + + using HashAlgorithm? algorithm = CreateHashAlgorithm(checksum.Algorithm); + if (algorithm is null) + { + return null; + } + + // Hash a copy in which the PDB id bytes are zeroed, matching how the checksum was produced. + byte[] working = (byte[])pdb.Clone(); + Array.Clear(working, idOffset, PdbIdLength); + byte[] computed = algorithm.ComputeHash(working); + + return computed.AsSpan().SequenceEqual(checksum.Hash); + } + + /// + /// Locates the byte offset of the PDB id (the start of the #Pdb metadata stream) within a + /// portable PDB. + /// + /// The PDB byte content. + /// The offset of the #Pdb stream, or -1 if it cannot be located. + private static int FindPdbIdOffset(byte[] pdb) + { + try + { + using var reader = new BinaryReader(new MemoryStream(pdb, writable: false)); + + if (reader.ReadUInt32() != MetadataSignature) + { + return -1; + } + + reader.ReadUInt16(); // Major version. + reader.ReadUInt16(); // Minor version. + reader.ReadUInt32(); // Reserved. + + int versionLength = reader.ReadInt32(); + + // A malformed or hostile PDB can encode a negative or oversized length; treat it as + // unparseable rather than letting the seek below throw and fail the whole run. + if (versionLength < 0 || versionLength > pdb.Length) + { + return -1; + } + + reader.BaseStream.Position += versionLength; // Version string (padded to a 4-byte boundary). + + // ECMA-335 rounds the recorded version length up to a multiple of four, so the position is + // normally already aligned; align defensively in case a producer records an unrounded + // length, otherwise the following flags/stream-header reads would be misaligned. + reader.BaseStream.Position = (reader.BaseStream.Position + 3) & ~3L; + + reader.ReadUInt16(); // Flags. + ushort streamCount = reader.ReadUInt16(); + + for (int i = 0; i < streamCount; i++) + { + uint offset = reader.ReadUInt32(); + reader.ReadUInt32(); // Stream size. + string name = ReadStreamName(reader); + if (name == "#Pdb") + { + return (int)offset; + } + } + } + catch (Exception ex) when (ex is EndOfStreamException or IOException or ArgumentOutOfRangeException) + { + // Truncated, malformed, or hostile PDB; treat as unlocatable. + } + + return -1; + } + + /// + /// Reads a null-terminated, 4-byte-aligned ASCII metadata stream name from the current position. + /// + /// The reader positioned at the start of a stream name. + /// The decoded stream name. + private static string ReadStreamName(BinaryReader reader) + { + var bytes = new List(16); + int read = 0; + while (true) + { + byte b = reader.ReadByte(); + read++; + if (b == 0) + { + break; + } + bytes.Add(b); + } + + // Stream names are padded with additional null bytes to a 4-byte boundary. + while (read % 4 != 0) + { + reader.ReadByte(); + read++; + } + + return System.Text.Encoding.ASCII.GetString(bytes.ToArray()); + } + + /// + /// Creates a for a PDB checksum algorithm name. + /// + /// The algorithm name as recorded in the debug directory (for example "SHA256"). + /// A new hash algorithm instance, or if the name is not recognized. + private static HashAlgorithm? CreateHashAlgorithm(string algorithm) => + algorithm.ToUpperInvariant() switch + { + "SHA256" => SHA256.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "SHA1" => SHA1.Create(), + _ => null, + }; +} diff --git a/tools/PackageValidator/src/Program.cs b/tools/PackageValidator/src/Program.cs new file mode 100644 index 0000000000..d706b2aae3 --- /dev/null +++ b/tools/PackageValidator/src/Program.cs @@ -0,0 +1,385 @@ +// 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.CommandLine; +using System.CommandLine.Help; +using System.CommandLine.Invocation; +using System.Text.Json; + +namespace PackageValidator; + +/// +/// Console entry point for the PackageValidator tool, which inspects one or more NuGet packages +/// (.nupkg), reports version, signing, and symbol information for every binary they contain, +/// and applies a set of intrinsic validation rules that callers can use to gate pipelines. +/// +/// +/// +/// Assemblies are inspected purely through metadata without loading them into the runtime. This +/// keeps the tool cross-platform, avoids executing any module initializers, and lets it read +/// assemblies built for target frameworks the tool itself does not run on (for example, .NET +/// Framework assemblies inspected from a .NET host). +/// +/// +/// To exercise the symbol-package (.snupkg) code path with real packages from nuget.org, +/// note that the flat-container feed only serves .nupkg files. The matching symbol package +/// must be downloaded from the gallery's symbol-package endpoint and saved beside the +/// .nupkg with the same base name: +/// +/// # main package +/// https://api.nuget.org/v3-flatcontainer/{idLower}/{version}/{idLower}.{version}.nupkg +/// # sibling symbol package (note: original-cased id, no .snupkg suffix in the URL) +/// https://www.nuget.org/api/v2/symbolpackage/{Id}/{Version} +/// +/// For example, the symbol package for Microsoft.Data.SqlClient 5.2.2 is at +/// https://www.nuget.org/api/v2/symbolpackage/Microsoft.Data.SqlClient/5.2.2; save it as +/// microsoft.data.sqlclient.5.2.2.snupkg next to the .nupkg so the tool finds it. +/// +/// +internal static class Program +{ + /// + /// Parses command-line arguments and dispatches to the inspection and validation logic. + /// + /// The raw command-line arguments. + /// + /// A process exit code: 0 on success with no gating findings, 1 on a + /// runtime/inspection failure, 2 when the --fail-on gate is triggered, and a + /// non-zero System.CommandLine code for argument parsing errors. + /// + private static int Main(string[] args) + { + // One or more paths, each a .nupkg file or a directory to scan for .nupkg files. + var pathsArgument = new Argument("paths") + { + Description = "One or more .nupkg files or directories to scan for .nupkg files.", + Arity = ArgumentArity.OneOrMore, + }; + + // Opt-in switch that selects machine-readable JSON over the default human-readable layout. + var jsonOption = new Option("--json") + { + Description = "Emit machine-readable JSON instead of human-readable text.", + }; + + // Switch that suppresses looking for and processing sibling .snupkg symbol packages. + var noSnupkgOption = new Option("--no-snupkg") + { + Description = "Do not look for or process sibling .snupkg symbol packages.", + }; + + // Repeatable gate: fail the run when a finding matches the given severity or category. + var failOnOption = new Option("--fail-on") + { + Description = "Exit non-zero when a finding matches a severity or category (see Notes).", + Arity = ArgumentArity.ZeroOrMore, + AllowMultipleArgumentsPerToken = true, + }; + + // Repeatable expected-version assertions, each an optional 'id=' prefix plus a value. + var expectPackageVersionOption = new Option("--expect-package-version") + { + Description = "Confirm the package version (VALUE or id=VALUE; see Notes).", + Arity = ArgumentArity.ZeroOrMore, + AllowMultipleArgumentsPerToken = true, + }; + var expectFileVersionOption = new Option("--expect-file-version") + { + Description = "Confirm each assembly's file version (VALUE or id=VALUE; see Notes).", + Arity = ArgumentArity.ZeroOrMore, + AllowMultipleArgumentsPerToken = true, + }; + var expectAssemblyVersionOption = new Option("--expect-assembly-version") + { + Description = "Confirm each assembly's assembly version (VALUE or id=VALUE; see Notes).", + Arity = ArgumentArity.ZeroOrMore, + AllowMultipleArgumentsPerToken = true, + }; + + var rootCommand = new RootCommand( + "Inspect and validate NuGet packages: report versions, signing, and symbols, and apply " + + "intrinsic validation rules.") + { + pathsArgument, + jsonOption, + noSnupkgOption, + failOnOption, + expectPackageVersionOption, + expectFileVersionOption, + expectAssemblyVersionOption, + }; + + // Append a free-form Notes section to the default --help output. + ConfigureHelp(rootCommand); + + rootCommand.SetAction(parseResult => + { + string[] paths = parseResult.GetValue(pathsArgument) ?? []; + bool json = parseResult.GetValue(jsonOption); + bool noSnupkg = parseResult.GetValue(noSnupkgOption); + string[] failOn = parseResult.GetValue(failOnOption) ?? []; + string[] expectPackage = parseResult.GetValue(expectPackageVersionOption) ?? []; + string[] expectFile = parseResult.GetValue(expectFileVersionOption) ?? []; + string[] expectAssembly = parseResult.GetValue(expectAssemblyVersionOption) ?? []; + + VersionExpectations expectations; + try + { + expectations = VersionExpectations.Parse(expectPackage, expectFile, expectAssembly); + } + catch (FormatException ex) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + + return Run(paths, json, !noSnupkg, failOn, expectations); + }); + + return rootCommand.Parse(args).Invoke(); + } + + /// + /// Appends a free-form "Notes" section to the default --help output, documenting the + /// accepted --fail-on values and the VALUE/id=VALUE form of the + /// --expect-* options without crowding their one-line descriptions. + /// + /// The root command whose help is being customized. + private static void ConfigureHelp(RootCommand rootCommand) + { + // HelpBuilder/HelpContext are not public in this System.CommandLine version, so the free-form + // section is appended by wrapping the help action: render the default help, then the Notes. + foreach (Option option in rootCommand.Options) + { + if (option is HelpOption helpOption + && helpOption.Action is SynchronousCommandLineAction inner) + { + helpOption.Action = new NotesHelpAction(inner); + } + } + } + + /// + /// A help action that renders the default help and then writes a free-form Notes section. + /// + private sealed class NotesHelpAction(SynchronousCommandLineAction inner) : SynchronousCommandLineAction + { + /// + public override int Invoke(ParseResult parseResult) + { + int result = inner.Invoke(parseResult); + Console.Out.WriteLine(); + WriteNotes(Console.Out); + return result; + } + } + + /// + /// Writes the free-form Notes help section. + /// + /// The output writer to write the section to. + private static void WriteNotes(TextWriter w) + { + w.WriteLine("Notes:"); + w.WriteLine(); + w.WriteLine(" --fail-on accepts a severity, the token 'any', or a finding category."); + w.WriteLine(" A finding trips the gate when its severity name or its category matches a"); + w.WriteLine(" supplied token; 'any' matches every finding. Exit code is 2 when tripped."); + w.WriteLine(" severities: error warning info"); + w.WriteLine(" special: any"); + w.WriteLine(" categories:"); + foreach (string category in Categories.All) + { + w.WriteLine($" {category}"); + } + w.WriteLine(); + w.WriteLine(" --expect-package-version / --expect-file-version / --expect-assembly-version"); + w.WriteLine(" confirm versions against values the caller already computed. Each value is:"); + w.WriteLine(" VALUE apply the expectation to every package in the run"); + w.WriteLine(" id=VALUE apply only to the package whose id is 'id'"); + w.WriteLine(" A specific id overrides a bare VALUE (wildcard), and each option may be"); + w.WriteLine(" repeated, so a family-wide expectation can coexist with a per-package override:"); + w.WriteLine(" --expect-file-version *=7.1.0.17604 \\"); + w.WriteLine(" --expect-file-version Microsoft.SqlServer.Server=1.1.0.17604"); + w.WriteLine(" Mismatches are reported as unexpected-package-version, unexpected-file-version,"); + w.WriteLine(" or unexpected-assembly-version findings (Error severity)."); + } + + /// + /// Inspects and validates the resolved packages and renders the result. + /// + /// The file and directory paths supplied on the command line. + /// Whether to emit JSON instead of human-readable text. + /// Whether to process sibling symbol packages. + /// The severity/category tokens that trigger a non-zero gate exit. + /// The caller-supplied expected version values to confirm. + /// The process exit code. + private static int Run( + string[] paths, bool json, bool processSnupkg, string[] failOn, VersionExpectations expectations) + { + List packages; + try + { + packages = ResolvePackages(paths); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + + if (packages.Count == 0) + { + Console.Error.WriteLine("Error: no .nupkg files found at the given path(s)."); + return 1; + } + + var reports = new List(); + try + { + foreach (string package in packages) + { + PackageReport report = PackageInspector.Inspect(package, processSnupkg); + Validator.Validate(report, expectations); + reports.Add(report); + } + } + catch (Exception ex) + { + // Surface a concise message rather than a stack trace; the tool is meant to be consumed + // by humans and scripts, not debugged by its callers. + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + + List crossPackage = Validator.ValidateBatch(reports); + ValidationRun run = BuildRun(reports, crossPackage, failOn); + + if (json) + { + Console.WriteLine(JsonSerializer.Serialize(run, JsonContext.SerializerOptions)); + } + else + { + HumanReporter.Print(run); + } + + return run.Summary.Failed ? 2 : 0; + } + + /// + /// Assembles a from inspected packages, computing summary counts and + /// evaluating the --fail-on gate. + /// + /// The per-package reports. + /// The cross-package findings. + /// The severity/category tokens that trigger a non-zero gate exit. + /// The assembled run. + private static ValidationRun BuildRun( + List reports, List crossPackage, string[] failOn) + { + IEnumerable allFindings = reports + .SelectMany(r => r.Findings ?? Enumerable.Empty()) + .Concat(crossPackage); + + int errors = 0, warnings = 0, infos = 0; + bool failed = false; + var gate = new HashSet(failOn.Select(f => f.Trim().ToLowerInvariant())); + + foreach (Finding finding in allFindings) + { + switch (finding.Severity) + { + case Severity.Error: errors++; break; + case Severity.Warning: warnings++; break; + case Severity.Info: infos++; break; + } + + if (Matches(finding, gate)) + { + failed = true; + } + } + + return new ValidationRun + { + Packages = reports, + CrossPackageFindings = crossPackage.Count == 0 ? null : crossPackage, + Summary = new ValidationSummary + { + PackageCount = reports.Count, + ErrorCount = errors, + WarningCount = warnings, + InfoCount = infos, + Failed = failed, + }, + }; + } + + /// + /// Determines whether a finding matches any of the configured --fail-on tokens. A token + /// matches when it equals the finding's severity name or its category key; the special token + /// any matches every finding. + /// + /// The finding to test. + /// The lowercased set of configured tokens. + /// if the finding should trip the gate. + private static bool Matches(Finding finding, HashSet gate) + { + if (gate.Count == 0) + { + return false; + } + + return gate.Contains("any") + || gate.Contains(finding.Severity.ToString().ToLowerInvariant()) + || gate.Contains(finding.Category); + } + + /// + /// Expands the supplied paths into a sorted, de-duplicated list of .nupkg files. Directory + /// paths are scanned recursively; .snupkg files are excluded. + /// + /// The file and directory paths supplied on the command line. + /// The resolved package file paths. + private static List ResolvePackages(string[] paths) + { + var resolved = new SortedSet(StringComparer.Ordinal); + + // Skip directories the process cannot read rather than aborting the whole scan. + var enumeration = new EnumerationOptions + { + RecurseSubdirectories = true, + IgnoreInaccessible = true, + }; + + foreach (string path in paths) + { + if (Directory.Exists(path)) + { + foreach (string file in Directory.EnumerateFiles(path, "*.nupkg", enumeration)) + { + resolved.Add(Path.GetFullPath(file)); + } + } + else if (File.Exists(path)) + { + if (path.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) + { + resolved.Add(Path.GetFullPath(path)); + } + else + { + Console.Error.WriteLine($"Warning: skipping non-.nupkg file: {path}"); + } + } + else + { + Console.Error.WriteLine($"Warning: path not found: {path}"); + } + } + + return resolved.ToList(); + } +} diff --git a/tools/PackageValidator/src/SymbolResolver.cs b/tools/PackageValidator/src/SymbolResolver.cs new file mode 100644 index 0000000000..aa93a09d06 --- /dev/null +++ b/tools/PackageValidator/src/SymbolResolver.cs @@ -0,0 +1,270 @@ +// 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.IO.Compression; + +namespace PackageValidator; + +/// +/// Cross-checks a package's sibling .snupkg symbol package against the assemblies in the +/// main package, matching PDBs by debug GUID, verifying them by checksum where possible, and +/// detecting orphaned or mismatched symbol files. +/// +internal static class SymbolResolver +{ + /// + /// Evaluates symbol coverage for the discovered binaries. Embedded symbols are always assessed; + /// the sibling .snupkg symbol package is additionally cross-checked unless disabled. + /// + /// The path to the inspected .nupkg. + /// The binaries discovered in the package (modified in place). + /// Whether to look for and process a separate symbol package. + /// A describing the symbol state. + public static SymbolPackageInfo Resolve( + string packagePath, List binaries, bool processSnupkg) + { + // Symbols apply only to managed assemblies. Embedded-symbol state was already captured during + // inspection and is always honored, independent of the --no-snupkg switch. + List managed = binaries.Where(b => b.IsManagedAssembly).ToList(); + + string status; + string? file = null; + var matchedPdbs = new HashSet(StringComparer.OrdinalIgnoreCase); + var allPdbPaths = new List(); + + if (!processSnupkg) + { + // The separate symbol package is intentionally not consulted; embedded symbols below are + // still evaluated. + status = "skipped"; + } + else + { + // The symbol package is, by NuGet convention, named identically but with a .snupkg + // extension sitting right beside the .nupkg. + string snupkgPath = Path.ChangeExtension(packagePath, ".snupkg"); + if (!File.Exists(snupkgPath)) + { + status = "missing"; + } + else + { + status = "present"; + file = Path.GetFileName(snupkgPath); + MatchSymbolPackage(snupkgPath, managed, matchedPdbs, allPdbPaths); + } + } + + // Combine the two symbol sources per assembly. + foreach (BinaryReport asm in managed) + { + bool embedded = asm.HasEmbeddedSymbols == true; + bool packageMatches = asm.SymbolPackageSymbolsMatch == true; + asm.HasSymbols = embedded || packageMatches; + } + + // Coverage and match quality are assessed only over implementation assemblies; reference + // assemblies and satellite resource assemblies legitimately ship without symbols. + List implementation = managed.Where(b => b.Kind == BinaryKind.Implementation).ToList(); + bool? allHaveSymbols = implementation.Count == 0 + ? null + : implementation.All(a => a.HasSymbols == true); + + bool? allSymbolsMatch = null; + List? orphans = null; + if (status == "present") + { + List withPackageSymbols = + managed.Where(a => a.HasSymbolPackageSymbols == true).ToList(); + allSymbolsMatch = withPackageSymbols.Count == 0 + ? null + : withPackageSymbols.All(a => a.SymbolPackageSymbolsMatch == true); + + List unmatched = allPdbPaths + .Where(p => !matchedPdbs.Contains(p)) + .OrderBy(p => p, StringComparer.Ordinal) + .ToList(); + orphans = unmatched.Count == 0 ? null : unmatched; + } + + return new SymbolPackageInfo + { + Status = status, + File = file, + AllImplementationAssembliesHaveSymbols = allHaveSymbols, + AllSymbolsMatch = allSymbolsMatch, + OrphanSymbolFiles = orphans, + }; + } + + /// + /// Indexes the PDBs in a symbol package and resolves each managed assembly against them. + /// + /// The path to the sibling symbol package. + /// The managed assemblies to resolve (modified in place). + /// Receives the set of PDB paths that were matched to an assembly. + /// Receives every PDB path found in the symbol package. + private static void MatchSymbolPackage( + string snupkgPath, + List managed, + HashSet matchedPdbs, + List allPdbPaths) + { + using ZipArchive archive = ZipFile.OpenRead(snupkgPath); + + // Index every PDB by its GUID (authoritative identity match) and by its path with the + // extension stripped (fallback to detect a present-but-mismatched symbol file). Each PDB is + // read only transiently to compute its GUID; the bytes are not retained, so only the small + // subset that matches an assembly by GUID is re-read on demand for checksum verification. + var pdbByGuid = new Dictionary(); + var pdbByPathKey = new Dictionary(StringComparer.OrdinalIgnoreCase); + var pdbGuidByPath = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (!entry.FullName.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + allPdbPaths.Add(entry.FullName); + pdbByPathKey[StripExtension(entry.FullName)] = entry.FullName; + + Guid? guid = PortablePdb.TryReadGuid(ReadEntry(entry)); + if (guid is Guid value) + { + pdbGuidByPath[entry.FullName] = value; + if (!pdbByGuid.ContainsKey(value)) + { + pdbByGuid[value] = entry.FullName; + } + } + } + + foreach (BinaryReport asm in managed) + { + if (asm.CodeViewGuid is Guid guid && pdbByGuid.TryGetValue(guid, out string? byGuid)) + { + // A symbol-package PDB shares this assembly's debug GUID. + asm.HasSymbolPackageSymbols = true; + asm.SymbolPackageSymbolsMatch = true; + asm.SymbolPackageFile = byGuid; + asm.SymbolPackageVerifiedByChecksum = VerifyMatchedChecksum(archive, byGuid, asm); + matchedPdbs.Add(byGuid); + } + else if (pdbByPathKey.TryGetValue(StripExtension(asm.Path), out string? byPath)) + { + // A PDB sits where this assembly's symbols should be, but its GUID does not match, + // meaning the symbols belong to a different build. + asm.HasSymbolPackageSymbols = true; + asm.SymbolPackageSymbolsMatch = false; + asm.SymbolPackageFile = byPath; + matchedPdbs.Add(byPath); + } + else + { + asm.HasSymbolPackageSymbols = false; + } + } + + // A package may legitimately ship the same assembly (identical debug GUID) at more than one + // path — e.g. .NET Framework SqlClient appears in both lib/net462 and + // runtimes/win/lib/net462, and the symbol package mirrors it with an identical PDB in each + // spot. Only the first PDB per GUID is indexed above, so every co-located assembly resolves + // to that single PDB and the identical duplicate is never consumed, which would misreport it + // as an orphan. Re-associate any such unmatched duplicate whose co-located assembly is + // present in the package and shares the PDB's debug GUID. + var guidByAssemblyPathKey = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (BinaryReport asm in managed) + { + if (asm.CodeViewGuid is Guid guid) + { + guidByAssemblyPathKey[StripExtension(asm.Path)] = guid; + } + } + + foreach (string pdbPath in allPdbPaths) + { + if (matchedPdbs.Contains(pdbPath)) + { + continue; + } + + if (pdbGuidByPath.TryGetValue(pdbPath, out Guid pdbGuid) + && guidByAssemblyPathKey.TryGetValue(StripExtension(pdbPath), out Guid asmGuid) + && pdbGuid == asmGuid) + { + matchedPdbs.Add(pdbPath); + } + } + } + + /// + /// Re-reads a GUID-matched PDB from the symbol package on demand and verifies it against the + /// assembly's recorded PDB checksums. Loading the bytes here (rather than retaining every PDB + /// during indexing) keeps peak memory to a single PDB. + /// + /// The open symbol-package archive. + /// The full archive path of the matched PDB. + /// The assembly whose checksums drive verification. + /// The checksum verification result, or if the entry cannot be read. + private static bool? VerifyMatchedChecksum(ZipArchive archive, string pdbFullName, BinaryReport asm) + { + ZipArchiveEntry? entry = archive.GetEntry(pdbFullName); + return entry is null ? null : VerifyChecksum(asm, ReadEntry(entry)); + } + + /// + /// Verifies a matched PDB against the assembly's recorded PDB checksums. + /// + /// The assembly whose checksums drive verification. + /// The matched PDB bytes. + /// + /// if any recorded checksum matches, if checksums + /// were available but none matched, and if no checksum could be evaluated. + /// + private static bool? VerifyChecksum(BinaryReport asm, byte[] pdb) + { + if (asm.Checksums is not { Count: > 0 } checksums) + { + return null; + } + + bool anyEvaluated = false; + foreach (PdbChecksum checksum in checksums) + { + bool? result = PortablePdb.TryVerifyChecksum(pdb, checksum); + if (result is true) + { + return true; + } + if (result is false) + { + anyEvaluated = true; + } + } + + // If at least one checksum was evaluated and none matched, the PDB does not correspond to the + // assembly's build despite the GUID; otherwise verification was inconclusive. + return anyEvaluated ? false : null; + } + + /// Reads all bytes of an archive entry into a buffer. + private static byte[] ReadEntry(ZipArchiveEntry entry) + { + using var buffer = new MemoryStream(); + using (Stream stream = entry.Open()) + { + stream.CopyTo(buffer); + } + return buffer.ToArray(); + } + + /// + /// Removes the final file extension from an archive path, yielding a key that aligns a DLL with + /// its co-located PDB. + /// + private static string StripExtension(string path) => + path[..^Path.GetExtension(path).Length]; +} diff --git a/tools/PackageValidator/src/Validator.cs b/tools/PackageValidator/src/Validator.cs new file mode 100644 index 0000000000..6dd738fd42 --- /dev/null +++ b/tools/PackageValidator/src/Validator.cs @@ -0,0 +1,350 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace PackageValidator; + +/// +/// Stable finding category keys. These are the values accepted by --fail-on. +/// +internal static class Categories +{ + public const string VersionInconsistency = "version-inconsistency"; + public const string MissingSymbols = "missing-symbols"; + public const string SymbolMismatch = "symbol-mismatch"; + public const string SymbolChecksumMismatch = "symbol-checksum-mismatch"; + public const string SymbolOrphan = "symbol-orphan"; + public const string SymbolDuplicate = "symbol-duplicate"; + public const string DelaySigned = "delay-signed"; + public const string Unsigned = "unsigned"; + public const string PackageUnsigned = "package-unsigned"; + public const string DependencyInconsistency = "dependency-inconsistency"; + public const string UnexpectedPackageVersion = "unexpected-package-version"; + public const string UnexpectedFileVersion = "unexpected-file-version"; + public const string UnexpectedAssemblyVersion = "unexpected-assembly-version"; + + /// Gets every known category key. + public static IReadOnlyList All { get; } = + [ + VersionInconsistency, MissingSymbols, SymbolMismatch, SymbolChecksumMismatch, + SymbolOrphan, SymbolDuplicate, DelaySigned, Unsigned, PackageUnsigned, + DependencyInconsistency, UnexpectedPackageVersion, UnexpectedFileVersion, + UnexpectedAssemblyVersion, + ]; +} + +/// +/// Applies the intrinsic validation rules to inspected packages, producing per-package and +/// cross-package instances. +/// +internal static class Validator +{ + /// + /// Validates a single package, attaching its findings to . + /// + /// The inspected package report (modified in place). + /// + /// Optional caller-supplied expected version values to confirm against, or + /// to skip expected-version checks. + /// + public static void Validate(PackageReport report, VersionExpectations? expectations = null) + { + var findings = new List(); + + CheckVersionConsistency(report, findings); + CheckExpectedVersions(report, expectations, findings); + CheckSymbols(report, findings); + CheckSigning(report, findings); + CheckPackageSignature(report, findings); + + report.Findings = findings.Count == 0 ? null : findings; + } + + /// + /// Confirms a package's version, and its assemblies' file and assembly versions, against the + /// caller-supplied expected values. Pointing every package at the same expected value provides + /// inter-package version-match validation and also catches an all-wrong-but-consistent build. + /// + private static void CheckExpectedVersions( + PackageReport report, VersionExpectations? expectations, List findings) + { + if (expectations is null || expectations.IsEmpty) + { + return; + } + + string? expectedPackage = expectations.PackageVersionFor(report.PackageId); + if (expectedPackage is not null + && !string.Equals(report.PackageVersion, expectedPackage, StringComparison.Ordinal)) + { + findings.Add(new Finding + { + Severity = Severity.Error, + Category = Categories.UnexpectedPackageVersion, + Target = report.PackageFile, + Message = + $"package version is '{report.PackageVersion ?? "(none)"}', expected '{expectedPackage}'.", + }); + } + + string? expectedFile = expectations.FileVersionFor(report.PackageId); + string? expectedAssembly = expectations.AssemblyVersionFor(report.PackageId); + + // File and assembly versions are expected to be uniform across every managed assembly the + // package ships, so confirm each one. A missing version is also a mismatch when an + // expectation is supplied, so the assertion does not silently pass. + foreach (BinaryReport asm in report.Binaries.Where(b => b.IsManagedAssembly)) + { + if (expectedFile is not null + && !string.Equals(asm.FileVersion, expectedFile, StringComparison.Ordinal)) + { + findings.Add(new Finding + { + Severity = Severity.Error, + Category = Categories.UnexpectedFileVersion, + Target = asm.Path, + Message = $"file version is '{asm.FileVersion ?? "(none)"}', expected '{expectedFile}'.", + }); + } + + if (expectedAssembly is not null + && !string.Equals(asm.AssemblyVersion, expectedAssembly, StringComparison.Ordinal)) + { + findings.Add(new Finding + { + Severity = Severity.Error, + Category = Categories.UnexpectedAssemblyVersion, + Target = asm.Path, + Message = $"assembly version is '{asm.AssemblyVersion ?? "(none)"}', expected '{expectedAssembly}'.", + }); + } + } + } + + /// + /// Validates relationships that span multiple packages in a single run. + /// + /// All package reports in the run. + /// The cross-package findings, or an empty list when there are none. + public static List ValidateBatch(IReadOnlyList reports) + { + var findings = new List(); + + // Index packages by id so dependencies on in-batch packages can be checked for version + // consistency. + var byId = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (PackageReport report in reports) + { + if (report.PackageId is { Length: > 0 } id) + { + byId[id] = report; + } + } + + foreach (PackageReport report in reports) + { + if (report.DependencyGroups is null) + { + continue; + } + + foreach (DependencyGroup group in report.DependencyGroups) + { + foreach (DependencyInfo dependency in group.Dependencies) + { + if (!byId.TryGetValue(dependency.Id, out PackageReport? target) + || target.PackageVersion is not { Length: > 0 } targetVersion) + { + continue; + } + + if (VersionRange.Satisfies(dependency.VersionRange, targetVersion) == false) + { + findings.Add(new Finding + { + Severity = Severity.Warning, + Category = Categories.DependencyInconsistency, + Target = report.PackageFile, + Message = + $"depends on {dependency.Id} {dependency.VersionRange}, but the {dependency.Id} " + + $"package in this run is version {targetVersion}, which is outside that range.", + }); + } + } + } + } + + return findings; + } + + /// + /// Flags assemblies of the same name that carry inconsistent assembly or file versions. + /// + private static void CheckVersionConsistency(PackageReport report, List findings) + { + // Group by assembly name, excluding satellite resource assemblies (which mirror their + // parent and need no independent check). + IEnumerable> groups = report.Binaries + .Where(b => b.IsManagedAssembly && b.Kind != BinaryKind.Satellite && b.AssemblyName is not null) + .GroupBy(b => b.AssemblyName!); + + foreach (IGrouping group in groups) + { + List assemblyVersions = group + .Select(b => b.AssemblyVersion) + .Where(v => v is not null) + .Distinct(StringComparer.Ordinal) + .ToList()!; + if (assemblyVersions.Count > 1) + { + findings.Add(new Finding + { + Severity = Severity.Error, + Category = Categories.VersionInconsistency, + Target = $"{report.PackageFile}:{group.Key}", + Message = + $"assembly '{group.Key}' ships with multiple assembly versions: " + + $"{string.Join(", ", assemblyVersions)}.", + }); + } + + List fileVersions = group + .Select(b => b.FileVersion) + .Where(v => v is not null) + .Distinct(StringComparer.Ordinal) + .ToList()!; + if (fileVersions.Count > 1) + { + findings.Add(new Finding + { + Severity = Severity.Warning, + Category = Categories.VersionInconsistency, + Target = $"{report.PackageFile}:{group.Key}", + Message = + $"assembly '{group.Key}' ships with multiple file versions: " + + $"{string.Join(", ", fileVersions)}.", + }); + } + } + } + + /// + /// Flags missing, mismatched, checksum-failed, duplicated, and orphaned symbols. + /// + private static void CheckSymbols(PackageReport report, List findings) + { + foreach (BinaryReport asm in report.Binaries.Where(b => b.IsManagedAssembly)) + { + // Only implementation assemblies are expected to ship symbols. + if (asm.Kind == BinaryKind.Implementation && asm.HasSymbols != true) + { + findings.Add(new Finding + { + Severity = Severity.Warning, + Category = Categories.MissingSymbols, + Target = asm.Path, + Message = "implementation assembly has no embedded or symbol-package symbols.", + }); + } + + if (asm.HasSymbolPackageSymbols == true && asm.SymbolPackageSymbolsMatch == false) + { + findings.Add(new Finding + { + Severity = Severity.Error, + Category = Categories.SymbolMismatch, + Target = asm.Path, + Message = + $"symbol-package PDB '{asm.SymbolPackageFile}' does not match the assembly build (GUID differs).", + }); + } + + if (asm.SymbolPackageVerifiedByChecksum == false) + { + findings.Add(new Finding + { + Severity = Severity.Error, + Category = Categories.SymbolChecksumMismatch, + Target = asm.Path, + Message = + $"symbol-package PDB '{asm.SymbolPackageFile}' matched by GUID but failed checksum verification.", + }); + } + + if (asm.HasEmbeddedSymbols == true && asm.HasSymbolPackageSymbols == true) + { + findings.Add(new Finding + { + Severity = Severity.Info, + Category = Categories.SymbolDuplicate, + Target = asm.Path, + Message = "symbols are present both embedded and in the symbol package.", + }); + } + } + + if (report.SymbolPackage.OrphanSymbolFiles is { Count: > 0 } orphans) + { + foreach (string orphan in orphans) + { + findings.Add(new Finding + { + Severity = Severity.Warning, + Category = Categories.SymbolOrphan, + Target = orphan, + Message = "symbol-package PDB does not correspond to any assembly in the package.", + }); + } + } + } + + /// + /// Flags delay-signed and unsigned implementation assemblies. + /// + private static void CheckSigning(PackageReport report, List findings) + { + foreach (BinaryReport asm in report.Binaries.Where( + b => b.IsManagedAssembly && b.Kind == BinaryKind.Implementation)) + { + switch (asm.SigningStatus) + { + case PackageValidator.SigningStatus.DelaySigned: + findings.Add(new Finding + { + Severity = Severity.Warning, + Category = Categories.DelaySigned, + Target = asm.Path, + Message = "assembly carries a public key but is delay-signed (strong-name flag not set).", + }); + break; + + case PackageValidator.SigningStatus.Unsigned: + findings.Add(new Finding + { + Severity = Severity.Info, + Category = Categories.Unsigned, + Target = asm.Path, + Message = "assembly is not strong-name signed.", + }); + break; + } + } + } + + /// + /// Flags a package that carries no NuGet author/repository signature. + /// + private static void CheckPackageSignature(PackageReport report, List findings) + { + if (!report.IsSigned) + { + findings.Add(new Finding + { + Severity = Severity.Info, + Category = Categories.PackageUnsigned, + Target = report.PackageFile, + Message = "package is not signed (no .signature.p7s entry).", + }); + } + } +} diff --git a/tools/PackageValidator/src/VersionExpectations.cs b/tools/PackageValidator/src/VersionExpectations.cs new file mode 100644 index 0000000000..6af7f39054 --- /dev/null +++ b/tools/PackageValidator/src/VersionExpectations.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. + +namespace PackageValidator; + +/// +/// Caller-supplied expected version values, keyed by package id, used to confirm that inspected +/// packages and their assemblies carry the versions the build was supposed to produce. This enables +/// inter-package version-match validation: pointing every package at the same expected value proves +/// they agree, and also catches the case where every package is consistently wrong. +/// +internal sealed class VersionExpectations +{ + /// The wildcard id that matches any package when no specific id is supplied. + public const string Wildcard = "*"; + + private readonly Dictionary _package = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _file = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _assembly = new(StringComparer.OrdinalIgnoreCase); + + /// Gets a value indicating whether any expectation was supplied. + public bool IsEmpty => _package.Count == 0 && _file.Count == 0 && _assembly.Count == 0; + + /// + /// Parses the raw --expect-* option values into a . + /// + /// The --expect-package-version specs. + /// The --expect-file-version specs. + /// The --expect-assembly-version specs. + /// The parsed expectations. + /// Thrown when a spec has an empty value. + public static VersionExpectations Parse( + string[] packageVersion, string[] fileVersion, string[] assemblyVersion) + { + var expectations = new VersionExpectations(); + Fill(expectations._package, packageVersion, "--expect-package-version"); + Fill(expectations._file, fileVersion, "--expect-file-version"); + Fill(expectations._assembly, assemblyVersion, "--expect-assembly-version"); + return expectations; + } + + /// Gets the expected package version for an id, or if none applies. + public string? PackageVersionFor(string? id) => Resolve(_package, id); + + /// Gets the expected file version for an id, or if none applies. + public string? FileVersionFor(string? id) => Resolve(_file, id); + + /// Gets the expected assembly version for an id, or if none applies. + public string? AssemblyVersionFor(string? id) => Resolve(_assembly, id); + + /// + /// Parses each [id=]value spec into a map entry, defaulting a missing id to the wildcard. + /// + private static void Fill(Dictionary map, string[] specs, string optionName) + { + foreach (string raw in specs) + { + string spec = raw.Trim(); + if (spec.Length == 0) + { + continue; + } + + // Package ids never contain '=' and version values never do either, so the first '=' + // unambiguously separates an optional id from the value. + int eq = spec.IndexOf('='); + string id = eq < 0 ? Wildcard : spec[..eq].Trim(); + string value = eq < 0 ? spec : spec[(eq + 1)..].Trim(); + + if (value.Length == 0) + { + throw new FormatException($"{optionName}: missing value in '{raw}'."); + } + + map[id.Length == 0 ? Wildcard : id] = value; + } + } + + /// + /// Resolves an expectation for a package id, preferring a specific id over the wildcard. + /// + private static string? Resolve(Dictionary map, string? id) + { + if (id is not null && map.TryGetValue(id, out string? specific)) + { + return specific; + } + + return map.TryGetValue(Wildcard, out string? wildcard) ? wildcard : null; + } +} diff --git a/tools/PackageValidator/src/VersionRange.cs b/tools/PackageValidator/src/VersionRange.cs new file mode 100644 index 0000000000..7bd619c828 --- /dev/null +++ b/tools/PackageValidator/src/VersionRange.cs @@ -0,0 +1,299 @@ +// 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.Globalization; + +namespace PackageValidator; + +/// +/// A minimal evaluator for NuGet dependency version ranges, sufficient to check whether a concrete +/// package version falls inside a declared range. Comparison follows SemVer 2.0 precedence rules, +/// including prerelease ordering; build metadata is ignored (it does not affect precedence). +/// +internal static class VersionRange +{ + /// + /// Determines whether a concrete version satisfies a NuGet version range. + /// + /// The declared version range (for example "1.2.3" or "[1.0.0,2.0.0)"). + /// The concrete version to test. + /// + /// or when the relationship can be evaluated, and + /// when the range or version cannot be parsed (caller should not treat + /// this as a failure). + /// + public static bool? Satisfies(string? range, string version) + { + if (string.IsNullOrWhiteSpace(range)) + { + return null; + } + + SemanticVersion? target = Parse(version); + if (target is null) + { + return null; + } + + range = range.Trim(); + + // A bare version (no brackets) is a minimum-inclusive bound in NuGet semantics. + if (range[0] != '[' && range[0] != '(') + { + SemanticVersion? min = Parse(range); + return min is null ? null : Compare(target, min) >= 0; + } + + // A bracketed range must have a matching closing delimiter; otherwise it is unparseable. + char last = range[^1]; + if (range.Length < 2 || (last != ']' && last != ')')) + { + return null; + } + + bool minInclusive = range[0] == '['; + bool maxInclusive = last == ']'; + string inner = range[1..^1]; + + int comma = inner.IndexOf(','); + if (comma < 0) + { + // Only "[1.0.0]" (both delimiters inclusive) is a valid exact pin; any other bracket + // combination such as "(1.0.0)" or "(1.0.0]" is malformed and unparseable. + if (!minInclusive || !maxInclusive) + { + return null; + } + + SemanticVersion? exact = Parse(inner); + return exact is null ? null : Compare(target, exact) == 0; + } + + string lower = inner[..comma].Trim(); + string upper = inner[(comma + 1)..].Trim(); + + if (lower.Length > 0) + { + SemanticVersion? min = Parse(lower); + if (min is null) + { + return null; + } + int cmp = Compare(target, min); + if (cmp < 0 || (cmp == 0 && !minInclusive)) + { + return false; + } + } + + if (upper.Length > 0) + { + SemanticVersion? max = Parse(upper); + if (max is null) + { + return null; + } + int cmp = Compare(target, max); + if (cmp > 0 || (cmp == 0 && !maxInclusive)) + { + return false; + } + } + + return true; + } + + /// + /// Parses a version string into its numeric release components and optional prerelease + /// identifiers, discarding any build-metadata suffix. + /// + /// The version string. + /// The parsed version, or if the release portion is unparseable. + private static SemanticVersion? Parse(string version) + { + string text = version.Trim(); + + // Build metadata ("+...") does not affect precedence; drop it first. + int plus = text.IndexOf('+'); + if (plus >= 0) + { + text = text[..plus]; + } + + // A prerelease ("-...") follows the release; split on the first hyphen. + string release; + string[]? prerelease = null; + int hyphen = text.IndexOf('-'); + if (hyphen >= 0) + { + release = text[..hyphen]; + string pre = text[(hyphen + 1)..]; + + // A hyphen introduces a prerelease, so a trailing hyphen (empty prerelease) is malformed + // per SemVer/NuGet rather than "no prerelease"; reject it. + if (pre.Length == 0) + { + return null; + } + + prerelease = pre.Split('.'); + + // Every prerelease identifier must be non-empty (e.g. "alpha..1" is malformed). + foreach (string identifier in prerelease) + { + if (identifier.Length == 0) + { + return null; + } + } + } + else + { + release = text; + } + + // Keep empty components (do not use RemoveEmptyEntries) so malformed inputs such as "1..0" + // are rejected below by the numeric parse rather than silently collapsing to "1.0". + string[] parts = release.Split('.'); + if (parts.Length == 0) + { + return null; + } + + var numbers = new int[parts.Length]; + for (int i = 0; i < parts.Length; i++) + { + if (!int.TryParse(parts[i], NumberStyles.None, CultureInfo.InvariantCulture, out numbers[i])) + { + return null; + } + } + + return new SemanticVersion(numbers, prerelease); + } + + /// + /// Compares two versions using SemVer 2.0 precedence: release components first (missing trailing + /// components treated as zero), then prerelease precedence (a prerelease version is lower than + /// the same release without one). + /// + /// The first version. + /// The second version. + /// A negative, zero, or positive value per standard comparison semantics. + private static int Compare(SemanticVersion a, SemanticVersion b) + { + int length = Math.Max(a.Release.Length, b.Release.Length); + for (int i = 0; i < length; i++) + { + int left = i < a.Release.Length ? a.Release[i] : 0; + int right = i < b.Release.Length ? b.Release[i] : 0; + int cmp = left.CompareTo(right); + if (cmp != 0) + { + return cmp; + } + } + + return ComparePrerelease(a.Prerelease, b.Prerelease); + } + + /// + /// Compares two prerelease identifier sets per SemVer 2.0 precedence rules. + /// + /// The first version's prerelease identifiers, or if none. + /// The second version's prerelease identifiers, or if none. + /// A negative, zero, or positive value per standard comparison semantics. + private static int ComparePrerelease(string[]? a, string[]? b) + { + // A version without a prerelease has higher precedence than one with a prerelease. + if (a is null && b is null) + { + return 0; + } + if (a is null) + { + return 1; + } + if (b is null) + { + return -1; + } + + int shared = Math.Min(a.Length, b.Length); + for (int i = 0; i < shared; i++) + { + int cmp = CompareIdentifier(a[i], b[i]); + if (cmp != 0) + { + return cmp; + } + } + + // When all shared identifiers are equal, the larger set has higher precedence. + return a.Length.CompareTo(b.Length); + } + + /// + /// Compares two prerelease identifiers: numeric identifiers compare numerically and rank lower + /// than alphanumeric identifiers, which compare by ASCII order. + /// + private static int CompareIdentifier(string a, string b) + { + bool aNumeric = IsNumeric(a); + bool bNumeric = IsNumeric(b); + + if (aNumeric && bNumeric) + { + if (long.TryParse(a, NumberStyles.None, CultureInfo.InvariantCulture, out long na) + && long.TryParse(b, NumberStyles.None, CultureInfo.InvariantCulture, out long nb)) + { + return na.CompareTo(nb); + } + + // Fall back for identifiers too large for long: compare by significant length, then text. + string ta = a.TrimStart('0'); + string tb = b.TrimStart('0'); + int byLength = ta.Length.CompareTo(tb.Length); + return byLength != 0 ? byLength : string.CompareOrdinal(ta, tb); + } + + // Numeric identifiers always have lower precedence than alphanumeric ones. + if (aNumeric) + { + return -1; + } + if (bNumeric) + { + return 1; + } + + return string.CompareOrdinal(a, b); + } + + /// Determines whether an identifier consists solely of ASCII digits. + private static bool IsNumeric(string value) + { + if (value.Length == 0) + { + return false; + } + + foreach (char c in value) + { + if (!char.IsAsciiDigit(c)) + { + return false; + } + } + + return true; + } + + /// + /// A parsed semantic version: numeric release components plus optional prerelease identifiers. + /// + /// The numeric release components (for example 1.2.3). + /// The dot-separated prerelease identifiers, or for a release version. + private sealed record SemanticVersion(int[] Release, string[]? Prerelease); +} diff --git a/tools/PackageValidator/test/AssemblyInspectorTests.cs b/tools/PackageValidator/test/AssemblyInspectorTests.cs new file mode 100644 index 0000000000..45ff53b5a3 --- /dev/null +++ b/tools/PackageValidator/test/AssemblyInspectorTests.cs @@ -0,0 +1,113 @@ +// 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.Security.Cryptography; +using Xunit; + +namespace PackageValidator.Tests; + +/// +/// Tests for , focused on the public-key-token derivation that turns +/// a strong-name public key into its 8-byte token. +/// +public class AssemblyInspectorTests +{ + /// + /// Verifies that an assembly with no public key (empty or null blob) yields no token, since such + /// an assembly is not strong-name signed. + /// + [Fact] + public void ComputePublicKeyToken_returns_null_for_empty_key() + { + Assert.Null(AssemblyInspector.ComputePublicKeyToken([])); + Assert.Null(AssemblyInspector.ComputePublicKeyToken(null!)); + } + + /// + /// Verifies the token derivation contract: the low 8 bytes of SHA-1(publicKey), emitted in + /// reverse (little-endian) order as lowercase hex. + /// + [Fact] + public void ComputePublicKeyToken_is_reversed_sha1_tail() + { + // Arrange: an arbitrary public-key blob plus the token computed independently from the + // documented contract (reversed last 8 bytes of its SHA-1). + byte[] key = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + byte[] hash = SHA1.HashData(key); + var expected = new byte[8]; + for (int i = 0; i < 8; i++) + { + expected[i] = hash[hash.Length - 1 - i]; + } + string expectedHex = Convert.ToHexStringLower(expected); + + // Act + Assert: the inspector must produce that same hex token. + Assert.Equal(expectedHex, AssemblyInspector.ComputePublicKeyToken(key)); + } +} + +/// +/// Tests for , covering portable-PDB GUID reading and the detection of +/// legacy Windows (MSF) PDBs that cannot be matched by GUID. +/// +public class PortablePdbTests +{ + /// + /// Verifies that the legacy Windows PDB magic ("Microsoft C/C++ MSF 7.00") is recognized so such + /// PDBs can be reported rather than silently treated as portable. + /// + [Fact] + public void IsWindowsPdb_detects_msf_signature() + { + // Arrange: bytes that begin with the Windows PDB MSF signature. + byte[] windows = System.Text.Encoding.ASCII.GetBytes("Microsoft C/C++ MSF 7.00\r\n"); + + // Act + Assert. + Assert.True(PortablePdb.IsWindowsPdb(windows)); + } + + /// + /// Verifies that non-Windows-PDB content (including the portable-PDB "BSJB" signature and empty + /// input) is not misidentified as a Windows PDB. + /// + [Fact] + public void IsWindowsPdb_rejects_other_content() + { + byte[] portableSignature = [0x42, 0x53, 0x4A, 0x42]; // "BSJB" - the portable PDB / metadata magic + Assert.False(PortablePdb.IsWindowsPdb(portableSignature)); + Assert.False(PortablePdb.IsWindowsPdb([])); + } + + /// + /// Verifies that bytes which are not a readable portable PDB yield no GUID rather than throwing. + /// + [Fact] + public void TryReadGuid_returns_null_for_non_pdb() + { + Assert.Null(PortablePdb.TryReadGuid([0, 1, 2, 3])); + } + + /// + /// Verifies that a malformed portable-PDB header encoding a negative version length is treated as + /// unparseable (checksum verification returns ) rather than throwing and + /// failing the whole run. + /// + [Fact] + public void TryVerifyChecksum_returns_null_for_malformed_version_length() + { + // A metadata header ("BSJB" + major/minor/reserved) followed by a negative version length, + // which would otherwise make the seek throw. + byte[] malformed = + [ + 0x42, 0x53, 0x4A, 0x42, // signature "BSJB" + 0x00, 0x00, // major version + 0x00, 0x00, // minor version + 0x00, 0x00, 0x00, 0x00, // reserved + 0xFF, 0xFF, 0xFF, 0xFF, // version length = -1 + ]; + var checksum = new PdbChecksum { Algorithm = "SHA256", Hash = new byte[32] }; + + Assert.Null(PortablePdb.TryVerifyChecksum(malformed, checksum)); + } +} diff --git a/tools/PackageValidator/test/BinaryClassifierTests.cs b/tools/PackageValidator/test/BinaryClassifierTests.cs new file mode 100644 index 0000000000..82836036b3 --- /dev/null +++ b/tools/PackageValidator/test/BinaryClassifierTests.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. + +using Xunit; + +namespace PackageValidator.Tests; + +/// +/// Tests for , which maps a DLL's package path to the role it plays +/// (implementation, reference, satellite, or other). +/// +public class BinaryClassifierTests +{ + /// + /// Verifies the path-to-kind mapping for the common NuGet layouts: lib/ and + /// runtimes/.../lib/ are implementation, ref/ is reference, culture + /// *.resources.dll are satellite, and anything else is other. + /// + /// The DLL path within the package. + /// The expected name. + [Theory] + [InlineData("lib/net8.0/Foo.dll", "Implementation")] + [InlineData("runtimes/win/lib/net462/Foo.dll", "Implementation")] + [InlineData("ref/net8.0/Foo.dll", "Reference")] + [InlineData("lib/net8.0/de/Foo.resources.dll", "Satellite")] + [InlineData("lib/net8.0/fr/Foo.resources.dll", "Satellite")] + [InlineData("build/Foo.dll", "Other")] + [InlineData("build/lib/Foo.dll", "Other")] // nested lib/ outside runtimes/ is not implementation + [InlineData("tools/Foo.dll", "Other")] + public void Classify_maps_paths_to_kinds(string path, string expected) + { + // Compare by name (the enum is internal, so the public test signature cannot expose it). + Assert.Equal(expected, BinaryClassifier.Classify(path).ToString()); + } + + /// + /// Verifies that a satellite resource assembly is classified as + /// even though it also lives under lib/, since the .resources.dll suffix is the + /// stronger signal. + /// + [Fact] + public void Classify_satellite_takes_precedence_over_lib() + { + // The path is under lib/ but the name ends in .resources.dll, which must win. + Assert.Equal("Satellite", BinaryClassifier.Classify("lib/net8.0/es/Foo.resources.dll").ToString()); + } +} diff --git a/tools/PackageValidator/test/PackageValidator.Test.csproj b/tools/PackageValidator/test/PackageValidator.Test.csproj new file mode 100644 index 0000000000..5d3d40056c --- /dev/null +++ b/tools/PackageValidator/test/PackageValidator.Test.csproj @@ -0,0 +1,23 @@ + + + + Exe + net10.0 + enable + enable + latest + PackageValidator.Test + false + true + true + + + + + + + + + + + diff --git a/tools/PackageValidator/test/ValidatorTests.cs b/tools/PackageValidator/test/ValidatorTests.cs new file mode 100644 index 0000000000..38b21f7639 --- /dev/null +++ b/tools/PackageValidator/test/ValidatorTests.cs @@ -0,0 +1,365 @@ +// 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.Linq; +using Xunit; + +namespace PackageValidator.Tests; + +/// +/// Tests for , the intrinsic rules engine. Each test builds a synthetic +/// so a single rule can be exercised in isolation, then asserts on the +/// findings the validator attaches. +/// +public class ValidatorTests +{ + /// + /// Creates a symbol-package descriptor in the "skipped" state, so symbol rules stay inert and a + /// test can focus on the rule under examination. + /// + /// A with status "skipped". + private static SymbolPackageInfo SkippedSymbols() => new() + { + Status = "skipped", + }; + + /// + /// Builds a minimal, otherwise-valid signed package report wrapping the given binaries, so a + /// test only has to specify the binaries relevant to the rule it exercises. + /// + /// The binaries the package contains. + /// A populated . + private static PackageReport Package(params BinaryReport[] binaries) => new() + { + PackageFile = "Test.1.0.0.nupkg", + PackageId = "Test", + PackageVersion = "1.0.0", + IsSigned = true, + Binaries = binaries.ToList(), + SymbolPackage = SkippedSymbols(), + }; + + /// + /// Verifies that two assemblies of the same name carrying different assembly versions raise an + /// Error-severity version-inconsistency finding. + /// + [Fact] + public void Flags_inconsistent_assembly_versions_as_error() + { + // Arrange: the same assembly under two TFMs with conflicting assembly versions. + PackageReport report = Package( + new BinaryReport + { + Path = "lib/net8.0/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + FileVersion = "1.0.0.0", + HasSymbols = true, + }, + new BinaryReport + { + Path = "lib/net462/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "2.0.0.0", + FileVersion = "1.0.0.0", + HasSymbols = true, + }); + + // Act. + Validator.Validate(report); + + // Assert: exactly one Error version-inconsistency finding with the expected target and message. + Finding finding = Assert.Single( + report.Findings!, f => f.Category == Categories.VersionInconsistency); + Assert.Equal(Severity.Error, finding.Severity); + Assert.Equal("Test.1.0.0.nupkg:Foo", finding.Target); + Assert.Equal( + "assembly 'Foo' ships with multiple assembly versions: 1.0.0.0, 2.0.0.0.", + finding.Message); + } + + /// + /// Verifies that same-named assemblies with matching assembly versions but differing file + /// versions raise a Warning-severity version-inconsistency finding (a weaker signal than an + /// assembly-version conflict). + /// + [Fact] + public void Flags_inconsistent_file_versions_as_warning() + { + // Arrange: identical assembly versions, but the file-version revision differs between TFMs. + PackageReport report = Package( + new BinaryReport + { + Path = "lib/net8.0/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + FileVersion = "1.0.0.0", + HasSymbols = true, + }, + new BinaryReport + { + Path = "lib/net462/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + FileVersion = "1.0.0.17603", + HasSymbols = true, + }); + + // Act. + Validator.Validate(report); + + // Assert: the mismatch surfaces as a Warning rather than an Error. + // + // A differing file version alone is only an advisory signal: without an explicit + // expectation the validator cannot know which value is authoritative, so it warns instead of + // failing. When a caller supplies an expected file version via --expect, + // CheckExpectedVersions instead treats any mismatch as an Error (the UnexpectedFileVersion + // rule), so desired outcomes can still be enforced hard. + Finding finding = Assert.Single( + report.Findings!, f => f.Category == Categories.VersionInconsistency); + Assert.Equal(Severity.Warning, finding.Severity); + Assert.Equal("Test.1.0.0.nupkg:Foo", finding.Target); + Assert.Equal( + "assembly 'Foo' ships with multiple file versions: 1.0.0.0, 1.0.0.17603.", + finding.Message); + } + + /// + /// Verifies that the missing-symbols rule applies only to implementation assemblies: a reference + /// assembly without symbols is exempt, so exactly one finding (for the implementation DLL) is + /// produced. + /// + [Fact] + public void Flags_missing_symbols_only_for_implementation_assemblies() + { + // Arrange: an implementation and a reference assembly, both lacking symbols. + PackageReport report = Package( + new BinaryReport + { + Path = "lib/net8.0/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + HasSymbols = false, + }, + new BinaryReport + { + Path = "ref/net8.0/Foo.dll", + Kind = BinaryKind.Reference, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + HasSymbols = false, + }); + + // Act. + Validator.Validate(report); + + // Assert: only the implementation assembly is flagged; the reference assembly is exempt. + Finding missing = Assert.Single( + report.Findings!, f => f.Category == Categories.MissingSymbols); + Assert.Equal(Severity.Warning, missing.Severity); + Assert.Equal("lib/net8.0/Foo.dll", missing.Target); + Assert.Equal( + "implementation assembly has no embedded or symbol-package symbols.", + missing.Message); + } + + /// + /// Verifies that a delay-signed implementation assembly (public key present but the strong-name + /// flag unset) raises a Warning-severity delay-signed finding. + /// + [Fact] + public void Flags_delay_signed_assembly() + { + // Arrange: an assembly that carries a public key but reports DelaySigned. + // + // Delay-signed binaries only appear in local developer builds and non-official CI builds, + // where the real strong-name key is unavailable so the assembly is signed with its public + // key alone. Official release builds complete strong-name signing, so this state should never + // ship; flagging it guards against accidentally publishing a not-actually-signed DLL. + PackageReport report = Package(new BinaryReport + { + Path = "lib/net8.0/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + PublicKeyToken = "23ec7fc2d6eaa4a5", + SigningStatus = SigningStatus.DelaySigned, + HasSymbols = true, + }); + + // Act. + Validator.Validate(report); + + // Assert. + Finding finding = Assert.Single( + report.Findings!, f => f.Category == Categories.DelaySigned); + Assert.Equal(Severity.Warning, finding.Severity); + Assert.Equal("lib/net8.0/Foo.dll", finding.Target); + Assert.Equal( + "assembly carries a public key but is delay-signed (strong-name flag not set).", + finding.Message); + } + + /// + /// Verifies that a symbol-package PDB which matches by GUID but fails checksum verification raises + /// an Error-severity checksum-mismatch finding. + /// + [Fact] + public void Flags_symbol_checksum_mismatch_as_error() + { + // Arrange: a GUID-matched symbol whose checksum verification was evaluated and failed. + PackageReport report = Package(new BinaryReport + { + Path = "lib/net8.0/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + HasSymbols = true, + HasSymbolPackageSymbols = true, + SymbolPackageSymbolsMatch = true, + SymbolPackageVerifiedByChecksum = false, + SymbolPackageFile = "lib/net8.0/Foo.pdb", + }); + + // Act. + Validator.Validate(report); + + // Assert. + Finding finding = Assert.Single( + report.Findings!, f => f.Category == Categories.SymbolChecksumMismatch); + Assert.Equal(Severity.Error, finding.Severity); + Assert.Equal("lib/net8.0/Foo.dll", finding.Target); + Assert.Equal( + "symbol-package PDB 'lib/net8.0/Foo.pdb' matched by GUID but failed checksum verification.", + finding.Message); + } + + /// + /// Verifies that a package without a NuGet signature raises a package-unsigned finding. + /// + [Fact] + public void Flags_unsigned_package() + { + // Arrange: a valid package, then re-create it as unsigned (IsSigned is init-only). + PackageReport report = Package(new BinaryReport + { + Path = "lib/net8.0/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + HasSymbols = true, + }); + report = new PackageReport + { + PackageFile = report.PackageFile, + PackageId = report.PackageId, + PackageVersion = report.PackageVersion, + IsSigned = false, + Binaries = report.Binaries, + SymbolPackage = report.SymbolPackage, + }; + + // Act. + Validator.Validate(report); + + // Assert. + Finding finding = Assert.Single( + report.Findings!, f => f.Category == Categories.PackageUnsigned); + Assert.Equal(Severity.Info, finding.Severity); + Assert.Equal("Test.1.0.0.nupkg", finding.Target); + Assert.Equal("package is not signed (no .signature.p7s entry).", finding.Message); + } + + /// + /// Verifies the cross-package rule: a dependency on an in-batch package whose actual version + /// falls outside the declared range raises a dependency-inconsistency finding. + /// + [Fact] + public void Batch_flags_dependency_version_inconsistency() + { + // Arrange: a consumer requiring Lib >= 2.0.0, but the Lib in the same batch is only 1.0.0. + var consumer = new PackageReport + { + PackageFile = "Consumer.1.0.0.nupkg", + PackageId = "Consumer", + PackageVersion = "1.0.0", + IsSigned = true, + Binaries = [], + SymbolPackage = SkippedSymbols(), + DependencyGroups = + [ + new DependencyGroup + { + TargetFramework = "net8.0", + Dependencies = [new DependencyInfo { Id = "Lib", VersionRange = "[2.0.0,)" }], + }, + ], + }; + var lib = new PackageReport + { + PackageFile = "Lib.1.0.0.nupkg", + PackageId = "Lib", + PackageVersion = "1.0.0", + IsSigned = true, + Binaries = [], + SymbolPackage = SkippedSymbols(), + }; + + // Act: validate the two packages together so the cross-package rule can correlate them. + List findings = Validator.ValidateBatch([consumer, lib]); + + // Assert. + Finding finding = Assert.Single( + findings, f => f.Category == Categories.DependencyInconsistency); + Assert.Equal(Severity.Warning, finding.Severity); + Assert.Equal("Consumer.1.0.0.nupkg", finding.Target); + Assert.Equal( + "depends on Lib [2.0.0,), but the Lib " + + "package in this run is version 1.0.0, which is outside that range.", + finding.Message); + } + + /// + /// Verifies that a consistent, signed package with matching versions and symbols produces no + /// Error-severity findings (the informational/clean baseline). + /// + [Fact] + public void Clean_package_produces_no_error_findings() + { + // Arrange: a signed, strong-named assembly with symbols and consistent versions. + PackageReport report = Package(new BinaryReport + { + Path = "lib/net8.0/Foo.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "Foo", + AssemblyVersion = "1.0.0.0", + FileVersion = "1.0.0.0", + PublicKeyToken = "23ec7fc2d6eaa4a5", + SigningStatus = SigningStatus.Signed, + HasSymbols = true, + }); + + // Act. + Validator.Validate(report); + + // Assert: a fully consistent, signed, strong-named package with symbols produces no findings + // at all. Validator leaves Findings null when the finding list is empty. + Assert.Null(report.Findings); + } +} diff --git a/tools/PackageValidator/test/VersionExpectationsTests.cs b/tools/PackageValidator/test/VersionExpectationsTests.cs new file mode 100644 index 0000000000..75ad781888 --- /dev/null +++ b/tools/PackageValidator/test/VersionExpectationsTests.cs @@ -0,0 +1,184 @@ +// 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 Xunit; + +namespace PackageValidator.Tests; + +/// +/// Tests for the --expect-* assertions wired through and +/// : confirming package, file, and assembly versions against caller-supplied +/// expected values, including wildcard/per-id precedence and missing-version handling. +/// +public class VersionExpectationsTests +{ + /// + /// Creates a symbol-package descriptor in the "skipped" state so symbol rules stay inert. + /// + /// A with status "skipped". + private static SymbolPackageInfo SkippedSymbols() => new() { Status = "skipped" }; + + /// + /// Builds a single-assembly package report with the given identity and versions, used to model a + /// family member under expected-version assertions. + /// + /// The package id. + /// The package version. + /// The contained assembly's file version. + /// The contained assembly's assembly version. + /// A populated . + private static PackageReport Family(string id, string pkgVer, string fileVer, string asmVer) => new() + { + PackageFile = $"{id}.{pkgVer}.nupkg", + PackageId = id, + PackageVersion = pkgVer, + IsSigned = true, + Binaries = + [ + new BinaryReport + { + Path = "lib/net8.0/X.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "X", + AssemblyVersion = asmVer, + FileVersion = fileVer, + HasSymbols = true, + }, + ], + SymbolPackage = SkippedSymbols(), + }; + + /// + /// Verifies a wildcard file-version expectation: the matching package passes while a package + /// whose file version differs is flagged with an Error-severity finding. + /// + [Fact] + public void Wildcard_file_version_flags_mismatch() + { + // Arrange: a wildcard expectation, one conforming package and one that does not. + VersionExpectations exp = VersionExpectations.Parse([], ["7.1.0.17604"], []); + PackageReport ok = Family("MDS", "7.1.0", "7.1.0.17604", "7.0.0.0"); + PackageReport bad = Family("Ext", "7.1.0", "7.1.0.0", "7.0.0.0"); + + // Act. + Validator.Validate(ok, exp); + Validator.Validate(bad, exp); + + // Assert: only the non-conforming package raises an unexpected-file-version finding. + Assert.DoesNotContain(ok.Findings ?? [], f => f.Category == Categories.UnexpectedFileVersion); + Assert.Contains(bad.Findings!, f => + f.Category == Categories.UnexpectedFileVersion && f.Severity == Severity.Error); + } + + /// + /// Verifies that a per-id expectation overrides the wildcard, so a package matching its specific + /// expected value is not flagged even though it differs from the wildcard value. + /// + [Fact] + public void Specific_id_overrides_wildcard() + { + // Arrange: the family expects .17604, but SqlServer is allowed its own value. + VersionExpectations exp = VersionExpectations.Parse( + [], ["*=7.1.0.17604", "Microsoft.SqlServer.Server=1.1.0.17604"], []); + PackageReport sqlServer = Family("Microsoft.SqlServer.Server", "1.1.0", "1.1.0.17604", "1.0.0.0"); + + // Act. + Validator.Validate(sqlServer, exp); + + // Assert: the per-id expectation is honored, so no mismatch is reported. + Assert.DoesNotContain(sqlServer.Findings ?? [], f => f.Category == Categories.UnexpectedFileVersion); + } + + /// + /// Verifies that an assembly with no AssemblyFileVersion is flagged (reported as + /// (none)) against a file-version expectation, so the assertion never silently passes when + /// the version attribute is absent. + /// + [Fact] + public void Missing_file_version_is_flagged_against_expectation() + { + // Arrange: an expectation plus an implementation assembly that carries no file version. + VersionExpectations exp = VersionExpectations.Parse([], ["7.1.0.17604"], []); + var report = new PackageReport + { + PackageFile = "MDS.7.1.0.nupkg", + PackageId = "MDS", + PackageVersion = "7.1.0", + IsSigned = true, + Binaries = + [ + new BinaryReport + { + Path = "lib/net8.0/X.dll", + Kind = BinaryKind.Implementation, + IsManagedAssembly = true, + AssemblyName = "X", + AssemblyVersion = "7.0.0.0", + FileVersion = null, + HasSymbols = true, + }, + ], + SymbolPackage = SkippedSymbols(), + }; + + // Act. + Validator.Validate(report, exp); + + // Assert: the missing version is reported as an Error mentioning "(none)". + Assert.Contains(report.Findings!, f => + f.Category == Categories.UnexpectedFileVersion + && f.Severity == Severity.Error + && f.Message.Contains("(none)")); + } + + /// + /// Verifies that a package whose version differs from the expected package version is flagged. + /// + [Fact] + public void Package_version_mismatch_is_flagged() + { + // Arrange: expect a -pr build, but the package is a -ci build. + VersionExpectations exp = VersionExpectations.Parse(["7.1.0-preview1-pr17604"], [], []); + PackageReport bad = Family("MDS", "7.1.0-preview1-ci17621", "7.1.0.17621", "7.0.0.0"); + + // Act. + Validator.Validate(bad, exp); + + // Assert. + Assert.Contains(bad.Findings!, f => f.Category == Categories.UnexpectedPackageVersion); + } + + /// + /// Verifies that when no expectations are supplied, the expected-version rules are inert: the + /// expectations are empty and no unexpected-version findings are produced. + /// + [Fact] + public void Empty_expectations_produce_no_expected_version_findings() + { + // Arrange: no expectations at all. + VersionExpectations exp = VersionExpectations.Parse([], [], []); + Assert.True(exp.IsEmpty); + + // Act: validate a package whose versions would mismatch were any expectation set. + PackageReport p = Family("MDS", "7.1.0", "7.1.0.0", "7.0.0.0"); + Validator.Validate(p, exp); + + // Assert: none of the expected-version categories appear. + Assert.DoesNotContain(p.Findings ?? [], f => + f.Category is Categories.UnexpectedFileVersion + or Categories.UnexpectedPackageVersion + or Categories.UnexpectedAssemblyVersion); + } + + /// + /// Verifies that an id= spec with an empty value is rejected at parse time rather than + /// being interpreted as a real expectation. + /// + [Fact] + public void Parse_rejects_empty_value() + { + Assert.Throws(() => VersionExpectations.Parse([], ["MDS="], [])); + } +} diff --git a/tools/PackageValidator/test/VersionRangeTests.cs b/tools/PackageValidator/test/VersionRangeTests.cs new file mode 100644 index 0000000000..a1e0816779 --- /dev/null +++ b/tools/PackageValidator/test/VersionRangeTests.cs @@ -0,0 +1,134 @@ +// 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 Xunit; + +namespace PackageValidator.Tests; + +/// +/// Tests for , the SemVer 2.0-aware evaluator that decides whether a +/// concrete version falls inside a NuGet dependency version range. +/// +public class VersionRangeTests +{ + /// + /// Verifies the bracket/interval grammar for release-only versions: bare minimum bounds, exact + /// pins, and inclusive/exclusive interval endpoints. + /// + /// The declared version range. + /// The concrete version under test. + /// Whether is expected to satisfy . + [Theory] + [InlineData("1.0.0", "1.0.0", true)] // bare version is a minimum-inclusive bound + [InlineData("1.0.0", "1.5.0", true)] + [InlineData("1.0.0", "0.9.0", false)] + [InlineData("[1.0.0]", "1.0.0", true)] // exact + [InlineData("[1.0.0]", "1.0.1", false)] + [InlineData("[1.0.0,2.0.0)", "1.5.0", true)] + [InlineData("[1.0.0,2.0.0)", "2.0.0", false)] // upper exclusive + [InlineData("[1.0.0,2.0.0]", "2.0.0", true)] // upper inclusive + [InlineData("(1.0.0,)", "1.0.0", false)] // lower exclusive + [InlineData("(1.0.0,)", "1.0.1", true)] + [InlineData("(,2.0.0]", "1.0.0", true)] + [InlineData("(,2.0.0]", "2.0.1", false)] + public void Satisfies_evaluates_ranges(string range, string version, bool expected) + { + // Each row supplies its own inputs (arrange) and expected outcome (assert); the call is the act. + Assert.Equal(expected, VersionRange.Satisfies(range, version)); + } + + /// + /// Verifies SemVer precedence between a release and its prerelease: a prerelease ranks below the + /// matching release. + /// + [Fact] + public void Satisfies_release_outranks_prerelease_of_same_version() + { + // A prerelease is lower precedence than its release, so it falls below a min-inclusive bound. + Assert.Equal(false, VersionRange.Satisfies("1.0.0", "1.0.0-alpha")); + + // A release satisfies a prerelease lower bound (release > prerelease of the same version). + Assert.Equal(true, VersionRange.Satisfies("1.0.0-alpha", "1.0.0")); + + // A higher release still satisfies even when it carries a prerelease tag, because its release + // components (1.2.3) already exceed the bound. + Assert.Equal(true, VersionRange.Satisfies("1.0.0", "1.2.3-preview.1")); + } + + /// + /// Verifies prerelease-identifier precedence: numeric identifiers compare numerically and rank + /// below alphanumeric ones, and a larger identifier set outranks a smaller prefix. + /// + /// The declared version range. + /// The concrete version under test. + /// Whether is expected to satisfy . + [Theory] + [InlineData("[1.0.0-alpha,1.0.0]", "1.0.0-beta", true)] // alpha < beta < release + [InlineData("[1.0.0-alpha.1,)", "1.0.0-alpha.2", true)] // numeric identifier ordering + [InlineData("[1.0.0-alpha.2,)", "1.0.0-alpha.1", false)] + [InlineData("[1.0.0-alpha,)", "1.0.0-alpha.1", true)] // larger identifier set ranks higher + [InlineData("[1.0.0-alpha,)", "1.0.0-1", false)] // numeric ranks below alphanumeric + public void Satisfies_orders_prerelease_identifiers(string range, string version, bool expected) + { + Assert.Equal(expected, VersionRange.Satisfies(range, version)); + } + + /// + /// Verifies that build metadata (the + suffix) is ignored, matching SemVer/NuGet, so it + /// never changes whether a version satisfies a range. + /// + [Fact] + public void Satisfies_ignores_build_metadata() + { + // "+abc123" / "+build.5" must not affect precedence, so the version still matches an exact + // pin and a minimum bound respectively. + Assert.Equal(true, VersionRange.Satisfies("[1.0.0]", "1.0.0+abc123")); + Assert.Equal(true, VersionRange.Satisfies("1.0.0", "1.0.0+build.5")); + } + + /// + /// Verifies that malformed or unparseable ranges/versions return rather + /// than throwing, so callers can safely skip them. + /// + /// The declared version range. + /// The concrete version under test. + [Theory] + [InlineData("[", "1.0.0")] // opening bracket with no closing delimiter + [InlineData("(", "1.0.0")] // opening paren with no closing delimiter + [InlineData("[1.0.0", "1.0.0")] // missing closing delimiter + [InlineData("(1.0.0", "1.0.0")] // missing closing delimiter + [InlineData("[1..0]", "1.0.0")] // empty release component in the range + [InlineData("1.0.0", "1..0")] // empty release component in the version + [InlineData("1.0.0", "1.0.0-")] // trailing hyphen: empty prerelease + [InlineData("1.0.0", "1.0.0-alpha..1")] // empty prerelease identifier + [InlineData("(1.0.0)", "1.0.0")] // exact pin must be [..]; (..) is malformed + [InlineData("(1.0.0]", "1.0.0")] // mismatched exact-pin delimiters + [InlineData("[1.0.0)", "1.0.0")] // mismatched exact-pin delimiters + public void Satisfies_returns_null_for_malformed_input(string range, string version) + { + Assert.Null(VersionRange.Satisfies(range, version)); + } + + /// + /// Verifies that an absent range is treated as "no constraint" by returning + /// (the caller must not treat this as a failure). + /// + [Fact] + public void Satisfies_returns_null_for_empty_range() + { + Assert.Null(VersionRange.Satisfies(null, "1.0.0")); + Assert.Null(VersionRange.Satisfies("", "1.0.0")); + } + + /// + /// Verifies that versions with different component counts compare correctly, with missing + /// trailing components treated as zero (for example 1.0 equals 1.0.0.0). + /// + [Fact] + public void Satisfies_treats_missing_components_as_zero() + { + Assert.Equal(true, VersionRange.Satisfies("1.0", "1.0.0.0")); + Assert.Equal(true, VersionRange.Satisfies("[1.0]", "1.0.0")); + } +} From 36f4f94e3de73849ff795fcb142f0d2d7a624334 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:12:09 -0700 Subject: [PATCH 194/298] Introduce new performance benchmark runners (#4447) --- BUILDGUIDE.md | 49 ++- .../tests/Directory.Packages.props | 1 + .../AsyncLargeDataReadRunner.cs | 119 ++++++++ .../BeginTransactionRunner.cs | 86 ++++++ .../CancellationTokenReadAsyncRunner.cs | 119 ++++++++ .../ConnectionPoolStressRunner.cs | 282 ++++++++++++++++++ .../JsonVsVarcharReadRunner.cs | 150 ++++++++++ .../BenchmarkRunners/MarsOverheadRunner.cs | 74 +++++ .../ParallelAsyncConnectionRunner.cs | 54 ++++ .../SequentialXmlReadRunner.cs | 98 ++++++ .../Config/BenchmarkConfig.cs | 58 +++- .../tests/PerformanceTests/Config/Config.cs | 15 +- ...oft.Data.SqlClient.PerformanceTests.csproj | 10 +- .../tests/PerformanceTests/Program.cs | 136 ++++++++- .../tests/PerformanceTests/runnerconfig.json | 46 --- .../tests/PerformanceTests/runnerconfig.jsonc | 125 ++++++++ 16 files changed, 1330 insertions(+), 92 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/AsyncLargeDataReadRunner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BeginTransactionRunner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/CancellationTokenReadAsyncRunner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolStressRunner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/JsonVsVarcharReadRunner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/MarsOverheadRunner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ParallelAsyncConnectionRunner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/SequentialXmlReadRunner.cs delete mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.json create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 12a91935cc..6298bbc9eb 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -451,13 +451,14 @@ $ sqlcmd -S localhost -U sa -P password 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. +The default `runnerconfig.jsonc` expects a database named `sqlclient-perf-db`, +but you may change the config to use any existing database. The benchmarks +create and drop their own tables (typically prefixed with `perf_`) in this +database; other existing tables are left untouched. ### Configure Runner -Configure the benchmarks by editing the `runnerconfig.json` file directly in the +Configure the benchmarks by editing the `runnerconfig.jsonc` file directly in the `PerformanceTests` directory with an appropriate connection string and benchmark settings: @@ -465,6 +466,9 @@ settings: { "ConnectionString": "Server=tcp:localhost; Integrated Security=true; Initial Catalog=sqlclient-perf-db;", "UseManagedSniOnWindows": false, + "UseOptimizedAsyncBehaviour": true, + "WaitForProfiler": false, + "UseNativeMemoryAndETWProfiler": false, "Benchmarks": { "SqlConnectionRunnerConfig": @@ -484,36 +488,51 @@ settings: 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 +The top-level flags control global runner behavior: + +| Flag | Description | +| --- | --- | +| `UseManagedSniOnWindows` | Enables the managed SNI implementation on Windows instead of native SNI. | +| `UseOptimizedAsyncBehaviour` | Enables packet multiplexing and other async optimizations in SqlClient. | +| `WaitForProfiler` | Pauses at startup and prints the process ID so you can attach an external profiler (e.g. `dotnet-trace`) before benchmarks run. | +| `UseNativeMemoryAndETWProfiler` | Attaches the `NativeMemoryProfiler` and `EtwProfiler` BenchmarkDotNet diagnosers. Windows only; has no effect on other OSes. | + +Some benchmarks (e.g. `DataTypeReaderRunner`, `DataTypeReaderAsyncRunner`) also +read per-type test values from `datatypes.json` in the `PerformanceTests` +directory. Like `runnerconfig.jsonc`, this file's location can be overridden +with the `DATATYPES_CONFIG` environment variable. + +After making edits to `runnerconfig.jsonc` 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 +default, the benchmarks look for `runnerconfig.jsonc` in the same directory as the DLL. Optionally, to avoid polluting your git workspace and requiring a build after -each config change, copy `runnerconfig.json` to a new file, make your edits +each config change, copy `runnerconfig.jsonc` to a new file, make your edits there, and then specify the new file with the RUNNER_CONFIG environment -variable. +variable. The same approach works for `datatypes.json` via the +`DATATYPES_CONFIG` environment variable. PowerShell: ```pwsh -> copy runnerconfig.json $HOME\.configs\runnerconfig.json +> copy runnerconfig.jsonc $HOME\.configs\runnerconfig.jsonc -# Make edits to $HOME\.configs\runnerconfig.json +# Make edits to $HOME\.configs\runnerconfig.jsonc # You must set the RUNNER_CONFIG environment variable for the current shell. -> $env:RUNNER_CONFIG="${HOME}\.configs\runnerconfig.json" +> $env:RUNNER_CONFIG="${HOME}\.configs\runnerconfig.jsonc" ``` Bash: ```bash -$ cp runnerconfig.json ~/.configs/runnerconfig.json +$ cp runnerconfig.jsonc ~/.configs/runnerconfig.jsonc -# Make edits to ~/.configs/runnerconfig.json +# Make edits to ~/.configs/runnerconfig.jsonc # Optionally export RUNNER_CONFIG. -$ export RUNNER_CONFIG=~/.configs/runnerconfig.json +$ export RUNNER_CONFIG=~/.configs/runnerconfig.jsonc ``` ### Run Benchmarks @@ -533,5 +552,5 @@ Bash: # copy prepared by the build. $ dotnet run -c Release -f net9.0 -$ RUNNER_CONFIG=~/.configs/runnerconfig.json dotnet run -c Release -f net9.0 +$ RUNNER_CONFIG=~/.configs/runnerconfig.jsonc dotnet run -c Release -f net9.0 ``` diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props index 4f3dbacb21..54596a729f 100644 --- a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props @@ -14,6 +14,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/AsyncLargeDataReadRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/AsyncLargeDataReadRunner.cs new file mode 100644 index 0000000000..851b5b8d66 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/AsyncLargeDataReadRunner.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Benchmarks for sync vs async reading of large VARBINARY(MAX) values. + /// Reproduces issues #593 and #1562. + /// + public class AsyncLargeDataReadRunner : BaseRunner + { + private string _tableName; + private string _connectionString; + + /// + /// Size of the data to read in bytes. + /// + [Params(1_048_576, 5_242_880, 10_485_760, 20_971_520)] + public int DataSizeBytes { get; set; } + + /// + /// Size of the client-side read buffer used to drain the VARBINARY(MAX) column. + /// Kept small (8 KB) and large (1 MB) to observe whether buffer size relative to + /// the payload materially changes throughput. + /// + [Params(8_192, 1_048_576)] + public int ReadBufferBytes { get; set; } + + [GlobalSetup] + public void Setup() + { + _connectionString = s_config.ConnectionString; + string machineHash = ((uint)Environment.MachineName.GetHashCode()).ToString("x8"); + _tableName = $"[perf_AsyncLargeData_{machineHash}_{Guid.NewGuid():N}]"; + + using var conn = new SqlConnection(_connectionString); + conn.Open(); + + using var createCmd = new SqlCommand( + $"CREATE TABLE {_tableName} (Id INT IDENTITY PRIMARY KEY, Data VARBINARY(MAX))", conn); + createCmd.ExecuteNonQuery(); + + // Generate the payload entirely server-side via CRYPT_GEN_RANDOM so we don't + // allocate a multi-megabyte byte[] on the client and don't ship the payload + // over the wire just to seed the benchmark. + using var insertCmd = new SqlCommand( + $@"INSERT INTO {_tableName} (Data) + SELECT SUBSTRING( + CONVERT( + varbinary(max), + REPLICATE( + CONVERT(varchar(max), CRYPT_GEN_RANDOM(8000), 2), + (@dataSizeBytes + 7999) / 8000 + ), + 2 + ), + 1, + @dataSizeBytes + );", conn); + insertCmd.Parameters.Add("@dataSizeBytes", SqlDbType.Int).Value = DataSizeBytes; + insertCmd.ExecuteNonQuery(); + } + + [GlobalCleanup] + public void Cleanup() + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {_tableName}", conn); + cmd.ExecuteNonQuery(); + SqlConnection.ClearAllPools(); + } + + [Benchmark] + public void ReadLargeDataSync() + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = new SqlCommand($"SELECT Data FROM {_tableName}", conn); + using var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess); + while (reader.Read()) + { + byte[] buffer = new byte[ReadBufferBytes]; + long offset = 0; + long bytesRead; + do + { + bytesRead = reader.GetBytes(0, offset, buffer, 0, buffer.Length); + offset += bytesRead; + } while (bytesRead > 0); + } + } + + [Benchmark] + public async Task ReadLargeDataAsync() + { + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + using var cmd = new SqlCommand($"SELECT Data FROM {_tableName}", conn); + using var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + while (await reader.ReadAsync()) + { + using var stream = reader.GetStream(0); + byte[] buffer = new byte[ReadBufferBytes]; + int bytesRead; + do + { + bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + } while (bytesRead > 0); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BeginTransactionRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BeginTransactionRunner.cs new file mode 100644 index 0000000000..3a3bded24e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BeginTransactionRunner.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. + +using System; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Benchmarks measuring BeginTransaction round-trip latency. + /// Reproduces issue #1554. + /// + public class BeginTransactionRunner : BaseRunner + { + private SqlConnection _connection; + private string _tableName; + + [GlobalSetup] + public void Setup() + { + _connection = new SqlConnection(s_config.ConnectionString); + _connection.Open(); + + string machineHash = ((uint)Environment.MachineName.GetHashCode()).ToString("x8"); + _tableName = $"[perf_TxnBench_{machineHash}_{Guid.NewGuid():N}]"; + using var cmd = new SqlCommand( + $"CREATE TABLE {_tableName} (Id INT IDENTITY PRIMARY KEY, Value INT)", _connection); + cmd.ExecuteNonQuery(); + } + + [GlobalCleanup] + public void Cleanup() + { + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {_tableName}", _connection); + cmd.ExecuteNonQuery(); + _connection.Close(); + _connection.Dispose(); + SqlConnection.ClearAllPools(); + } + + // Truncate the table between iterations so every iteration inserts into an empty + // table. Without this, the table grows across iterations (and across benchmarks, + // since they share the same connection/table), which would skew the measured + // BeginTransaction overhead as row count increases. + [IterationCleanup] + public void IterationCleanup() + { + using var cmd = new SqlCommand($"TRUNCATE TABLE {_tableName}", _connection); + cmd.ExecuteNonQuery(); + } + + [Benchmark] + public void WithTransaction() + { + using var txn = _connection.BeginTransaction(); + using var cmd = new SqlCommand($"INSERT INTO {_tableName} (Value) VALUES (1)", _connection, txn); + cmd.ExecuteNonQuery(); + txn.Commit(); + } + + [Benchmark] + public void WithoutTransaction() + { + using var cmd = new SqlCommand($"INSERT INTO {_tableName} (Value) VALUES (1)", _connection); + cmd.ExecuteNonQuery(); + } + + [Benchmark] + public async Task WithTransactionAsync() + { + using var txn = (SqlTransaction)await _connection.BeginTransactionAsync(); + using var cmd = new SqlCommand($"INSERT INTO {_tableName} (Value) VALUES (1)", _connection, txn); + await cmd.ExecuteNonQueryAsync(); + await txn.CommitAsync(); + } + + [Benchmark] + public async Task WithoutTransactionAsync() + { + using var cmd = new SqlCommand($"INSERT INTO {_tableName} (Value) VALUES (1)", _connection); + await cmd.ExecuteNonQueryAsync(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/CancellationTokenReadAsyncRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/CancellationTokenReadAsyncRunner.cs new file mode 100644 index 0000000000..aa632503fc --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/CancellationTokenReadAsyncRunner.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Benchmarks measuring CancellationToken overhead during ReadAsync iteration. + /// Reproduces issue #2408. + /// + public class CancellationTokenReadAsyncRunner : BaseRunner + { + private static long s_rowCount; + private string _tableName; + private string _connectionString; + private string _query; + private CancellationTokenSource _cts; + private CancellationToken _token; + + /// + /// Whether to pass a CancellationToken to ReadAsync. + /// + [Params(true, false)] + public bool UseCancellationToken { get; set; } + + [GlobalSetup] + public void Setup() + { + s_rowCount = s_config.Benchmarks.CancellationTokenReadAsyncRunnerConfig.RowCount; + _connectionString = s_config.ConnectionString; + string machineHash = ((uint)Environment.MachineName.GetHashCode()).ToString("x8"); + _tableName = $"[perf_CancelToken_{machineHash}_{Guid.NewGuid():N}]"; + using var conn = new SqlConnection(_connectionString); + conn.Open(); + + using var createCmd = new SqlCommand( + $"CREATE TABLE {_tableName} (Id INT IDENTITY PRIMARY KEY, Value INT)", conn); + createCmd.ExecuteNonQuery(); + + // Bulk insert rows + using var bulkCopy = new SqlBulkCopy(conn) + { + DestinationTableName = _tableName, + BatchSize = 10000 + }; + + var dt = new System.Data.DataTable(); + dt.Columns.Add("Value", typeof(int)); + for (int i = 0; i < s_rowCount; i++) + { + dt.Rows.Add(i * 2); + } + bulkCopy.ColumnMappings.Add("Value", "Value"); + bulkCopy.WriteToServer(dt); + + _query = $"SELECT Id, Value FROM {_tableName}"; + } + + [GlobalCleanup] + public void Cleanup() + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {_tableName}", conn); + cmd.ExecuteNonQuery(); + SqlConnection.ClearAllPools(); + } + + // Allocate the CancellationTokenSource in [IterationSetup] / dispose it in + // [IterationCleanup] so the CTS alloc/dispose cost is excluded from the measurement + // and only the per-row ReadAsync(CancellationToken) overhead is captured. + [IterationSetup] + public void IterationSetup() + { + if (UseCancellationToken) + { + _cts = new CancellationTokenSource(); + _token = _cts.Token; + } + else + { + _cts = null; + _token = CancellationToken.None; + } + } + + [IterationCleanup] + public void IterationCleanup() + { + _cts?.Dispose(); + _cts = null; + } + + [Benchmark] + public async Task ReadAsyncWithOrWithoutToken() + { + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + using var cmd = new SqlCommand(_query, conn); + using var reader = await cmd.ExecuteReaderAsync(); + + if (UseCancellationToken) + { + while (await reader.ReadAsync(_token)) + { } + } + else + { + while (await reader.ReadAsync()) + { } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolStressRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolStressRunner.cs new file mode 100644 index 0000000000..9dad6f41d0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolStressRunner.cs @@ -0,0 +1,282 @@ +// 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.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Stress-tests the connection pool with randomized parallel access patterns: + /// - Massive concurrent open/close churn + /// - Randomized hold durations simulating real workloads + /// - Mixed sync/async callers competing for pooled connections + /// - Connection reuse with interleaved queries + /// - Pool exhaustion and recovery under pressure + /// + /// Related issues: #601, #979, #3356 + /// + public class ConnectionPoolStressRunner : BaseRunner + { + private string _connectionString; + private string _tableName; + + /// + /// Number of concurrent tasks hammering the pool. + /// + [Params(10, 20, 25)] + public int Parallelism { get; set; } + + /// + /// Max pool size — controls how many physical connections the pool can hold. + /// When Parallelism exceeds this, tasks must wait for a free connection. + /// + [Params(50, 100)] + public int MaxPoolSize { get; set; } + + [GlobalSetup] + public void Setup() + { + _connectionString = s_config.ConnectionString + + $";Pooling=True;Max Pool Size={MaxPoolSize};Min Pool Size=5;Connect Timeout=60"; + + // Create a small table for query workloads. + // Hash the machine name instead of using it verbatim: hostnames can be long enough + // to push the identifier past SQL Server's 128-character limit. Cast to uint rather + // than using Math.Abs, which throws OverflowException when the hash is int.MinValue. + string machineHash = ((uint)Environment.MachineName.GetHashCode()).ToString("x8"); + _tableName = $"[perf_PoolStress_{machineHash}_{Guid.NewGuid():N}]"; + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = new SqlCommand( + $"CREATE TABLE {_tableName} (Id INT IDENTITY PRIMARY KEY, Val INT)", conn); + cmd.ExecuteNonQuery(); + + // Seed a few rows so SELECT queries return data + using var insert = new SqlCommand( + $"INSERT INTO {_tableName} (Val) VALUES (1),(2),(3),(4),(5)", conn); + insert.ExecuteNonQuery(); + } + + [IterationCleanup] + public void IterationCleanup() + { + SqlConnection.ClearAllPools(); + } + + [GlobalCleanup] + public void Cleanup() + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {_tableName}", conn); + cmd.ExecuteNonQuery(); + conn.Close(); + SqlConnection.ClearAllPools(); + } + + /// + /// Pure open/close churn — every task opens a pooled connection, immediately closes it, + /// and repeats. Measures raw pool checkout/return throughput under contention. + /// The per-task loop count scales with MaxPoolSize so total checkouts stay proportional + /// to pool capacity regardless of how the [Params] values change. + /// + [Benchmark] + public async Task RapidFireOpenClose() + { + int iterationsPerTask = Math.Max(20, MaxPoolSize / Math.Max(1, Parallelism) * 4); + var tasks = new Task[Parallelism]; + for (int i = 0; i < Parallelism; i++) + { + tasks[i] = Task.Run(async () => + { + for (int j = 0; j < iterationsPerTask; j++) + { + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + // immediate return to pool + } + }); + } + await Task.WhenAll(tasks); + } + + /// + /// Randomized hold — each task opens a connection, holds it for a random duration + /// (0-50ms), optionally runs a query, then returns it. Simulates realistic mixed + /// workloads where some connections are held briefly and others longer. + /// + [Benchmark] + public async Task RandomizedHoldAndQuery() + { + var tasks = new Task[Parallelism]; + for (int i = 0; i < Parallelism; i++) + { + int seed = i; + tasks[i] = Task.Run(async () => + { + var rng = new Random(seed); + for (int j = 0; j < 10; j++) + { + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + + // ~50% of the time, execute a lightweight query while holding the connection + if (rng.Next(2) == 0) + { + using var cmd = new SqlCommand($"SELECT TOP 1 Val FROM {_tableName}", conn); + _ = await cmd.ExecuteScalarAsync(); + } + + // Random hold time: 0-50ms + int holdMs = rng.Next(51); + if (holdMs > 0) + { + await Task.Delay(holdMs); + } + } + }); + } + await Task.WhenAll(tasks); + } + + /// + /// Mixed sync and async — half the tasks use sync Open/ExecuteReader, + /// the other half use async. Stresses the pool lock paths that differ + /// between sync and async checkout. + /// + [Benchmark] + public async Task MixedSyncAsyncContention() + { + var tasks = new Task[Parallelism]; + for (int i = 0; i < Parallelism; i++) + { + bool useAsync = i % 2 == 0; + tasks[i] = Task.Run(async () => + { + for (int j = 0; j < 10; j++) + { + using var conn = new SqlConnection(_connectionString); + if (useAsync) + { + await conn.OpenAsync(); + using var cmd = new SqlCommand($"SELECT COUNT(*) FROM {_tableName}", conn); + _ = await cmd.ExecuteScalarAsync(); + } + else + { + conn.Open(); + using var cmd = new SqlCommand($"SELECT COUNT(*) FROM {_tableName}", conn); + _ = cmd.ExecuteScalar(); + } + } + }); + } + await Task.WhenAll(tasks); + } + + /// + /// Connection reuse with multiple commands — each task opens one connection and + /// executes many sequential queries before returning it. Measures pool efficiency + /// when connections are held for multi-step operations (like EF SaveChanges). + /// + [Benchmark] + public async Task MultiCommandReuse() + { + var tasks = new Task[Parallelism]; + for (int i = 0; i < Parallelism; i++) + { + int seed = i; + tasks[i] = Task.Run(async () => + { + var rng = new Random(seed); + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + + // Execute a burst of 5-15 commands on the same connection + int commandCount = rng.Next(5, 16); + for (int c = 0; c < commandCount; c++) + { + using var cmd = new SqlCommand($"SELECT Val FROM {_tableName} WHERE Id = @id", conn); + cmd.Parameters.AddWithValue("@id", rng.Next(1, 6)); + using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) { } + } + }); + } + await Task.WhenAll(tasks); + } + + /// + /// Pool exhaustion and recovery — spawns more tasks than MaxPoolSize so some must + /// wait. Measures how well the pool handles back-pressure when all connections are + /// checked out and callers are queued. + /// + [Benchmark] + public async Task PoolExhaustionRecovery() + { + // Ensure we exceed pool capacity + int taskCount = Math.Max(Parallelism, MaxPoolSize * 2); + var tasks = new Task[taskCount]; + for (int i = 0; i < taskCount; i++) + { + int seed = i; + tasks[i] = Task.Run(async () => + { + var rng = new Random(seed); + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + + // Hold the connection for 10-100ms to create pool pressure + using var cmd = new SqlCommand($"SELECT TOP 1 Val FROM {_tableName}", conn); + _ = await cmd.ExecuteScalarAsync(); + + await Task.Delay(rng.Next(10, 101)); + }); + } + await Task.WhenAll(tasks); + } + + /// + /// Bursty traffic pattern — sends waves of connections with pauses between bursts, + /// simulating real web server traffic patterns where requests cluster. Each burst + /// spins up concurrent tasks so the configured parallelism + /// level actually drives the observed concurrency. + /// + [Benchmark] + public async Task BurstyTrafficPattern() + { + const int burstCount = 5; + + for (int burst = 0; burst < burstCount; burst++) + { + var tasks = new Task[Parallelism]; + for (int i = 0; i < Parallelism; i++) + { + int seed = burst * Parallelism + i; + tasks[i] = Task.Run(async () => + { + var rng = new Random(seed); + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + + // Each connection in the burst does 1-5 queries + int queryCount = rng.Next(1, 6); + for (int q = 0; q < queryCount; q++) + { + using var cmd = new SqlCommand($"SELECT Val FROM {_tableName}", conn); + using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) { } + } + }); + } + await Task.WhenAll(tasks); + + // Brief pause between bursts (simulates request clustering) + await Task.Delay(5); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/JsonVsVarcharReadRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/JsonVsVarcharReadRunner.cs new file mode 100644 index 0000000000..7b9533386b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/JsonVsVarcharReadRunner.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.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Benchmarks comparing read performance of JSON vs VARCHAR(MAX) columns. + /// Reproduces issue #3499. + /// Note: JSON column type requires SQL Server 2025+ or Azure SQL. + /// This runner is disabled by default in runnerconfig.jsonc. + /// + public class JsonVsVarcharReadRunner : BaseRunner + { + private static long s_rowCount; + private string _jsonTableName; + private string _varcharTableName; + private string _connectionString; + + /// + /// Which column type to read from: "JSON" or "VARCHAR". + /// + [Params("JSON", "VARCHAR")] + public string ColumnType { get; set; } + + private string ActiveTableName => ColumnType == "JSON" ? _jsonTableName : _varcharTableName; + + [GlobalSetup] + public void Setup() + { + s_rowCount = s_config.Benchmarks.JsonVsVarcharReadRunnerConfig.RowCount; + _connectionString = s_config.ConnectionString; + + string machineHash = ((uint)Environment.MachineName.GetHashCode()).ToString("x8"); + string suffix = $"{machineHash}_{Guid.NewGuid():N}"; + _jsonTableName = $"[perf_Json_{suffix}]"; + _varcharTableName = $"[perf_Varchar_{suffix}]"; + + string sampleJson = "{\"id\":1,\"name\":\"test\",\"values\":[1,2,3],\"nested\":{\"key\":\"value\"}}"; + + using var conn = new SqlConnection(_connectionString); + conn.Open(); + + // Create JSON table + using (var cmd = new SqlCommand( + $"CREATE TABLE {_jsonTableName} (Id INT IDENTITY PRIMARY KEY, Data JSON)", conn)) + { + cmd.ExecuteNonQuery(); + } + + // Create VARCHAR(MAX) table + using (var cmd = new SqlCommand( + $"CREATE TABLE {_varcharTableName} (Id INT IDENTITY PRIMARY KEY, Data VARCHAR(MAX))", conn)) + { + cmd.ExecuteNonQuery(); + } + + // Bulk insert identical data into both tables + var dt = new System.Data.DataTable(); + dt.Columns.Add("Data", typeof(string)); + for (int i = 0; i < s_rowCount; i++) + { + dt.Rows.Add(sampleJson); + } + + using (var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = _jsonTableName, BatchSize = 10000 }) + { + bulkCopy.ColumnMappings.Add("Data", "Data"); + bulkCopy.WriteToServer(dt); + } + + using (var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = _varcharTableName, BatchSize = 10000 }) + { + bulkCopy.ColumnMappings.Add("Data", "Data"); + bulkCopy.WriteToServer(dt); + } + } + + [GlobalCleanup] + public void Cleanup() + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + + using (var cmd = new SqlCommand($"DROP TABLE IF EXISTS {_jsonTableName}", conn)) + { + cmd.ExecuteNonQuery(); + } + + using (var cmd = new SqlCommand($"DROP TABLE IF EXISTS {_varcharTableName}", conn)) + { + cmd.ExecuteNonQuery(); + } + + SqlConnection.ClearAllPools(); + } + + [Benchmark] + public void ReadDataSync() + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = new SqlCommand($"SELECT Data FROM {ActiveTableName}", conn); + using var reader = cmd.ExecuteReader(); + if (ColumnType == "JSON") + { + // Exercise the SqlJson accessor path so the JSON case captures + // JSON-type handling overhead rather than the shared string path. + while (reader.Read()) + { + _ = reader.GetSqlJson(0).Value; + } + } + else + { + while (reader.Read()) + { + _ = reader.GetString(0); + } + } + } + + [Benchmark] + public async Task ReadDataAsync() + { + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + using var cmd = new SqlCommand($"SELECT Data FROM {ActiveTableName}", conn); + using var reader = await cmd.ExecuteReaderAsync(); + if (ColumnType == "JSON") + { + while (await reader.ReadAsync()) + { + _ = reader.GetSqlJson(0).Value; + } + } + else + { + while (await reader.ReadAsync()) + { + _ = await reader.GetFieldValueAsync(0); + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/MarsOverheadRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/MarsOverheadRunner.cs new file mode 100644 index 0000000000..db0ea313a3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/MarsOverheadRunner.cs @@ -0,0 +1,74 @@ +// 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.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Benchmarks comparing query execution with MARS enabled vs disabled. + /// Reproduces issues #422 and #1283. + /// + public class MarsOverheadRunner : BaseRunner + { + private static long s_rowCount; + private Table _table; + private string _query; + + /// + /// Whether MARS is enabled on the connection string. + /// + [Params(true, false)] + public bool MARS { get; set; } + + private string ConnectionString => s_config.ConnectionString + $";MultipleActiveResultSets={MARS}"; + + [GlobalSetup] + public void Setup() + { + s_rowCount = s_config.Benchmarks.MarsOverheadRunnerConfig.RowCount; + + using var conn = new SqlConnection(s_config.ConnectionString); + conn.Open(); + + _table = TablePatterns.TableAll25Columns(s_datatypes, nameof(MarsOverheadRunner)) + .CreateTable(conn) + .InsertBulkRows(s_rowCount, conn); + + _query = $"SELECT * FROM {_table.Name}"; + } + + [GlobalCleanup] + public void Cleanup() + { + using var conn = new SqlConnection(s_config.ConnectionString); + conn.Open(); + _table.DropTable(conn); + SqlConnection.ClearAllPools(); + } + + [Benchmark] + public void ExecuteReaderWithMars() + { + using var conn = new SqlConnection(ConnectionString); + conn.Open(); + using var cmd = new SqlCommand(_query, conn); + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { } + } + + [Benchmark] + public async Task ExecuteReaderAsyncWithMars() + { + using var conn = new SqlConnection(ConnectionString); + await conn.OpenAsync(); + using var cmd = new SqlCommand(_query, conn); + using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ParallelAsyncConnectionRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ParallelAsyncConnectionRunner.cs new file mode 100644 index 0000000000..32516bddad --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ParallelAsyncConnectionRunner.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.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Benchmarks for concurrent async connection opens to surface pool contention. + /// Reproduces issues #601 and #979. + /// + public class ParallelAsyncConnectionRunner : BaseRunner + { + /// + /// Number of concurrent connections to open. + /// + [Params(10, 50, 100)] + public int Concurrency { get; set; } + + /// + /// Whether connection pooling is enabled. + /// + [Params(true, false)] + public bool Pooling { get; set; } + + private string ConnectionString => s_config.ConnectionString + $";Pooling={Pooling};Max Pool Size=200"; + + [IterationCleanup] + public void IterationCleanup() + { + SqlConnection.ClearAllPools(); + } + + [Benchmark] + public async Task OpenConnectionsConcurrently() + { + var tasks = new Task[Concurrency]; + for (int i = 0; i < Concurrency; i++) + { + tasks[i] = OpenAndCloseConnectionAsync(); + } + await Task.WhenAll(tasks); + } + + private async Task OpenAndCloseConnectionAsync() + { + using var conn = new SqlConnection(ConnectionString); + await conn.OpenAsync(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/SequentialXmlReadRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/SequentialXmlReadRunner.cs new file mode 100644 index 0000000000..4d7d15843e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/SequentialXmlReadRunner.cs @@ -0,0 +1,98 @@ +// 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.Text; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Benchmarks for XML column reads via SequentialAccess at increasing data sizes + /// to detect O(N²) scaling behavior. + /// Reproduces issue #1877. + /// + public class SequentialXmlReadRunner : BaseRunner + { + private string _tableName; + private string _connectionString; + private string _query; + + /// + /// Size of the XML data in kilobytes. + /// + [Params(10, 100, 500, 1000)] + public int XmlSizeKB { get; set; } + + /// + /// Whether to use CommandBehavior.SequentialAccess. + /// + [Params(true, false)] + public bool UseSequentialAccess { get; set; } + + [GlobalSetup] + public void Setup() + { + _connectionString = s_config.ConnectionString; + string machineHash = ((uint)Environment.MachineName.GetHashCode()).ToString("x8"); + _tableName = $"[perf_SeqXml_{machineHash}_{Guid.NewGuid():N}]"; + using var conn = new SqlConnection(_connectionString); + conn.Open(); + + using var createCmd = new SqlCommand( + $"CREATE TABLE {_tableName} (Id INT IDENTITY PRIMARY KEY, Data XML)", conn); + createCmd.ExecuteNonQuery(); + + // Generate XML of approximately XmlSizeKB kilobytes + string xmlData = GenerateXml(XmlSizeKB * 1024); + + using var insertCmd = new SqlCommand( + $"INSERT INTO {_tableName} (Data) VALUES (@data)", conn); + insertCmd.Parameters.Add("@data", SqlDbType.Xml).Value = xmlData; + insertCmd.ExecuteNonQuery(); + + _query = $"SELECT Data FROM {_tableName}"; + } + + [GlobalCleanup] + public void Cleanup() + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = new SqlCommand($"DROP TABLE IF EXISTS {_tableName}", conn); + cmd.ExecuteNonQuery(); + SqlConnection.ClearAllPools(); + } + + [Benchmark] + public void ReadXml() + { + var behavior = UseSequentialAccess ? CommandBehavior.SequentialAccess : CommandBehavior.Default; + + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = new SqlCommand(_query, conn); + using var reader = cmd.ExecuteReader(behavior); + while (reader.Read()) + { + _ = reader.GetString(0); + } + } + + private static string GenerateXml(int targetBytes) + { + var sb = new StringBuilder(targetBytes + 256); + sb.Append(""); + int index = 0; + while (sb.Length < targetBytes) + { + sb.Append($"This is element number {index} with some padding data to increase size."); + index++; + } + sb.Append(""); + return sb.ToString(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/BenchmarkConfig.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/BenchmarkConfig.cs index 6c0bceb6ba..8cd26c8641 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/BenchmarkConfig.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/BenchmarkConfig.cs @@ -7,25 +7,53 @@ using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Toolchains.InProcess.Emit; +#if WINDOWS +using BenchmarkDotNet.Diagnostics.Windows.Configs; +#endif namespace Microsoft.Data.SqlClient.PerformanceTests { public static class BenchmarkConfig { - public static ManualConfig s_instance(RunnerJob runnerJob) => - DefaultConfig.Instance - .WithOption(ConfigOptions.DisableOptimizationsValidator, true) - .AddDiagnoser(MemoryDiagnoser.Default) - .AddDiagnoser(ThreadingDiagnoser.Default) - .AddExporter(MarkdownExporter.GitHub) - .AddJob( - Job.MediumRun.WithToolchain(InProcessEmitToolchain.Instance) - .WithLaunchCount(runnerJob.LaunchCount) - .WithInvocationCount(runnerJob.InvocationCount) - .WithIterationCount(runnerJob.IterationCount) - .WithWarmupCount(runnerJob.WarmupCount) - .WithUnrollFactor(1) - .WithStrategy(BenchmarkDotNet.Engines.RunStrategy.Throughput) - ); + /// + /// When set to true, attaches the NativeMemoryProfiler and EtwProfiler diagnosers + /// so native memory allocations and ETW traces are captured for each benchmark run. + /// This is only supported on Windows; the value is ignored on other OSes since the + /// underlying diagnosers are compiled out (see the "WINDOWS" compile constant, which + /// is only set when building on Windows in the PerformanceTests.csproj file). + /// + public static bool UseNativeMemoryAndEtwProfiler { get; set; } + + public static ManualConfig s_instance(RunnerJob runnerJob) + { + ManualConfig config = DefaultConfig.Instance + .WithOption(ConfigOptions.DisableOptimizationsValidator, true) + .WithOption(ConfigOptions.DontOverwriteResults, true) + .AddDiagnoser(MemoryDiagnoser.Default) + .AddDiagnoser(ThreadingDiagnoser.Default) + .AddExporter(MarkdownExporter.GitHub) + .AddJob( + Job.MediumRun.WithToolchain(InProcessEmitToolchain.Instance) + .WithLaunchCount(runnerJob.LaunchCount) + .WithInvocationCount(runnerJob.InvocationCount) + .WithIterationCount(runnerJob.IterationCount) + .WithWarmupCount(runnerJob.WarmupCount) + .WithUnrollFactor(1) + .WithStrategy(BenchmarkDotNet.Engines.RunStrategy.Throughput) + .WithEnvironmentVariable("COMPlus_gcServer", "1") + ) + .WithOptions(ConfigOptions.JoinSummary); + +#if WINDOWS + if (UseNativeMemoryAndEtwProfiler) + { + config = config + .AddDiagnoser(NativeMemoryProfiler.Default) + .AddDiagnoser(new EtwProfiler()); + } +#endif + + return config; + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs index f52fb1b554..7a93f65090 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs @@ -11,6 +11,9 @@ public class Config { public string ConnectionString; public bool UseManagedSniOnWindows; + public bool UseOptimizedAsyncBehaviour; + public bool WaitForProfiler; + public bool UseNativeMemoryAndETWProfiler; public Benchmarks Benchmarks; /// @@ -18,7 +21,7 @@ public class Config /// /// 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. + /// "runnerconfig.jsonc" in the current working directory will be used. /// /// /// @@ -32,7 +35,7 @@ public class Config public static Config Load() { return Loader.FromJsonFile( - "runnerconfig.json", "RUNNER_CONFIG"); + "runnerconfig.jsonc", "RUNNER_CONFIG"); } } @@ -43,6 +46,14 @@ public class Benchmarks public RunnerJob SqlBulkCopyRunnerConfig; public RunnerJob DataTypeReaderRunnerConfig; public RunnerJob DataTypeReaderAsyncRunnerConfig; + public RunnerJob AsyncLargeDataReadRunnerConfig; + public RunnerJob MarsOverheadRunnerConfig; + public RunnerJob ParallelAsyncConnectionRunnerConfig; + public RunnerJob CancellationTokenReadAsyncRunnerConfig; + public RunnerJob SequentialXmlReadRunnerConfig; + public RunnerJob JsonVsVarcharReadRunnerConfig; + public RunnerJob BeginTransactionRunnerConfig; + public RunnerJob ConnectionPoolStressRunnerConfig; } public class RunnerJob 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 2da560b6b7..1986ce68e1 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 @@ -17,7 +17,7 @@ - + @@ -38,4 +38,12 @@ + + + + + + + $(DefineConstants);WINDOWS + diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs index 8df9d36a3e..8f64c7448a 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs @@ -11,30 +11,77 @@ public class Program { private readonly Config _config; + /// + /// Initializes a new instance of the class, + /// which loads the benchmark configuration and runs the benchmarks. + /// public Program() { - // Load config file _config = Config.Load(); - if (_config.UseManagedSniOnWindows) - { - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); - } + SetupConfigurations(); + Run_SqlConnectionBenchmark(); Run_SqlCommandBenchmark(); Run_SqlBulkCopyBenchmark(); Run_DataTypeReaderBenchmark(); Run_DataTypeReaderAsyncBenchmark(); + Run_AsyncLargeDataReadBenchmark(); + Run_MarsOverheadBenchmark(); + Run_ParallelAsyncConnectionBenchmark(); + Run_CancellationTokenReadAsyncBenchmark(); + Run_SequentialXmlReadBenchmark(); + Run_JsonVsVarcharReadBenchmark(); + Run_BeginTransactionBenchmark(); + Run_ConnectionPoolStressBenchmark(); // TODOs: - // Transactions - // Insert/Update queries (+CRUD) // Prepared/Regular Parameterized queries - // DataType Reader Max (large size / max columns / million row tables) - // DataType conversions (Implicit) - // MARS enabled // Always Encrypted } + private void SetupConfigurations() + { + // If the config file specifies to use managed SNI on Windows, + // enable the appropriate AppContext switch to use the managed SNI implementation. + if (_config.UseManagedSniOnWindows) + { + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); + } + + // If the config file specifies to use optimized async behavior, + // enable packet multiplexing feature and other optimizations in SqlClient + // by setting the appropriate AppContext switches. + if(_config.UseOptimizedAsyncBehaviour) + { + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour", false); + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni", false); + } + + // If the config file specifies to use the NativeMemoryProfiler and ETW profiler, + // propagate the setting to BenchmarkConfig so it can attach these Windows-only + // diagnosers when building each benchmark's ManualConfig. + BenchmarkConfig.UseNativeMemoryAndEtwProfiler = _config.UseNativeMemoryAndETWProfiler; + + // If the config file specifies to wait for a profiler, + // display the process ID and wait for user input before starting the benchmarks. + if (_config.WaitForProfiler) + { + int pid = System.Diagnostics.Process.GetCurrentProcess().Id; + Console.WriteLine(); + Console.WriteLine("==========================================="); + Console.WriteLine($" Process ID: {pid}"); + Console.WriteLine("==========================================="); + Console.WriteLine(); + Console.WriteLine("Attach your profiler now. Examples:"); + Console.WriteLine($" dotnet-counters monitor -p {pid}"); + Console.WriteLine($" dotnet-trace collect -p {pid}"); + Console.WriteLine(); + Console.WriteLine("Press any key to start benchmarks..."); + Console.ReadKey(intercept: true); + Console.WriteLine(); + } + } + private void Run_SqlConnectionBenchmark() { if (_config.Benchmarks.SqlConnectionRunnerConfig?.Enabled == true) @@ -75,10 +122,73 @@ private void Run_SqlBulkCopyBenchmark() } } - public static void Main() + private void Run_AsyncLargeDataReadBenchmark() + { + if (_config.Benchmarks.AsyncLargeDataReadRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.AsyncLargeDataReadRunnerConfig)); + } + } + + private void Run_MarsOverheadBenchmark() + { + if (_config.Benchmarks.MarsOverheadRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.MarsOverheadRunnerConfig)); + } + } + + private void Run_ParallelAsyncConnectionBenchmark() { - // Run the benchmarks. - new Program(); + if (_config.Benchmarks.ParallelAsyncConnectionRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.ParallelAsyncConnectionRunnerConfig)); + } + } + + private void Run_CancellationTokenReadAsyncBenchmark() + { + if (_config.Benchmarks.CancellationTokenReadAsyncRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.CancellationTokenReadAsyncRunnerConfig)); + } + } + + private void Run_SequentialXmlReadBenchmark() + { + if (_config.Benchmarks.SequentialXmlReadRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.SequentialXmlReadRunnerConfig)); + } + } + + private void Run_JsonVsVarcharReadBenchmark() + { + if (_config.Benchmarks.JsonVsVarcharReadRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.JsonVsVarcharReadRunnerConfig)); + } + } + + private void Run_BeginTransactionBenchmark() + { + if (_config.Benchmarks.BeginTransactionRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.BeginTransactionRunnerConfig)); + } } + + private void Run_ConnectionPoolStressBenchmark() + { + if (_config.Benchmarks.ConnectionPoolStressRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.ConnectionPoolStressRunnerConfig)); + } + } + + /// + /// The main entry point for the performance tests program. + /// + public static void Main() => _ = new Program(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.json b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.json deleted file mode 100644 index 47d1736127..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "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 - }, - "SqlCommandRunnerConfig": { - "Enabled": true, - "LaunchCount": 1, - "IterationCount": 10, - "InvocationCount": 2, - "WarmupCount": 1, - "RowCount": 10000 - }, - "SqlBulkCopyRunnerConfig": { - "Enabled": true, - "LaunchCount": 1, - "IterationCount": 10, - "InvocationCount": 1, - "WarmupCount": 1, - "RowCount": 10000 - }, - "DataTypeReaderRunnerConfig": { - "Enabled": true, - "LaunchCount": 1, - "IterationCount": 20, - "InvocationCount": 2, - "WarmupCount": 1, - "RowCount": 1000 - }, - "DataTypeReaderAsyncRunnerConfig": { - "Enabled": true, - "LaunchCount": 1, - "IterationCount": 20, - "InvocationCount": 2, - "WarmupCount": 1, - "RowCount": 1000 - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc new file mode 100644 index 0000000000..34d7a511aa --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc @@ -0,0 +1,125 @@ +{ + "ConnectionString": "Server=tcp:localhost; Integrated Security=true; Initial Catalog=sqlclient-perf-db;", + // Enable this flag to enable managed SNI on Windows. + "UseManagedSniOnWindows": false, + // Enable this flag to enable optimized async behavior in SqlClient. + // This is useful for benchmarking packet multiplexing and async performance improvements. + "UseOptimizedAsyncBehaviour": true, + // Enable this flag to get prompted to attach a profiler when running the performance tests. + // This is useful for capturing event source traces when running a performance test on any OS. + // On Windows, you can also set UseNativeMemoryAndETWProfiler=true to attach ETW diagnosers automatically. + "WaitForProfiler": false, + // Enable this flag to enable the NativeMemoryProfiler and ETW profiler on Windows (only). + // This is useful for capturing native memory allocation traces when running a performance test on Windows. + // Note: This flag cannot be enabled on non-Windows OSes as the NativeMemoryProfiler is not supported on non-Windows OSes. + "UseNativeMemoryAndETWProfiler": false, + // This section contains the configuration for each benchmark. + // You can enable/disable benchmarks and configure the number of iterations, invocations, + // warmup runs, and row count for each benchmark. + "Benchmarks": { + "SqlConnectionRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 50, + "InvocationCount":30, + "WarmupCount": 5, + "RowCount": 0 + }, + "SqlCommandRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 2, + "WarmupCount": 1, + "RowCount": 10000 + }, + "SqlBulkCopyRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 10000 + }, + "DataTypeReaderRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 2, + "WarmupCount": 1, + "RowCount": 1000 + }, + "DataTypeReaderAsyncRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 2, + "WarmupCount": 1, + "RowCount": 1000 + }, + "AsyncLargeDataReadRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 0 + }, + "MarsOverheadRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 1000 + }, + "ParallelAsyncConnectionRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 0 + }, + "CancellationTokenReadAsyncRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 10000 + }, + "SequentialXmlReadRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 0 + }, + "JsonVsVarcharReadRunnerConfig": { + "Enabled": false, + "LaunchCount": 1, + "IterationCount": 100, + "InvocationCount": 1, + "WarmupCount": 5, + "RowCount": 10000 + }, + "BeginTransactionRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 2, + "WarmupCount": 1, + "RowCount": 0 + }, + "ConnectionPoolStressRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 20, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 0 + } + } +} From 71086453e251f6abadca9d28d2081beee5f19ce3 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:00:41 +0100 Subject: [PATCH 195/298] Feature | Add scaffolding for SSRP response parsing (#3741) * Add scaffolding for SSRP response parsing This consists of: * ReadOnlySequenceSegment derivative, * Utilities to read data from a ReadOnlySequence * Test cases * Replace ActiveIssue attributes with [Theory(Skip = "...")] * Formatting changes Lifted some constant string components to constants. Newline formatting. Where possible, used expression body. * Naming convention changes * Parameter line chopping * Respond to code review Correct screaming snake case method. Correct ensure that test case provides a zero-length Version field (not an absent one.) * Add SSRP parsing test cases --------- Co-authored-by: Ben Russell --- .../src/Microsoft/Data/Common/PacketBuffer.cs | 31 + .../Data/Common/ReadOnlySequenceUtilities.cs | 92 +++ .../System/Diagnostics/CodeAnalysis.netfx.cs | 8 + .../Data/Sql/DacResponseProcessorTest.cs | 33 + .../Sql/SqlDataSourceResponseProcessorTest.cs | 56 ++ .../Microsoft/Data/Sql/SsrpPacketTestData.cs | 738 ++++++++++++++++++ 6 files changed, 958 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/PacketBuffer.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ReadOnlySequenceUtilities.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/PacketBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/PacketBuffer.cs new file mode 100644 index 0000000000..079c2933a5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/PacketBuffer.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; + +#nullable enable + +namespace Microsoft.Data.Common; + +/// +/// One buffer, which may contain one unparsed packet from a single destination. +/// +internal sealed class PacketBuffer : ReadOnlySequenceSegment +{ + public PacketBuffer(ReadOnlyMemory buffer, PacketBuffer? previous) + { + Memory = buffer; + + if (previous is not null) + { + previous.Next = this; + RunningIndex = previous.RunningIndex + previous.Memory.Length; + } + else + { + RunningIndex = 0; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ReadOnlySequenceUtilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ReadOnlySequenceUtilities.cs new file mode 100644 index 0000000000..792ca6ddfc --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ReadOnlySequenceUtilities.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; +using System.Buffers.Binary; + +namespace Microsoft.Data.Common; + +internal static class ReadOnlySequenceUtilities +{ + /// + /// Reads the next byte from the sequence, advancing its position by one byte. + /// + /// The sequence to read and to advance from. + /// The first span in the sequence. Reassigned if the next byte can only be read from the next span. + /// Current position in the sequence. Advanced by one byte following a successful read. + /// The value read from . + /// true if was long enough to retrieve the next byte, false otherwise. + public static bool ReadByte(this ref ReadOnlySequence sequence, ref ReadOnlySpan currSpan, ref long currPos, out byte value) + { + if (sequence.Length < sizeof(byte)) + { + value = default; + return false; + } + + currPos += sizeof(byte); + if (currSpan.Length >= sizeof(byte)) + { + value = currSpan[0]; + + sequence = sequence.Slice(sizeof(byte)); + currSpan = currSpan.Slice(sizeof(byte)); + + return true; + } + else + { + Span buffer = stackalloc byte[sizeof(byte)]; + + sequence.Slice(0, sizeof(byte)).CopyTo(buffer); + value = buffer[0]; + + sequence = sequence.Slice(sizeof(byte)); + currSpan = sequence.First.Span; + + return true; + } + } + + /// + /// Reads the next two bytes from the sequence as a , advancing its position by two bytes. + /// + /// The sequence to read and to advance from. + /// The first span in the sequence. Reassigned if the next two bytes can only be read from the next span. + /// Current position in the sequence. Advanced by two bytes following a successful read. + /// The value read from + /// true if was long enough to retrieve the next two bytes, false otherwise. + public static bool ReadLittleEndian(this ref ReadOnlySequence sequence, ref ReadOnlySpan currSpan, ref long currPos, out ushort value) + { + if (sequence.Length < sizeof(ushort)) + { + value = default; + return false; + } + + currPos += sizeof(ushort); + if (currSpan.Length >= sizeof(ushort)) + { + value = BinaryPrimitives.ReadUInt16LittleEndian(currSpan); + + sequence = sequence.Slice(sizeof(ushort)); + currSpan = currSpan.Slice(sizeof(ushort)); + + return true; + } + else + { + Span buffer = stackalloc byte[sizeof(ushort)]; + + sequence.Slice(0, sizeof(ushort)).CopyTo(buffer); + value = BinaryPrimitives.ReadUInt16LittleEndian(buffer); + + sequence = sequence.Slice(sizeof(ushort)); + currSpan = sequence.First.Span; + + return true; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.netfx.cs b/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.netfx.cs index 1bf90ad001..4ac00aae59 100644 --- a/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/System/Diagnostics/CodeAnalysis.netfx.cs @@ -15,6 +15,14 @@ namespace System.Diagnostics.CodeAnalysis internal sealed class NotNullAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + public bool ReturnValue { get; } + } } #endif diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs new file mode 100644 index 0000000000..fe56f8c5d5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/DacResponseProcessorTest.cs @@ -0,0 +1,33 @@ +// 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.Buffers; +using Xunit; + +namespace Microsoft.Data.Sql.UnitTests; + +public class DacResponseProcessorTest +{ + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.InvalidSvrRespDacPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_InvalidDacResponse_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.ValidSvrRespDacPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_ValidDacResponse_ReturnsData(ReadOnlySequence packetBuffers, int expectedDacPort) + { + _ = packetBuffers; + _ = expectedDacPort; + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs new file mode 100644 index 0000000000..695a8a5cbb --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SqlDataSourceResponseProcessorTest.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using Xunit; + +namespace Microsoft.Data.Sql.UnitTests; + +public class SqlDataSourceResponseProcessorTest +{ + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.EmptyPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_EmptyBuffer_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.InvalidSvrRespPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_InvalidSqlDataSourceResponse_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.InvalidRespDataPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_InvalidSqlDataSourceResponse_RespData_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.InvalidTcpInfoPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_InvalidSqlDataSourceResponse_TcpInfo_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.InvalidClntUcastInstSvrRespPackets), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_InvalidSqlDataSourceResponseToClntUcastInst_ReturnsFalse(ReadOnlySequence packetBuffers) + { + _ = packetBuffers; + } + + [Theory(Skip = "Implementation in progress, see GH #3700")] + [MemberData(nameof(SsrpPacketTestData.ValidSvrRespPacketBuffer), MemberType = typeof(SsrpPacketTestData), DisableDiscoveryEnumeration = true)] + public void Process_ValidSqlDataSourceResponse_ReturnsData(ReadOnlySequence packetBuffers, string expectedVersion, int expectedTcpPort, string? expectedPipeName) + { + _ = packetBuffers; + _ = expectedVersion; + _ = expectedTcpPort; + _ = expectedPipeName; + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs new file mode 100644 index 0000000000..9729c33949 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Sql/SsrpPacketTestData.cs @@ -0,0 +1,738 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Data.Common; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Microsoft.Data.Sql.UnitTests; + +/// +/// Test cases used to verify the successful processing of valid SSRP responses and the silent +/// discarding of invalid SSRP responses. +/// +internal static class SsrpPacketTestData +{ + private const byte ValidSvrRespHeader = 0x05; + private const byte ValidRespDataDacProtocolVersion = 0x01; + private const byte ValidRespDataDacResponseSize = 0x06; + + private const string ValidServerName = "srv1"; + private const string ValidInstanceName = "MSSQLSERVER"; + private const string ValidServerVersion = "14.0.0.0"; + private const int ValidTcpPort1 = 1433; + private const int ValidTcpPort2 = 1434; + private const int ValidTcpPort3 = 1435; + private const int ValidTcpPort4 = 1436; + private const int ValidTcpPort5 = 1437; + + /// + /// One empty packet buffer, which should be successfully processed and contain zero responses. + /// + /// + /// + public static TheoryData> EmptyPacketBuffer => + new(GeneratePacketBuffers([])); + + /// + /// Various combinations of packet buffers containing normal SVR_RESP responses, all of which + /// should be successfully processed. + /// + /// + public static TheoryData, string, int, string?> ValidSvrRespPacketBuffer + { + get + { + const string PipeName = $@"\\{ValidServerName}\pipe\SampléPipeName"; + + string complexProtocolParameters = CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}", + npInfo: $"np;{PipeName}", + viaInfo: $"via;{ValidServerName} 1:1433", + rpcInfo: $"rpc;{ValidServerName}", + spxInfo: $"spx;{ValidInstanceName}", + adspInfo: "adsp;SQL2000", + bvInfo: "bv;item;group;item;group;org"); + + byte[] complexValidPacket = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, complexProtocolParameters)); + byte[] validPacket1 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}"))); + byte[] validPacket2 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}"))); + byte[] validPacket3 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort3}"))); + byte[] validPacket4 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion, CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort4}"))); + byte[] invalidPacket1 = FormatSvrRespMessage(ValidSvrRespHeader, + respData: CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, "v14", CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}"))); + + return new() + { + { + // One buffer, one response + GeneratePacketBuffers(complexValidPacket), + ValidServerVersion, + ValidTcpPort1, + PipeName + }, + { + // One response, split into four buffers in the middle of a string + GeneratePacketBuffers( + complexValidPacket.AsSpan(0, 14).ToArray(), + complexValidPacket.AsSpan(14, 22).ToArray(), + complexValidPacket.AsSpan(36, 71).ToArray(), + // Position 107 is the second byte of the é character when encoded to UTF8. + complexValidPacket.AsSpan(107).ToArray()), + ValidServerVersion, + ValidTcpPort1, + PipeName + }, + { + // Four responses, each with different methods + GeneratePacketBuffers( + validPacket1, + validPacket2, + validPacket3, + validPacket4), + ValidServerVersion, + ValidTcpPort4, + null + }, + { + // Five responses, with response three invalid + GeneratePacketBuffers( + complexValidPacket, + validPacket2, + invalidPacket1, + validPacket3, + validPacket4), + ValidServerVersion, + ValidTcpPort4, + null + } + }; + } + } + + /// + /// Various combinations of packet buffers containing SVR_RESP (DAC) responses, all of which + /// should be successfully processed. + /// + /// + public static TheoryData, int> ValidSvrRespDacPacketBuffer + { + get + { + byte[] validPacket1 = FormatSvrRespMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2)); + byte[] validPacket2 = FormatSvrRespMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort3)); + byte[] validPacket3 = FormatSvrRespMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort4)); + byte[] validPacket4 = FormatSvrRespMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort5)); + byte[] invalidPacket1 = FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: 0x03, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2)); + + return new() + { + { + // One buffer, one response + GeneratePacketBuffers(validPacket1), + ValidTcpPort2 + }, + { + // One response, split into three buffers. + // Buffer 1: the header byte + // Buffer 2: both bytes of RESP_SIZE and the first byte of RESP_DATA (protocol version). + // Buffer 3: remainder. + GeneratePacketBuffers(validPacket1.AsSpan(0, 1).ToArray(), + validPacket1.AsSpan(1, 3).ToArray(), + validPacket1.AsSpan(4).ToArray()), + ValidTcpPort2 + }, + { + // One response, split into three buffers. + // Buffer 1: the header and first byte of RESP_SIZE. + // Buffer 2: the second byte of RESP_SIZE and the first byte of RESP_DATA (protocol version). + // Buffer 3: remainder. + GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), + validPacket1.AsSpan(2, 2).ToArray(), + validPacket1.AsSpan(4).ToArray()), + ValidTcpPort2 + }, + { + // Two responses with trailing data + GeneratePacketBuffers(validPacket1.AsSpan(0, 2).ToArray(), + validPacket1.AsSpan(2, 2).ToArray(), + [..validPacket1.AsSpan(4).ToArray(), 0x05], + validPacket2.AsSpan(0, 2).ToArray(), + validPacket2.AsSpan(2).ToArray(), + [0x05]), + ValidTcpPort3 + }, + { + // Four responses, each with different DAC ports + GeneratePacketBuffers(validPacket1, + validPacket2, + validPacket3, + validPacket4), + ValidTcpPort5 + }, + { + // Five responses, with response three invalid + GeneratePacketBuffers(validPacket1, + validPacket2, + invalidPacket1, + validPacket3, + validPacket4), + ValidTcpPort5 + }, + { + // Four responses, with three extraneous 0x05 bytes between responses 2 and 3 + GeneratePacketBuffers(validPacket1, + [..validPacket2, 0x05], + [0x05], + [0x05, ..validPacket3], + validPacket4), + ValidTcpPort5 + } + }; + } + } + + /// + /// Packet buffers containing nothing but invalid SVR_RESP (DAC) responses. + /// + /// + public static TheoryData> InvalidSvrRespDacPackets => + [ + // Invalid header byte + GeneratePacketBuffers(FormatSvrRespMessage(header: 0x00, + ValidRespDataDacResponseSize, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // Invalid size + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: 0x09, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // Invalid protocol version + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRespData(protocolVersion: 0x02, ValidTcpPort2))), + + // Invalid port + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRespData(ValidRespDataDacProtocolVersion, 0))) + ]; + + /// + /// Packets containing an SVR_RESP response which is a valid response to a CLNT_[B|U]CAST_EX message + /// but not to a CLNT_UCAST_INST message. + /// + /// + public static TheoryData> InvalidClntUcastInstSvrRespPackets + { + get + { + // The RESP_DATA section of the response to a CLNT_UCAST_INST message must be shorter than 1024 bytes. + byte[] longPacket = FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort1}", npInfo: @"np;" + new string('a', 1025)))); + + return [GeneratePacketBuffers(longPacket)]; + } + } + + /// + /// Packet buffers containing an SSRP message which is failing due to invalid data + /// in the top-level SVR_RESP message fields. + /// + /// + public static TheoryData> InvalidSvrRespPackets => + [ + // Invalid SVR_RESP header field value + GeneratePacketBuffers(FormatSvrRespMessage(header: 0x04, + ValidRespDataDacResponseSize, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE too small (DAC response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: 0x05, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE too large (DAC response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize : 0x07, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE zero length (DAC response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: 0x00, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE far beyond reasonable limits (normal response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: ushort.MaxValue, + CreateRespData(ValidRespDataDacProtocolVersion, ValidTcpPort2))), + + // RESP_SIZE larger than the buffer (normal response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: 72, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))), + + // RESP_SIZE zero length (normal response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: 0x00, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))), + + // RESP_SIZE far beyond reasonable limits (normal response) + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + serializedResponseSize: ushort.MaxValue, + CreateRespData(ValidServerName, ValidInstanceName, isClustered: true, ValidServerVersion))) + ]; + + /// + /// Packet buffers containing an SSRP message with valid top-level SVR_RESP message + /// fields but invalid components of the child RESP_DATA structure. + /// + /// + public static TheoryData> InvalidRespDataPackets + { + get + { + const string InvalidServerName = "sr\u0008v\u00001"; + string validTcpInfo = CreateProtocolParameters($"tcp;{ValidTcpPort1}"); + + return [ + // All keys lowercase + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + lowercaseKey: true))), + + // Keys shuffled + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + shuffleKeys: true))), + + // Does not start with "ServerName" string + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitServerName: true))), + + // Server name longer than 255 bytes + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(serverName: new string('a', 256), + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo))), + + // Server name contains invalid characters + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(serverName: InvalidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo))), + + // Missing semicolons between keys and values + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitKeyValueSeparators: true))), + + // Missing terminating pair of semicolons + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitTrailingSemicolons: true))), + + // Missing "InstanceName" + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitInstanceName: true))), + + // Duplicate "InstanceName" key with different value + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + protocolParameters: "InstanceName;DUPLICATE"))), + + // Instance name longer than 255 bytes + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + instanceName: new string('a', 256), + isClustered: true, + ValidServerVersion, + validTcpInfo))), + + // Missing "IsClustered" + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitIsClustered: true))), + + // Invalid IsClustered value - omit the IsClustered;Yes pair and supply an invalid one. + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + protocolParameters: "IsClustered;INVALID;" + validTcpInfo, + omitIsClustered: true))), + + // Duplicate "IsClustered" key with different value + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + protocolParameters: "IsClustered;DUPLICATE"))), + + // Missing "Version" + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + validTcpInfo, + omitVersion: true))), + + // Empty version string + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + version: string.Empty, + validTcpInfo))), + + // Version string longer than 16 bytes + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + version: "65535.65535.65.53", + validTcpInfo))), + + // Version string not in the correct format: 1*[0-9"."] + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + version: "v14", + validTcpInfo))), + + // Duplicate "Version" key with different value + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + protocolParameters: "Version;DUPLICATE"))), + + // Protocol components listed twice + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: $"tcp;{ValidTcpPort3}")))), + + // Invalid protocol components appear + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(tcpInfo: $"tcp;{ValidTcpPort2}", otherParameters: "invalid_protocol;value")))), + + // Empty protocol components appear + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(otherParameters: ";value")))), + + // Invalid PROTOCOLVERSION field value + GeneratePacketBuffers(FormatSvrRespMessage(ValidSvrRespHeader, + ValidRespDataDacResponseSize, + CreateRespData(protocolVersion: 0x02, ValidTcpPort2))) + ]; + } + } + + /// + /// Packet buffers containing an SSRP message with valid top-level SVR_RESP message + /// fields, a valid RESP_DATA child structure but an invalid TCP_INFO structure. + /// + /// + public static TheoryData> InvalidTcpInfoPackets + { + get + { + return [ + // Port is absent + CreateSvrRespMessage("tcp"), + + // Port is zero-length + CreateSvrRespMessage("tcp;"), + + // Port is non-numeric + CreateSvrRespMessage("tcp;one"), + + // Port is > ushort.MaxValue + CreateSvrRespMessage("tcp;65536"), + + // Port is < 0 + CreateSvrRespMessage("tcp;-1") + ]; + + static ReadOnlySequence CreateSvrRespMessage(string tcpInfo) => + GeneratePacketBuffers( + FormatSvrRespMessage(ValidSvrRespHeader, + CreateRespData(ValidServerName, + ValidInstanceName, + isClustered: true, + ValidServerVersion, + CreateProtocolParameters(tcpInfo)))); + } + } + + private static ReadOnlySequence GeneratePacketBuffers(params byte[][] packetBuffers) + { + if (packetBuffers.Length == 0) + { + return ReadOnlySequence.Empty; + } + + PacketBuffer first = new(packetBuffers[0], null); + PacketBuffer curr = first; + PacketBuffer last; + + for (int i = 1; i < packetBuffers.Length; i++) + { + curr = new(packetBuffers[i], curr); + } + last = curr; + + return new ReadOnlySequence(first, 0, last, last.Memory.Length); + } + + /// + /// Generates an SVR_RESP message with a valid length. + /// + /// The SVR_RESP header value. Expected to be 0x05. + /// The serialized RESP_DATA section. + /// + /// A byte representation of one SVR_RESP message. + private static byte[] FormatSvrRespMessage(byte header, ReadOnlySpan respData) => + FormatSvrRespMessage(header, (ushort)respData.Length, respData); + + /// + /// Generates an SVR_RESP message with specific characteristics. + /// + /// The SVR_RESP header value. Expected to be 0x05. + /// The RESP_SIZE field to be serialized to the header. + /// The serialized RESP_DATA section. + /// If specified, the number of bytes to actually write. + /// A byte representation of one SVR_RESP message. + /// + /// + private static byte[] FormatSvrRespMessage(byte header, + ushort serializedResponseSize, + ReadOnlySpan respData, + int? realResponseSize = null) + { + byte[] realRespData = realResponseSize is null + ? new byte[sizeof(byte) + sizeof(ushort) + respData.Length] + : new byte[realResponseSize.Value]; + + // Pad any free space after RESP_DATA with 0x05 + if (realRespData.Length > sizeof(byte) + sizeof(ushort) + respData.Length) + { + realRespData.AsSpan(sizeof(byte) + sizeof(ushort) + respData.Length).Fill(0x05); + } + + // Write RESP_DATA + if (realRespData.Length > sizeof(byte) + sizeof(ushort)) + { + int bytesToCopy = Math.Min(respData.Length, realRespData.Length - sizeof(byte) - sizeof(ushort)); + + respData.Slice(0, bytesToCopy).CopyTo(realRespData.AsSpan(sizeof(byte) + sizeof(ushort))); + } + + // Write RESP_SIZE + if (realRespData.Length > sizeof(byte)) + { + Span responseSizeBytes = stackalloc byte[sizeof(ushort)]; + int bytesToCopy = Math.Min(responseSizeBytes.Length, realRespData.Length - sizeof(byte)); + + BinaryPrimitives.WriteUInt16LittleEndian(responseSizeBytes, serializedResponseSize); + responseSizeBytes.Slice(0, bytesToCopy).CopyTo(realRespData.AsSpan(sizeof(byte))); + } + + // Write SVR_RESP + if (realRespData.Length > 0) + { + realRespData[0] = header; + } + + return realRespData; + } + + /// + /// Creates a new RESP_DATA section of an SVR_RESP message for the DAC request with a specified protocol version and TCP port number. + /// + /// Protocol version. Expected to be 0x01. + /// TCP port number of the DAC. + /// A byte representation of a RESP_DATA section. + /// + private static byte[] CreateRespData(byte protocolVersion, ushort dacPort) + { + byte[] data = new byte[sizeof(byte) + sizeof(ushort)]; + + data[0] = protocolVersion; + BinaryPrimitives.WriteUInt16LittleEndian(data.AsSpan(1), dacPort); + return data; + } + + /// + /// Creates a RESP_DATA section of an SVR_RESP message with specific characteristics. + /// + /// ServerName parameter value. + /// InstanceName parameter value. + /// IsClustered parameter value. + /// Version parameter value. + /// If specified, the protocol parameters. Generated by . + /// If true, the ServerName, InstanceName, IsClustered and Version keys will be written in lowercase. + /// If true, the return value will not include the trailing ;;. + /// If true, no separators between the keys and values will be written. + /// If true, the mandatory ServerName parameter value will not be written. + /// If true, the mandatory InstanceName parameter value will not be written. + /// If true, the mandatory IsClustered parameter value will not be written. + /// If true, the mandatory Version parameter value will not be written. + /// If true, the key/value pairs will be written in a non-sequential order. + /// A byte representation of a RESP_DATA section. + /// + private static byte[] CreateRespData(string serverName, + string instanceName, + bool isClustered, + string version, + string? protocolParameters = null, + bool lowercaseKey = false, + bool omitTrailingSemicolons = false, + bool omitKeyValueSeparators = false, + bool omitServerName = false, + bool omitInstanceName = false, + bool omitIsClustered = false, + bool omitVersion = false, + bool shuffleKeys = false) + { + string serverNameKey = GenerateKeyValuePair("ServerName", serverName, omitServerName); + string instanceNameKey = GenerateKeyValuePair("InstanceName", instanceName, omitInstanceName); + string isClusteredKey = GenerateKeyValuePair("IsClustered", isClustered ? "Yes" : "No", omitIsClustered); + string versionKey = GenerateKeyValuePair("Version", version, omitVersion); + string[] components = + shuffleKeys + ? [protocolParameters ?? string.Empty, versionKey, isClusteredKey, instanceNameKey, serverNameKey] + : [serverNameKey, instanceNameKey, isClusteredKey, versionKey, protocolParameters ?? string.Empty]; + string outputString = string.Join(";", components) + + (omitTrailingSemicolons ? string.Empty : ";;"); + + return Encoding.UTF8.GetBytes(outputString); + + string GenerateKeyValuePair(string key, string value, bool omitKey) + { + if (omitKey) + { + return string.Empty; + } + + if (lowercaseKey) + { + key = key.ToLower(); + } + + return key + (omitKeyValueSeparators ? string.Empty : ";") + value; + } + } + + /// + /// Creates the protocol parameters for a RESP_DATA section. + /// + /// If non-null, the TCP_INFO data. + /// If non-null, the NP_INFO data. + /// If non-null, the VIA_INFO data. + /// If non-null, the RPC_INFO data + /// If non-null, the SPX_INFO data. + /// If non-null, the ADSP_INFO data. + /// If non-null, the BV_INFO data. + /// Any additional protocol parameters to include. + /// The collated protocol parameters for a RESP_DATA section. + private static string CreateProtocolParameters(string? tcpInfo = null, + string? npInfo = null, + string? viaInfo = null, + string? rpcInfo = null, + string? spxInfo = null, + string? adspInfo = null, + string? bvInfo = null, + string? otherParameters = null) + { + List protocolParameters = []; + ReadOnlySpan allParams = [tcpInfo, npInfo, viaInfo, rpcInfo, spxInfo, adspInfo, bvInfo, otherParameters]; + + foreach (string? param in allParams) + { + if (param is not null) + { + protocolParameters.Add(param); + } + } + + return string.Join(";", protocolParameters); + } +} From 8b90590c2cd0a98aeec6df053d7f9d95b678faa4 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:01:37 +0100 Subject: [PATCH 196/298] Cleanup | OS-independent build of non-SNI types (#4207) --- .../Handles/SafeLibraryHandle.netcore.cs | 2 +- .../src/Interop/Windows/Kernel32/Kernel32.cs | 11 +++----- .../Windows/Kernel32/Kernel32Safe.netfx.cs | 4 +-- .../Interop/Windows/Sni/AuthProviderInfo.cs | 4 --- .../src/Interop/Windows/Sni/ConsumerInfo.cs | 4 --- .../src/Interop/Windows/Sni/ConsumerNumber.cs | 4 --- .../src/Interop/Windows/Sni/Delegates.cs | 4 --- .../src/Interop/Windows/Sni/IoType.cs | 4 --- .../src/Interop/Windows/Sni/Prefix.cs | 4 --- .../src/Interop/Windows/Sni/Provider.cs | 4 --- .../src/Interop/Windows/Sni/QueryType.cs | 4 --- .../Windows/Sni/SniClientConsumerInfo.cs | 4 --- .../Interop/Windows/Sni/SniConsumerInfo.cs | 4 --- .../Interop/Windows/Sni/SniDnsCacheInfo.cs | 4 --- .../src/Interop/Windows/Sni/SniError.cs | 4 --- .../Interop/Windows/Sni/SniSslProtocols.cs | 4 --- ...ependencyProcessDispatcherStorage.netfx.cs | 2 +- .../Sni/TransparentNetworkResolutionMode.cs | 4 --- .../src/Microsoft.Data.SqlClient.csproj | 4 ++- .../Data/Common/AdapterUtil.netfx.cs | 2 -- .../SqlClient/Diagnostics/SqlClientMetrics.cs | 4 +-- .../Data/SqlClient/LocalAppContextSwitches.cs | 26 +++++++------------ ....netcore.windows.cs => LocalDB.netcore.cs} | 17 +++++++++--- .../ManagedSni/LocalDB.netcore.unix.cs | 20 -------------- .../Microsoft/Data/SqlClient/SqlDependency.cs | 4 +-- 25 files changed, 34 insertions(+), 118 deletions(-) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/{LocalDB.netcore.windows.cs => LocalDB.netcore.cs} (96%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.unix.cs diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Handles/SafeLibraryHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Handles/SafeLibraryHandle.netcore.cs index 54512985f1..35142e1ee8 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Handles/SafeLibraryHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Handles/SafeLibraryHandle.netcore.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. -#if NET && _WINDOWS +#if NET using Microsoft.Win32.SafeHandles; diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32.cs index 020726b104..5ff162d69e 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32.cs @@ -8,7 +8,7 @@ using Microsoft.Data.Common; using Microsoft.Win32.SafeHandles; -#if !NETFRAMEWORK && _WINDOWS +#if NET using Interop.Windows.Handles; #endif @@ -19,11 +19,6 @@ internal static class Kernel32 internal const ushort FILE_DEVICE_FILE_SYSTEM = 0x0009; internal const uint SEM_FAILCRITICALERRORS = 1; - #if _WINDOWS && !NETFRAMEWORK - internal const int LOAD_LIBRARY_AS_DATAFILE = 0x00000002; - internal const int LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800; - #endif - private const string DllName = "kernel32.dll"; /// CTL_CODE method. @@ -54,7 +49,7 @@ internal static extern bool DeviceIoControl( out uint cbBytesReturned, IntPtr overlapped); - #if NET && _WINDOWS + #if NET [DllImport(DllName, ExactSpelling = true, SetLastError = true)] public static extern bool FreeLibrary([In] IntPtr hModule); #endif @@ -71,7 +66,7 @@ internal static extern int GetFullPathName( IntPtr lpFilePartOrNull); #endif - #if NET && _WINDOWS + #if NET [DllImport(DllName, CharSet = CharSet.Ansi, BestFitMapping = false)] public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32Safe.netfx.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32Safe.netfx.cs index 874614765f..95a5d81440 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32Safe.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Kernel32/Kernel32Safe.netfx.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 && _WINDOWS +#if NETFRAMEWORK using System; using System.Runtime.InteropServices; diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/AuthProviderInfo.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/AuthProviderInfo.cs index e273e5fc1b..7b0818b7c5 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/AuthProviderInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/AuthProviderInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System.Runtime.InteropServices; namespace Interop.Windows.Sni @@ -25,5 +23,3 @@ internal struct AuthProviderInfo public string serverCertFileName; } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ConsumerInfo.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ConsumerInfo.cs index fcb7b9a20d..358e35cda0 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ConsumerInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ConsumerInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.Runtime.InteropServices; @@ -18,5 +16,3 @@ internal struct ConsumerInfo internal IntPtr key; } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ConsumerNumber.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ConsumerNumber.cs index f7b11801c9..a1d4fdf292 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ConsumerNumber.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ConsumerNumber.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Sni { internal enum ConsumerNumber @@ -14,5 +12,3 @@ internal enum ConsumerNumber SNI_Consumer_Invalid, } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Delegates.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Delegates.cs index 720ad481e4..fd1f2d8e39 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Delegates.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Delegates.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.Runtime.InteropServices; @@ -14,5 +12,3 @@ namespace Interop.Windows.Sni internal delegate IntPtr SqlClientCertificateDelegate(IntPtr pCallbackContext); } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/IoType.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/IoType.cs index ef02350e84..167a7a23b5 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/IoType.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/IoType.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Sni { internal enum IoType @@ -12,5 +10,3 @@ internal enum IoType WRITE, } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Prefix.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Prefix.cs index 52c98bfde9..45ec71a2cf 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Prefix.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Prefix.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Sni { internal enum Prefix @@ -16,5 +14,3 @@ internal enum Prefix INVALID_PREFIX, } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Provider.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Provider.cs index c9bf7a980c..61b33ebfc1 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Provider.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/Provider.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Sni { internal enum Provider @@ -26,5 +24,3 @@ internal enum Provider INVALID_PROV, } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/QueryType.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/QueryType.cs index 14de40336f..5fca987240 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/QueryType.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/QueryType.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Sni { internal enum QueryType @@ -49,5 +47,3 @@ internal enum QueryType #endif } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniClientConsumerInfo.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniClientConsumerInfo.cs index 89199e4417..062c36679f 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniClientConsumerInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniClientConsumerInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System.Runtime.InteropServices; using Microsoft.Data.SqlClient; @@ -36,5 +34,3 @@ internal unsafe struct SniClientConsumerInfo public SniDnsCacheInfo DNSCacheInfo; } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniConsumerInfo.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniConsumerInfo.cs index 8cb27dfb5f..2f084f228d 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniConsumerInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniConsumerInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.Runtime.InteropServices; @@ -23,5 +21,3 @@ internal struct SniConsumerInfo public IntPtr NodeAffinity; } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniDnsCacheInfo.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniDnsCacheInfo.cs index 1d5600aec9..e0e3c91efe 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniDnsCacheInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniDnsCacheInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System.Runtime.InteropServices; namespace Interop.Windows.Sni @@ -21,5 +19,3 @@ internal struct SniDnsCacheInfo public string wszCachedTcpPort; } } - -#endif 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 55f64a32d3..2e519c15ab 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System.Runtime.InteropServices; namespace Interop.Windows.Sni @@ -23,5 +21,3 @@ internal struct SniError internal uint lineNumber; } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniSslProtocols.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniSslProtocols.cs index f070082c16..cd9bf47ed3 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniSslProtocols.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniSslProtocols.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Sni { internal enum SniSslProtocols : uint @@ -32,5 +30,3 @@ internal enum SniSslProtocols : uint SP_PROT_TLS1_3 = SP_PROT_TLS1_3_SERVER | SP_PROT_TLS1_3_CLIENT, } } - -#endif 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 dc5b5be57e..57481d18b3 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 @@ -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. -#if NETFRAMEWORK && _WINDOWS +#if NETFRAMEWORK using System; using System.Diagnostics; diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/TransparentNetworkResolutionMode.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/TransparentNetworkResolutionMode.cs index 8d1ac82449..fe68cd420d 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/TransparentNetworkResolutionMode.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/TransparentNetworkResolutionMode.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - namespace Interop.Windows.Sni { internal enum TransparentNetworkResolutionMode : byte @@ -13,5 +11,3 @@ internal enum TransparentNetworkResolutionMode : byte ParallelMode } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 92c648c283..dc327b024a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -235,7 +235,9 @@ + Condition="'$(NormalizedTargetOs)' == 'windows_nt' AND '$(TargetFramework)' != 'net462'"> + ILLink.Substitutions.xml + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs index 99f627d603..d1d001448e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs @@ -13,9 +13,7 @@ using System.Text; using System.Threading.Tasks; -#if _WINDOWS using Interop.Windows.Kernel32; -#endif namespace Microsoft.Data.Common { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs index ed27a9591f..cb8c224440 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -9,9 +9,7 @@ #if NET using System.Threading; #else -#if _WINDOWS using Interop.Windows.Kernel32; -#endif using Microsoft.Data.Common; using System.Diagnostics; 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 85f8c87937..06bf6c4f0e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -139,7 +139,7 @@ internal static class LocalAppContextSwitches private const string UseOverallConnectTimeoutForPoolWaitString = "Switch.Microsoft.Data.SqlClient.UseOverallConnectTimeoutForPoolWait"; - #if NET && _WINDOWS + #if NET /// /// The name of the app context switch that controls whether to use the /// managed SNI implementation instead of the native SNI implementation on @@ -258,7 +258,7 @@ private enum SwitchValue : byte /// private static SwitchValue s_useOverallConnectTimeoutForPoolWait = SwitchValue.None; - #if NET && _WINDOWS + #if NET /// /// The cached value of the UseManagedNetworking switch. /// @@ -612,7 +612,7 @@ public static bool UseCompatibilityAsyncBehaviour defaultValue: false, ref s_useOverallConnectTimeoutForPoolWait); - #if NET && _WINDOWS + #if NET /// /// When set to true, .NET on Windows will use the managed SNI /// implementation instead of the native SNI implementation. @@ -621,24 +621,22 @@ public static bool UseCompatibilityAsyncBehaviour /// trimmed away when the corresponding AppContext switch is set at compile /// time. In such cases, this property will return a constant value, even if /// the AppContext switch is set or reset at runtime. See the - /// ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml - /// resource files for details. + /// ILLink.Substitutions.xml resource file for details. /// - /// The default value of this switch is false. + /// The default value of this switch is false on Windows and true on non-Windows platforms. /// public static bool UseManagedNetworking { get { - if (s_useManagedNetworking != SwitchValue.None) + if (!OsConstants.IsWindows) { - return s_useManagedNetworking == SwitchValue.True; + return true; } - if (!OsConstants.IsWindows) + if (s_useManagedNetworking != SwitchValue.None) { - s_useManagedNetworking = SwitchValue.True; - return true; + return s_useManagedNetworking == SwitchValue.True; } if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) @@ -651,12 +649,6 @@ public static bool UseManagedNetworking return false; } } - #elif NET - /// - /// .NET Core on Unix does not support native SNI, so this will always be - /// true. - /// - public static bool UseManagedNetworking => true; #else /// /// .NET Framework does not support the managed SNI, so this will always be diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.cs similarity index 96% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.cs index bd45d307a1..6dfa1c351f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 && _WINDOWS +#if NET using System; using System.Runtime.InteropServices; @@ -11,6 +11,7 @@ using Interop.Windows.Kernel32; using Microsoft.Win32; using Microsoft.Data.SqlClient.Internal; +using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient.ManagedSni { @@ -44,8 +45,16 @@ [Out] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder sqlConnectionDataSource, private LocalDB() { } - internal static string GetLocalDBConnectionString(string localDbInstance) => - Instance.LoadUserInstanceDll() ? Instance.GetConnectionString(localDbInstance) : null; + internal static string GetLocalDBConnectionString(string localDbInstance) + { + // LocalDB is not available for Unix and hence it cannot be supported. + if (!OsConstants.IsWindows) + { + throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); + } + + return Instance.LoadUserInstanceDll() ? Instance.GetConnectionString(localDbInstance) : null; + } internal static IntPtr GetProcAddress(string functionName) => Instance.LoadUserInstanceDll() ? Kernel32.GetProcAddress(LocalDB.Instance._sqlUserInstanceLibraryHandle, functionName) : IntPtr.Zero; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.unix.cs deleted file mode 100644 index 64ba1da0f8..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.unix.cs +++ /dev/null @@ -1,20 +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. - -#if NET && _UNIX - -using System; - -namespace Microsoft.Data.SqlClient.ManagedSni -{ - internal class LocalDB - { - internal static string GetLocalDBConnectionString(string localDbInstance) - { - throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); // LocalDB is not available for Unix and hence it cannot be supported. - } - } -} - -#endif 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 a101099dd8..0e9df041de 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -20,9 +20,7 @@ using System.Runtime.Remoting; using System.Runtime.Serialization; using System.Security.Permissions; -#if _WINDOWS using Interop.Windows.Sni; -#endif using Microsoft.Data.SqlClient.LocalDb; #endif From 45f000ab8ce30c31e50d3250d2238feea18cc27f Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 21 Jul 2026 07:39:26 -0300 Subject: [PATCH 197/298] Stress | Move to ci/stress and build against sqlclient-ci-package output (#4456) --- .../ado-pipelines.instructions.md | 4 +- .../prompts/update-build-pipelines.prompt.md | 4 +- .../stress/sqlclient-ci-stress-job.yml} | 102 +++++++++++++++--- .../stress/sqlclient-ci-stress-pipeline.yml} | 65 ++++------- .../stress/sqlclient-ci-stress-stage.yml} | 16 +-- .../sqlclient-pr-package-ref-pipeline.yml | 1 - .../sqlclient-pr-project-ref-pipeline.yml | 1 - .../StressTests/Directory.Packages.props | 12 +++ .../SqlClient.Stress.Framework.csproj | 15 ++- .../SqlClient.Stress.Runner.csproj | 8 +- .../SqlClient.Stress.Tests.csproj | 8 +- 11 files changed, 156 insertions(+), 80 deletions(-) rename eng/pipelines/{stress/stress-tests-job.yml => ci/stress/sqlclient-ci-stress-job.yml} (64%) rename eng/pipelines/{stress/stress-tests-pipeline.yml => ci/stress/sqlclient-ci-stress-pipeline.yml} (59%) rename eng/pipelines/{stress/stress-tests-stage.yml => ci/stress/sqlclient-ci-stress-stage.yml} (89%) diff --git a/.github/instructions/ado-pipelines.instructions.md b/.github/instructions/ado-pipelines.instructions.md index 7e0d02e4e2..3e5a66af78 100644 --- a/.github/instructions/ado-pipelines.instructions.md +++ b/.github/instructions/ado-pipelines.instructions.md @@ -22,7 +22,7 @@ Top-level CI/PR pipeline files: - `dotnet-sqlclient-ci-project-reference-pipeline.yml` — CI with Project references (Release) - `sqlclient-pr-package-ref-pipeline.yml` — PR validation with Package references - `sqlclient-pr-project-ref-pipeline.yml` — PR validation with Project references -- `stress/stress-tests-pipeline.yml` — Stress test pipeline and templates +- `ci/stress/sqlclient-ci-stress-pipeline.yml` — Stress test pipeline and templates Reusable templates are organized under: - `common/templates/jobs/` — Job templates (`ci-build-nugets-job`, `ci-code-coverage-job`, `ci-run-tests-job`) @@ -63,7 +63,7 @@ Stages execute in dependency order (Package reference mode requires artifacts fr 7. `verify_nuget_packages_stage` — Verify NuGet package metadata 8. `ci_run_tests_stage` — Run MDS and AKV test suites -Stress testing is no longer a stage threaded through `dotnet-sqlclient-ci-core.yml`; it lives under `eng/pipelines/stress/` as a separate pipeline flow. +Stress testing is no longer a stage threaded through `dotnet-sqlclient-ci-core.yml`; it lives under `eng/pipelines/ci/stress/` as a separate pipeline flow. When adding a new build stage, respect the dependency graph and pass artifact names/versions to downstream stages. diff --git a/.github/prompts/update-build-pipelines.prompt.md b/.github/prompts/update-build-pipelines.prompt.md index 63c87ea9c3..a652c9f82f 100644 --- a/.github/prompts/update-build-pipelines.prompt.md +++ b/.github/prompts/update-build-pipelines.prompt.md @@ -22,7 +22,7 @@ Follow this workflow step-by-step: - `sqlclient-pr-package-ref-pipeline.yml` — PR validation (package references) - `onebranch/sqlclient-official.yml` — official OneBranch build/release pipeline - `onebranch/sqlclient-non-official.yml` — non-official OneBranch build/release pipeline - - `stress/stress-tests-pipeline.yml` — stress test pipeline + - `ci/stress/sqlclient-ci-stress-pipeline.yml` — stress test pipeline - Shared templates live in `eng/pipelines/common/templates/` (jobs/, stages/, steps/). - CI variables are defined in `eng/pipelines/libraries/`; OneBranch variables are defined in `eng/pipelines/onebranch/variables/`. @@ -57,7 +57,7 @@ Follow this workflow step-by-step: - Test filters by platform: `nonnetfxtests`, `nonnetcoreapptests`, `nonwindowstests`, `nonlinuxtests`. - SNI testing matrix: both Native (`useManagedSNI=false`) and Managed (`useManagedSNI=true`). - Always Encrypted tests controlled by `runAlwaysEncryptedTests` parameter. -- Stress coverage is maintained under `eng/pipelines/stress/`, not as a stage inside `dotnet-sqlclient-ci-core.yml`. +- Stress coverage is maintained under `eng/pipelines/ci/stress/`, not as a stage inside `dotnet-sqlclient-ci-core.yml`. ## 5. Validate - Verify YAML syntax is valid. diff --git a/eng/pipelines/stress/stress-tests-job.yml b/eng/pipelines/ci/stress/sqlclient-ci-stress-job.yml similarity index 64% rename from eng/pipelines/stress/stress-tests-job.yml rename to eng/pipelines/ci/stress/sqlclient-ci-stress-job.yml index dd75602d6f..f4d730c05b 100644 --- a/eng/pipelines/stress/stress-tests-job.yml +++ b/eng/pipelines/ci/stress/sqlclient-ci-stress-job.yml @@ -3,7 +3,10 @@ # file to you under the MIT license. See the LICENSE file in the project root for more information. #################################################################################################### -# This job builds and runs stress tests by compiling the SqlClient projects transitively. +# This job builds and runs stress tests against the SqlClient NuGet packages produced by the +# sqlclient-ci-package pipeline. The package artifacts are downloaded into the local NuGet feed +# (packages/) and the stress test projects are built in "Package" mode (ReferenceType=Package) +# against the exact package versions produced by that pipeline. # # The stress tests are located here: # @@ -59,10 +62,6 @@ parameters: - name: netTestRuntimes type: object - # The name of the Azure Pipelines pool to use. - - name: poolName - type: string - # The local SQL Server instance's 'sa' password, for use in the config file. - name: saPassword type: string @@ -72,23 +71,26 @@ parameters: - name: sqlSetupStep type: step - # The pool VM image to use. + # The pool VM image to use. Both the internal (ADO-1ES-Pool) and public (ADO-CI-1ES-Pool) pools + # must provide an image with this name. + # + # NOTE: This value is evaluated at template-expansion (compile) time to select the pool, so it + # must be a literal and must not contain any runtime expressions (e.g. $(...) macros or + # $[...] runtime expressions). - name: vmImage type: string jobs: - job: stress_tests_job_${{ parameters.jobNameSuffix }} displayName: '[${{ parameters.displayNamePrefix }}] Run Stress Tests' - pool: - name: ${{ parameters.poolName }} - ${{ if eq(parameters.poolName, 'Azure Pipelines') }}: - vmImage: ${{ parameters.vmImage }} - ${{ else }}: - demands: - - imageOverride -equals ${{ parameters.vmImage }} variables: + # Whether this is an internal (ADO.Net project) or public (Public project) build. Evaluated + # at compile time so it can drive template expressions below. + - name: isInternalBuild + value: ${{ eq(variables['System.TeamProject'], 'ADO.Net') }} + # Import the variable group that provides SQL Server build properties used by the # shared configure-sql-server-*-step.yml templates (e.g. x64AliasRegistryPath, # x86AliasRegistryPath, SQLAliasName, SQLAliasPort). @@ -97,6 +99,11 @@ jobs: # Import the variable group that provides SQL Server test configuration variables. - group: ADO Test Configuration Properties + # The directory where the "- download:" step places the SqlClient-Driver-Packages artifact + # published by the triggering sqlclient-ci-package pipeline. + - name: packageArtifactDir + value: $(Pipeline.Workspace)/sqlclient-ci-package/SqlClient-Driver-Packages + # The top-level project file to build and run. - name: project value: $(Build.SourcesDirectory)/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj @@ -128,11 +135,22 @@ jobs: # bypassing the "--" separator. This causes app arguments to contain SDK options and triggers # unintended behavior. + # Reference-mode arguments shared by build and run. The stress tests are built in "Package" + # mode against the SqlClient packages downloaded from the sqlclient-ci-package pipeline. The + # exact versions are resolved at runtime from the downloaded .nupkg filenames (see the + # "Stage Packages and Resolve Versions" step). + - name: referenceArgs + value: >- + -p:ReferenceType=Package + -p:SqlClientPackageVersion=$(mdsPackageVersion) + -p:AzurePackageVersion=$(azurePackageVersion) + # dotnet CLI options for build. - name: dotnetBuildOpts value: >- --verbosity ${{ parameters.dotnetVerbosity }} -p:Configuration=${{ parameters.buildConfiguration }} + $(referenceArgs) # dotnet run options shared by all test steps (framework is appended per-step). - name: dotnetRunOpts @@ -140,11 +158,27 @@ jobs: --no-build --verbosity ${{ parameters.dotnetVerbosity }} --configuration ${{ parameters.buildConfiguration }} + $(referenceArgs) # Stress test options passed after the "--" separator. - name: stressTestOpts value: --assembly SqlClient.Stress.Tests --console + # Select the pool based on the requested image: + # - macOS images run on the Microsoft-hosted 'Azure Pipelines' pool (no 1ES macOS images). + # - All other images run on the 1ES pool for the current ADO project, matched via imageOverride. + pool: + ${{ if startsWith(parameters.vmImage, 'macos') }}: + name: 'Azure Pipelines' + vmImage: ${{ parameters.vmImage }} + ${{ else }}: + ${{ if eq(variables.isInternalBuild, true) }}: + name: ADO-1ES-Pool + ${{ else }}: + name: ADO-CI-1ES-Pool + demands: + - imageOverride -equals ${{ parameters.vmImage }} + steps: # Install the .NET SDK and Runtimes. @@ -170,9 +204,47 @@ jobs: # Write the JSON content to the config file. $content | Out-File -FilePath "config.jsonc" + # Download the SqlClient driver packages published by the triggering sqlclient-ci-package + # pipeline into the pipeline workspace. + - download: sqlclient-ci-package + artifact: SqlClient-Driver-Packages + displayName: Download SqlClient Driver Packages + + # Copy the downloaded packages into the local NuGet feed (packages/) and resolve the exact + # Microsoft.Data.SqlClient and Microsoft.Data.SqlClient.Extensions.Azure versions from the + # .nupkg filenames. These versions are passed to the build via the referenceArgs variable. + - task: PowerShell@2 + displayName: Stage Packages and Resolve Versions + inputs: + pwsh: true + targetType: inline + script: | + $ErrorActionPreference = 'Stop' + $feed = "$(Build.SourcesDirectory)/packages" + New-Item -ItemType Directory -Force -Path $feed | Out-Null + + Copy-Item "$(packageArtifactDir)/*.nupkg" $feed -Force + Copy-Item "$(packageArtifactDir)/*.snupkg" $feed -Force -ErrorAction SilentlyContinue + + $mdsPattern = '^Microsoft\.Data\.SqlClient\.(\d[^\\/]*)\.nupkg$' + $azurePattern = '^Microsoft\.Data\.SqlClient\.Extensions\.Azure\.(\d[^\\/]*)\.nupkg$' + + $mds = Get-ChildItem "$feed/*.nupkg" | Where-Object { $_.Name -match $mdsPattern } | Select-Object -First 1 + $azure = Get-ChildItem "$feed/*.nupkg" | Where-Object { $_.Name -match $azurePattern } | Select-Object -First 1 + + if (-not $mds) { throw "Microsoft.Data.SqlClient package not found in $feed" } + if (-not $azure) { throw "Microsoft.Data.SqlClient.Extensions.Azure package not found in $feed" } + + $mdsVersion = [regex]::Match($mds.Name, $mdsPattern).Groups[1].Value + $azureVersion = [regex]::Match($azure.Name, $azurePattern).Groups[1].Value + + Write-Host "Resolved Microsoft.Data.SqlClient version: $mdsVersion" + Write-Host "Resolved Microsoft.Data.SqlClient.Extensions.Azure version: $azureVersion" + Write-Host "##vso[task.setvariable variable=mdsPackageVersion]$mdsVersion" + Write-Host "##vso[task.setvariable variable=azurePackageVersion]$azureVersion" + # Authenticate with NuGet feeds so that upstream packages (e.g. runtime host packs) can be - # fetched through the ADO Artifacts feed. This is required on hosted pool agents (macOS) - # that do not have pre-configured feed credentials. + # fetched through the ADO Artifacts feed. - task: NuGetAuthenticate@1 displayName: Authenticate NuGet feeds diff --git a/eng/pipelines/stress/stress-tests-pipeline.yml b/eng/pipelines/ci/stress/sqlclient-ci-stress-pipeline.yml similarity index 59% rename from eng/pipelines/stress/stress-tests-pipeline.yml rename to eng/pipelines/ci/stress/sqlclient-ci-stress-pipeline.yml index c1eb87faab..74283a8128 100644 --- a/eng/pipelines/stress/stress-tests-pipeline.yml +++ b/eng/pipelines/ci/stress/sqlclient-ci-stress-pipeline.yml @@ -4,17 +4,15 @@ #################################################################################################### # This pipeline runs the stress test suite against the SqlClient projects, building them -# transitively as necessary, triggered by successful runs of the following pipelines: +# transitively as necessary, triggered by successful runs of the sqlclient-ci-package pipeline: # # Public project: -# Triggering pipelines: PR-SqlClient-Project, CI-SqlClient (branch main only) -# Pipeline name: sqlclient-stress -# Pipeline URL: https://sqlclientdrivers.visualstudio.com/public/_build?definitionId=2250 +# Triggering pipeline: sqlclient-ci-package +# Pipeline name: sqlclient-ci-stress # # ADO.Net project: -# Triggering pipeline: MDS Main CI (branch internal/main only) -# Pipeline name: sqlclient-stress -# Pipeline URL: https://dev.azure.com/SqlClientDrivers/ADO.Net/_build?definitionId=2284 +# Triggering pipeline: sqlclient-ci-package +# Pipeline name: sqlclient-ci-stress # Set the pipeline run name to the day-of-year and the daily run counter. name: $(DayOfYear)$(Rev:rr) @@ -23,7 +21,7 @@ name: $(DayOfYear)$(Rev:rr) pr: none trigger: none -# Trigger this pipeline after successful runs of the desired pipelines. +# Trigger this pipeline after successful runs of the sqlclient-ci-package pipeline. # # The pipeline identifiers are displayed in the Azure DevOps UI, so it is helpful if they indicate # the project, folder, and pipeline name, hence the verbose values below. @@ -31,30 +29,23 @@ trigger: none resources: pipelines: - # The PR-SqlClient-Project pipeline in the Public project. - - pipeline: Public-ADO-PR-SqlClient-Project - project: Public - source: /ADO/PR-SqlClient-Project + # Trigger this pipeline when the sqlclient-ci-package pipeline completes successfully. + # + # 'project' is omitted so the resource resolves to the sqlclient-ci-package pipeline in the + # *current* ADO project. This scopes the trigger per project automatically: the Public + # registration is triggered only by Public's sqlclient-ci-package, and the ADO.Net registration + # only by ADO.Net's. + # + # IMPORTANT: 'source' is the bare pipeline name with no folder path. This REQUIRES the + # sqlclient-ci-package name to be UNIQUE within BOTH the Public and ADO.Net projects. Azure + # DevOps only allows omitting the folder when the name is unambiguous; if a second pipeline with + # this name is ever added to either project, this resource will fail to resolve and the folder + # path must be added back (which, because the folders differ per project, would then require a + # per-project approach instead of this single shared definition). + - pipeline: sqlclient-ci-package + source: sqlclient-ci-package trigger: true - # The CI-SqlClient pipeline in the Public project. - - pipeline: Public-ADO-CI-SqlClient - project: Public - source: /ADO/CI-SqlClient - trigger: - branches: - include: - - main - - # The MDS Main CI pipeline in the ADO.Net project. - - pipeline: ADO-Net-Internal-CI-MDS-Main-CI - project: ADO.Net - source: /CI/MDS Main CI - trigger: - branches: - include: - - internal/main - # Pipeline parameters, visible in the Azure DevOps UI. parameters: @@ -92,17 +83,6 @@ parameters: - detailed - diagnostic -# Pipeline-level variables evaluated at compile time. -variables: - # The 1ES pool name, determined automatically by ADO project: - # - ADO.Net -> ADO-1ES-Pool - # - public -> ADO-CI-1ES-Pool - - name: poolName - ${{ if eq(variables['System.TeamProject'], 'ADO.Net') }}: - value: ADO-1ES-Pool - ${{ else }}: - value: ADO-CI-1ES-Pool - # The stages to run. stages: @@ -112,10 +92,9 @@ stages: debug: ${{ parameters.debug }} # Run the stress tests. - - template: /eng/pipelines/stress/stress-tests-stage.yml@self + - template: /eng/pipelines/ci/stress/sqlclient-ci-stress-stage.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} - poolName: ${{ variables.poolName }} warnOnTestFailure: ${{ parameters.warnOnTestFailure }} dotnetVerbosity: ${{ parameters.dotnetVerbosity }} diff --git a/eng/pipelines/stress/stress-tests-stage.yml b/eng/pipelines/ci/stress/sqlclient-ci-stress-stage.yml similarity index 89% rename from eng/pipelines/stress/stress-tests-stage.yml rename to eng/pipelines/ci/stress/sqlclient-ci-stress-stage.yml index 1f223c5142..689d1a702b 100644 --- a/eng/pipelines/stress/stress-tests-stage.yml +++ b/eng/pipelines/ci/stress/sqlclient-ci-stress-stage.yml @@ -57,10 +57,6 @@ parameters: type: object default: [net8.0, net9.0, net10.0] - # The 1ES pool name for Linux and Windows jobs. macOS always uses 'Azure Pipelines'. - - name: poolName - type: string - stages: - stage: stress_tests_stage displayName: Run Stress Tests @@ -77,7 +73,7 @@ stages: # ---------------------------------------------------------------------------------------------- # Build and test on Linux. - - template: /eng/pipelines/stress/stress-tests-job.yml@self + - template: /eng/pipelines/ci/stress/sqlclient-ci-stress-job.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} @@ -88,7 +84,6 @@ stages: # No .NET Framework runtimes on Linux. netFrameworkTestRuntimes: [] netTestRuntimes: ${{ parameters.netTestRuntimes }} - poolName: ${{ parameters.poolName }} saPassword: $(saPassword) sqlSetupStep: template: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml@self @@ -99,7 +94,7 @@ stages: # ---------------------------------------------------------------------------------------------- # Build and test on Windows - - template: /eng/pipelines/stress/stress-tests-job.yml@self + - template: /eng/pipelines/ci/stress/sqlclient-ci-stress-job.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} @@ -110,7 +105,6 @@ stages: # Note that we include the .NET Framework runtimes for test runs on Windows. netFrameworkTestRuntimes: ${{ parameters.netFrameworkTestRuntimes }} netTestRuntimes: ${{ parameters.netTestRuntimes }} - poolName: ${{ parameters.poolName }} saPassword: $(saPassword) sqlSetupStep: template: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml@self @@ -123,7 +117,7 @@ stages: # ---------------------------------------------------------------------------------------------- # Build and test on macOS. - - template: /eng/pipelines/stress/stress-tests-job.yml@self + - template: /eng/pipelines/ci/stress/sqlclient-ci-stress-job.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} @@ -134,12 +128,10 @@ stages: # No .NET Framework runtimes on macOS. netFrameworkTestRuntimes: [] netTestRuntimes: ${{ parameters.netTestRuntimes }} - # We don't have any 1ES Hosted Pool images for macOS, so we use a generic one from Azure - # Pipelines. - poolName: Azure Pipelines saPassword: $(saPassword) sqlSetupStep: template: /eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml@self parameters: saPassword: $(saPassword) + # A macOS vmImage name routes this job to the Microsoft-hosted 'Azure Pipelines' pool. vmImage: macos-latest diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index 70a37e0fa7..695145e990 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -54,7 +54,6 @@ pr: - eng/pipelines/ci/* - eng/pipelines/kerberos/* - eng/pipelines/onebranch/* - - eng/pipelines/stress/* # Do not trigger commit or schedule runs for this pipeline. trigger: none diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index f777626c90..14c1af4e7f 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -54,7 +54,6 @@ pr: - eng/pipelines/ci/* - eng/pipelines/kerberos/* - eng/pipelines/onebranch/* - - eng/pipelines/stress/* # Do not trigger commit or schedule runs for this pipeline. trigger: none diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props index addc60556f..4c180661aa 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props @@ -7,10 +7,22 @@ true true + + + + Project + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj index 6feb8f34d4..e02f3e79d2 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj @@ -5,8 +5,19 @@ - - + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj index 8a2bc08b4c..d6e81b457c 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Runner/SqlClient.Stress.Runner.csproj @@ -10,7 +10,13 @@ - + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClient.Stress.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClient.Stress.Tests.csproj index 90cdaf6e2b..15eb436add 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClient.Stress.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Tests/SqlClient.Stress.Tests.csproj @@ -5,7 +5,13 @@ - + + + + + From 8e5bf0007e2454f4c388eee7944d4f00e0ceffcf Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:13:13 -0300 Subject: [PATCH 198/298] Quarantine new flaky tests (#4453) * Issue 46480: GitHub #4379: Tests | Failing tests that need to marked flaky - Quarantining more flaky tests. * Quarantining more new flaky tests. * More new flaky tests. * Yet another new flaky test. * Another flaky test. * Quarantine new flaky tests and tidy quarantine comment (PR #4453) - Quarantine ConnectionFailoverTests.NetworkTimeout_ShouldFail (CI Assert.Equal timing flake). - Quarantine ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async (post-login connection timeout flake). - Remove unrelated ConnectionFailoverTests.TransientFault_ShouldConnectToPrimary failure line from the Shutdown_UnblocksSyncWaiter quarantine comment. * Harden macOS/Windows SQL Server CI setup against transient failures (PR #4453) - macOS: retry 'docker pull' of the mssql image (5x, 15s backoff) to tolerate mcr.microsoft.com DNS/registry timeouts, and verify 'docker run' launched the container. - Windows: retry the FileStream sp_configure (10x, 5s sleep) to tolerate transient post-restart 'session is in the kill state' severe errors. * Another flaky tests has been revealed! * Another flaky test. * Addressed review feedback. * Fix Invoke-Sqlcmd retry loops to catch non-terminating errors (PR #4453) Add -ErrorAction Stop to the Invoke-Sqlcmd calls inside the try/catch retry loops in configure-sql-server-win-step.yml. Without it, non-terminating errors bypass the catch block and defeat the retry logic. Applied to all three retry loops (Create SQL user, Restart SQL Server, Configure FileStream Access Level). Addresses review feedback on PR #4453. * Quarantine flaky NetworkDelay_RetryDisabled unit test NetworkDelay_RetryDisabled is timing-sensitive: it uses a 1000ms TransientDelayTdsServer with ConnectTimeout=5s and expects Open() to succeed. Under CI load the post-login phase can exceed the 5s budget (observed [Post-Login] complete=4004ms), causing a Connection Timeout Expired SqlException. Mark it [Trait("Category", "flaky")] so the CI category!=flaky filter excludes it, and record the observed failure stack trace as a comment for follow-up. Relates to PR #4453 (Quarantine new flaky tests). * Fix macOS SQL Server setup: point docker CLI at Colima socket The standalone docker CLI (brew install docker) defaulted to unix:///var/run/docker.sock, which does not exist on macOS without Docker Desktop, so every 'docker pull' failed instantly with 'failed to connect to the docker API'. Export DOCKER_HOST to Colima's socket (~/.colima/default/docker.sock), fail fast if 'colima start' fails, and verify the daemon is reachable via 'docker info' before pulling. Relates to PR #4453 (Quarantine new flaky tests). * Quarantine flaky ConnectionTest unit test ConnectionTest intermittently fails with InvalidOperationException 'The SQL Server instance returned an invalid or unsupported protocol version during login negotiation', thrown from BlockingPeriodErrorState.ThrowIfActive(). This is cross-test contamination of the shared connection pool: a prior simulated-server test's cached login error is re-thrown by this test's Open(). Mark it [Trait("Category", "flaky")] and record the observed stack trace. Relates to PR #4453 (Quarantine new flaky tests). * Fix connection-pool poisoning in simulated-server tests; unquarantine ConnectionTest Root-cause fix replacing the earlier ConnectionTest quarantine (3cebfbd8c). A failing pooled Open() against a non-Azure endpoint (localhost) enters a 5-second BlockingPeriodErrorState that is keyed by connection string. When a later test's ephemeral TdsServer reuses the same port, it inherits the cached exception via ThrowIfActive() and fails without contacting the new server. ConnectionTest was thus failing with a cached InvalidTDSVersion error seeded by ConnectionRefusesUnsupportedServerTdsVersion. Add Pooling=false to the failure-injecting tests so their expected failures do not poison a shared pool: - ConnectionTests: RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail, TransientFault_RetryDisabled_ShouldFail(+_Async), ConnectionTimeoutTest(+Async), ConnectionRefusesUnsupportedServerTdsVersion, TestConnWithVectorFeatExtVersionNegotiation - ConnectionRoutingTests / ConnectionRoutingTestsAzure: TransientFaultAtRoutedLocation_RetryDisabled_ShouldFail - ConnectionReadOnlyRoutingTests: RecursivelyRouted{,Async}Connection (ConnectionRoutingLimit) Remove the [Trait("Category","flaky")] from ConnectionTest: it was a poisoning victim, not a genuine flake, and is deterministic once the source is fixed. Genuine timing-race quarantines are left in place. Relates to PR #4453 (Quarantine new flaky tests). * Retry Colima start on macOS SQL Server setup The Lima hostagent that backs Colima intermittently fails to become ready within its short startup window on the macOS CI runners ("hostagent did not start up in 5s"), which is transient. Retry 'colima start' up to 3 times, dumping ha.stderr.log and tearing down the half-created instance (colima delete --force) between attempts before giving up. Relates to PR #4453 (Quarantine new flaky tests). --- .../steps/configure-sql-server-macos-step.yml | 82 ++++++++- .../steps/configure-sql-server-win-step.yml | 29 +++- .../WaitHandleDbConnectionPoolShutdownTest.cs | 7 + .../ConnectionFailoverTests.cs | 156 +++++++++++++++--- .../ConnectionReadOnlyRoutingTests.cs | 19 ++- .../ConnectionRoutingTests.cs | 12 ++ .../ConnectionRoutingTestsAzure.cs | 1 + .../SimulatedServerTests/ConnectionTests.cs | 131 ++++++++++++++- 8 files changed, 398 insertions(+), 39 deletions(-) diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml index 6e1541f939..89b5e9f738 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml @@ -52,14 +52,90 @@ steps: # Colima >= 0.8 when using --vm-type vz, which is dramatically faster than # --arch x86_64 (full QEMU VM emulation). # Requires macOS >= 13 (Ventura). - colima start --vm-type vz --cpu 4 --memory 4 + # + # The Lima hostagent that backs Colima intermittently fails to become + # ready within its short startup window on the macOS CI runners + # ("hostagent did not start up in 5s"), which is transient. Retry a few + # times, tearing down the half-created instance between attempts and + # dumping the hostagent log for diagnostics, before giving up. + colimaAttempts=3 + colimaStarted=0 + for ((c=1; c<=colimaAttempts; c++)) + do + echo "Starting Colima (attempt #$c of $colimaAttempts)..." + if colima start --vm-type vz --cpu 4 --memory 4; then + colimaStarted=1 + break + fi + echo "colima start failed (attempt #$c of $colimaAttempts)." + echo "--- hostagent stderr (last 40 lines) ---" + tail -40 "$HOME/.colima/_lima/colima/ha.stderr.log" 2>/dev/null || true + echo "--- Tearing down half-created instance before retry ---" + colima delete --force || true + sleep 5 + done + + if [ $colimaStarted -ne 1 ]; then + echo "ERROR: colima failed to start after $colimaAttempts attempts." + colima status || true + exit 1 + fi + + # Point the docker CLI at Colima's daemon socket. Colima normally sets an + # active docker context, but the standalone docker CLI (installed above via + # 'brew install docker') can default to unix:///var/run/docker.sock, which + # does not exist on macOS without Docker Desktop. This caused every + # 'docker pull' to fail instantly with: + # failed to connect to the docker API at unix:///var/run/docker.sock + # Export DOCKER_HOST explicitly so all docker commands reach Colima. + export DOCKER_HOST="unix://${HOME}/.colima/default/docker.sock" docker --version - docker pull --platform linux/amd64 mcr.microsoft.com/mssql/server:2025-latest + + # Verify the docker daemon is actually reachable before attempting to pull. + # This fails fast with useful diagnostics instead of looping through the + # pull retries when the socket is misconfigured. + if ! docker info >/dev/null 2>&1; then + echo "ERROR: docker daemon is not reachable at $DOCKER_HOST." + echo "--- colima status ---" + colima status || true + echo "--- docker context ls ---" + docker context ls || true + exit 1 + fi + + # Pull the SQL Server image with retries. The macOS agents intermittently + # fail to resolve or reach mcr.microsoft.com (DNS lookups or registry i/o + # time out), which previously failed the whole step on the first attempt. + # Retry a handful of times with a fixed delay before giving up. + pullAttempts=5 + pullDelay=15 + pulled=0 + for ((p=1; p<=pullAttempts; p++)) + do + echo "Pulling SQL Server image (attempt #$p of $pullAttempts)..." + if docker pull --platform linux/amd64 mcr.microsoft.com/mssql/server:2025-latest; then + pulled=1 + break + fi + echo "docker pull failed (attempt #$p of $pullAttempts). Retrying in ${pullDelay}s..." + sleep $pullDelay + done + + if [ $pulled -ne 1 ]; then + echo "ERROR: Unable to pull mcr.microsoft.com/mssql/server:2025-latest after $pullAttempts attempts (registry/DNS unreachable)." + exit 1 + fi # Password for the SA user (required) MSSQL_SA_PW="${{ parameters.saPassword }}" - docker run --platform linux/amd64 -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:2025-latest + # Start the container and fail fast (with diagnostics) if it does not launch, + # rather than silently falling through to the connection-wait loop. + if ! docker run --platform linux/amd64 -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:2025-latest; then + echo "ERROR: Failed to start the sql1 container." + docker ps -a --filter "name=^/sql1$" + exit 1 + fi sleep 10 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 2e58e5013c..5659d45de2 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 @@ -147,7 +147,7 @@ steps: while ($true) { $tries++ try { - Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @" + Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 -ErrorAction Stop @" CREATE LOGIN [${{parameters.user }}] WITH PASSWORD=N'$password', DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF; CREATE USER [${{parameters.user }}] FROM LOGIN [${{parameters.user }}]; @@ -293,7 +293,7 @@ steps: while ($true) { $tries++ try { - Invoke-Sqlcmd -ServerInstance "$machineName" -Query "SELECT @@VERSION" -ConnectionTimeout 5 + Invoke-Sqlcmd -ServerInstance "$machineName" -Query "SELECT @@VERSION" -ConnectionTimeout 5 -ErrorAction Stop Write-Host "SQL Server is ready after restart (attempt $tries)." break } catch { @@ -320,11 +320,26 @@ steps: Set-ExecutionPolicy Unrestricted Import-Module "sqlps" - Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 @" - EXEC sp_configure filestream_access_level, 2; - RECONFIGURE; - "@ - Write-Host "FileStream access level configured successfully." + # Immediately after the restart SQL Server can still be stabilizing, which + # can surface transient errors such as "the session is in the kill state" + # or other severe command errors. Retry a few times before failing so a + # momentary hiccup does not fail the whole step. + $tries = 0 + while ($true) { + $tries++ + try { + Invoke-Sqlcmd -ServerInstance "$machineName" -ConnectionTimeout 5 -Query "EXEC sp_configure filestream_access_level, 2; RECONFIGURE;" -ErrorAction Stop + Write-Host "FileStream access level configured successfully (attempt $tries)." + break + } catch { + if ($tries -ge 10) { + Write-Host "##[error]Failed to configure FileStream access level after $tries attempts." + throw + } + Write-Host "FileStream configuration failed (attempt $tries/10): $($_.Exception.Message). Retrying..." + Start-Sleep -Seconds 5 + } + } displayName: 'Configure FileStream Access Level [Win]' # Step 9: Start the SQL Server Browser service so that named instances can be diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs index 7268728c8b..4ec6370d88 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolShutdownTest.cs @@ -161,6 +161,13 @@ public void TryGetConnection_Async_AfterShutdown_ShortCircuits_NoPendingOpenSche } // Shutdown wakes up a thread parked in WaitHandle.WaitAny. + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.ConnectionPool.WaitHandleDbConnectionPoolShutdownTest.Shutdown_UnblocksSyncWaiter [5 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Waiter did not park within 5s. + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.ConnectionPool.WaitHandleDbConnectionPoolShutdownTest.Shutdown_UnblocksSyncWaiter() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\ConnectionPool\WaitHandleDbConnectionPoolShutdownTest.cs:line 207 [Fact] public void Shutdown_UnblocksSyncWaiter() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index 566d3d0cef..c86633c25e 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -132,8 +132,8 @@ public void NetworkError_TriggersFailover_ClearsPool() using SqlConnection connection = new(builder.ConnectionString); /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : 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 + Microsoft.Data.SqlClient.SqlException : 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: 0 - The wait operation timed out.) ---- System.ComponentModel.Win32Exception : The wait operation timed out. */ @@ -173,6 +173,17 @@ to allow remote connections. (provider: TCP Provider, error: 0 - The wait operat Assert.Equal(2, failoverServer.PreLoginCount); } + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkTimeout_ShouldFail [1 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Assert.Equal() Failure: Values differ + // Expected: 1 + // Actual: 0 + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkTimeout_ShouldFail() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 220 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) [Fact] public void NetworkTimeout_ShouldFail() { @@ -221,6 +232,42 @@ public void NetworkTimeout_ShouldFail() Assert.Equal(0, failoverServer.PreLoginCount); } + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkDelay_ShouldConnectToPrimary [4 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=0; handshake=17; [Login] initialization=0; authentication=0; [Post-Login] complete=3999; + // ---- System.ComponentModel.Win32Exception : The wait operation timed out. + // Stack Trace: + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1241 + // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1721 + // at Microsoft.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1243 + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 4168 + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3581 + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3480 + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1464 + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1780 + // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2456 + // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2343 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2208 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2170 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3214 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionOptions connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3793 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, TimeoutTimer timeout, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 399 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 687 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 124 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionInternal oldConnection, TimeoutTimer timeout, Boolean forceNewConnection, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 417 + // at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, Boolean forceNewConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\ProviderBase\DbConnectionInternal.cs:line 940 + // at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\ProviderBase\DbConnectionClosed.cs:line 69 + // at Microsoft.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry, Boolean forceNewConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 2287 + // at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, Boolean forceNewConnection, SqlConnectionOverrides overrides) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 2245 + // at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1619 + // at Microsoft.Data.SqlClient.SqlConnection.Open() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1596 + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkDelay_ShouldConnectToPrimary() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 260 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args) + // at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) + // ----- Inner Stack Trace ----- [Fact] public void NetworkDelay_ShouldConnectToPrimary() { @@ -305,11 +352,11 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa // Act /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. - The connection could have timed out while waiting for server to complete the login process and respond; - Or it could have timed out while attempting to create multiple active connections. - This failure occurred while attempting to connect to the Principle server. - The duration spent while attempting to connect to this server was - [Pre-Login] initialization=6; handshake=80; + Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. + The connection could have timed out while waiting for server to complete the login process and respond; + Or it could have timed out while attempting to create multiple active connections. + This failure occurred while attempting to connect to the Principle server. + The duration spent while attempting to connect to this server was - [Pre-Login] initialization=6; handshake=80; [Login] initialization=0; authentication=0; [Post-Login] complete=5443; ---- System.ComponentModel.Win32Exception : The wait operation timed out */ @@ -368,21 +415,21 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai using SqlConnection connection = new(builder.ConnectionString); // Act /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. - The timeout period elapsed during the post-login phase. - The connection could have timed out while waiting for server to complete the login process and respond; - Or it could have timed out while attempting to create multiple active connections. - This failure occurred while attempting to connect to the Principle server. - The duration spent while attempting to connect to this server was - [Pre-Login] initialization=4; handshake=68; - [Login] initialization=0; authentication=0; [Post-Login] complete=5443; + Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. + The timeout period elapsed during the post-login phase. + The connection could have timed out while waiting for server to complete the login process and respond; + Or it could have timed out while attempting to create multiple active connections. + This failure occurred while attempting to connect to the Principle server. + The duration spent while attempting to connect to this server was - [Pre-Login] initialization=4; handshake=68; + [Login] initialization=0; authentication=0; [Post-Login] complete=5443; ---- System.ComponentModel.Win32Exception : The wait operation timed out */ /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. - The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement. - This could be because the pre-login handshake failed or the server was unable to respond back in time. - This failure occurred while attempting to connect to the Principle server. - The duration spent while attempting to connect to this server was - [Pre-Login] initialization=0; handshake=5285; + Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. + The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement. + This could be because the pre-login handshake failed or the server was unable to respond back in time. + This failure occurred while attempting to connect to the Principle server. + The duration spent while attempting to connect to this server was - [Pre-Login] initialization=0; handshake=5285; ---- System.ComponentModel.Win32Exception : The wait operation timed out. */ connection.Open(); @@ -406,6 +453,15 @@ This could be because the pre-login handshake failed or the server was unable to /// Verifies login-phase transient SQL errors are retried on the primary endpoint and /// do not trigger failover-partner alternation. /// + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_ShouldConnectToPrimary(errorCode: 42109) [6 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Assert.Equal() Failure: Values differ + // Expected: 2 + // Actual: 3 + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_ShouldConnectToPrimary(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 451 [Theory] [InlineData(40613)] [InlineData(42108)] @@ -565,7 +621,14 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e Assert.Equal(0, failoverServer.PreLoginCount); } - [Trait("Category", "flaky")] // [errorcode: 42109] Assert.Fail() Failure + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail(errorCode: 42109) [2 s] + // EXEC : error Message: [/Users/runner/work/1/s/build2.proj] + // Assert.Fail() Failure + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail(UInt32 errorCode) in /Users/runner/work/1/s/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs:line 526 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Theory] [InlineData(40613)] [InlineData(42108)] @@ -656,8 +719,8 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // Connect once to the primary to trigger it to send the failover partner /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : 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. + Microsoft.Data.SqlClient.SqlException : 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: 0 - The wait operation timed out.) ---- System.ComponentModel.Win32Exception : The wait operation timed out. */ @@ -756,6 +819,18 @@ public async Task TransientFault_Async_ShouldConnectToPrimary_NotFailover(uint e /// Async parity with user-provided partner: login-phase transient SQL errors should /// still retry on primary without failover alternation. /// + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Async_ShouldConnectToPrimary_NotFailover(errorCode: 42109) [4 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Assert.Equal() Failure: Strings differ + // Γåô (pos 14) + // Expected: "localhost,60625" + // Actual: "localhost,60624" + // Γåæ (pos 14) + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Async_ShouldConnectToPrimary_NotFailover(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 816 + // --- End of stack trace from previous location --- [Theory] [InlineData(40613)] [InlineData(42108)] @@ -807,6 +882,19 @@ public async Task TransientFault_WithUserProvidedPartner_Async_ShouldConnectToPr /// Verifies pooled connections are not cleared and failover is not attempted when a /// login-phase transient SQL error occurs with a user-provided failover partner. /// + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(errorCode: 40613) [2 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Assert.Equal() Failure: Strings differ + // Γåô (pos 14) + // Expected: "localhost,50152" + // Actual: "localhost,50151" + // Γåæ (pos 14) + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 920 + // at InvokeStub_ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(Object, Span`1) + // at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) [Theory] [InlineData(40613)] [InlineData(42108)] @@ -932,6 +1020,17 @@ public void TransientFault_RetryDisabled_WithUserProvidedPartner_ShouldFail_NotF /// Isolates the parser-state guard by using a non-fatal login error token: without the /// guard, LoginWithFailover alternates to partner; with the guard, retry stays on primary. /// + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrimary_NotFailover [1 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [/mnt/vss/_work/1/s/build.proj] + // Assert.Equal() Failure: Values differ + // Expected: 0 + // Actual: 1 + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrimary_NotFailover() in /mnt/vss/_work/1/s/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs:line 1006 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) [Fact] public void NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrimary_NotFailover() { @@ -982,6 +1081,19 @@ public void NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrima /// Verifies opt-in legacy behavior: login-phase SQL errors can alternate to the /// failover partner when UseLegacyFailoverAlternationOnLoginSqlErrors is enabled. /// + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NonFatalTransientLoginError_WithLegacySwitch_ShouldAlternateToFailoverPartner [2 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Assert.Equal() Failure: Strings differ + // Γåô (pos 14) + // Expected: "localhost,60991" + // Actual: "localhost,60992" + // Γåæ (pos 14) + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NonFatalTransientLoginError_WithLegacySwitch_ShouldAlternateToFailoverPartner() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 1114 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) [Fact] public void NonFatalTransientLoginError_WithLegacySwitch_ShouldAlternateToFailoverPartner() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionReadOnlyRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionReadOnlyRoutingTests.cs index 8220871f22..4675066a12 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionReadOnlyRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionReadOnlyRoutingTests.cs @@ -80,7 +80,15 @@ public void RecursivelyRoutedConnection(int layers) }).ConnectionString; } - SqlConnectionStringBuilder builder = new(lastConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly }; + SqlConnectionStringBuilder builder = new(lastConnectionString) + { + ApplicationIntent = ApplicationIntent.ReadOnly, + Pooling = false, + // The whole routing chain must complete within a single Connect Timeout + // budget. Allow ample time so the many-hop chain rides out thread-pool + // congestion under parallel CI runs instead of tripping a timeout. + ConnectTimeout = 60 + }; using SqlConnection connection = new(builder.ConnectionString); connection.Open(); } @@ -125,7 +133,14 @@ public async Task RecursivelyRoutedAsyncConnection(int layers) SqlConnectionStringBuilder builder = new(lastConnectionString) { ApplicationIntent = ApplicationIntent.ReadOnly, - Encrypt = false + Encrypt = false, + Pooling = false, + // The async non-pooled open runs the entire routing chain in a single + // thread-pool-scheduled continuation, so it must finish within one Connect + // Timeout budget. The default 15s can be exceeded under CI thread-pool + // congestion (12 in-process TdsServers contend for the same starved pool), + // producing a flaky NonPooledOpenTimeout. Allow ample time to ride it out. + ConnectTimeout = 60 }; using SqlConnection connection = new(builder.ConnectionString); await connection.OpenAsync(); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs index 3065f780bc..c5bd6edf57 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs @@ -96,6 +96,7 @@ public void TransientFaultAtRoutedLocation_RetryDisabled_ShouldFail(uint errorCo ConnectRetryInterval = 1, ConnectRetryCount = 0, // Disable retry Encrypt = false, + Pooling = false, // Disable pooling so this expected failure does not poison a shared pool }; using SqlConnection connection = new(builder.ConnectionString); @@ -103,6 +104,17 @@ public void TransientFaultAtRoutedLocation_RetryDisabled_ShouldFail(uint errorCo Assert.Throws(() => connection.Open()); } + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionRoutingTests.NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed(multiSubnetFailoverEnabled: True) [2 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build2.proj] + // Assert.Equal() Failure: Values differ + // Expected: 1 + // Actual: 2 + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionRoutingTests.NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed(Boolean multiSubnetFailoverEnabled) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionRoutingTests.cs:line 147 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Theory] [InlineData(true)] [InlineData(false)] diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs index 642d54f5b2..f75514dae2 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs @@ -113,6 +113,7 @@ public void TransientFaultAtRoutedLocation_RetryDisabled_ShouldFail(uint errorCo ConnectRetryInterval = 1, ConnectRetryCount = 0, // Disable retry Encrypt = false, + Pooling = false, // Disable pooling so this expected failure does not poison a shared pool }; using SqlConnection connection = new(builder.ConnectionString); Assert.Throws(() => connection.Open()); diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index ef058c881a..10cd111a16 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -67,7 +67,8 @@ public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() server.Start(); var connStr = new SqlConnectionStringBuilder() { - DataSource = $"localhost,{server.EndPoint.Port}" + DataSource = $"localhost,{server.EndPoint.Port}", + Pooling = false, // Disable pooling so this expected failure does not poison a shared pool }.ConnectionString; using SqlConnection connection = new(connStr); @@ -75,6 +76,31 @@ public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() Assert.Contains("The instance of SQL Server you attempted to connect to does not support encryption.", ex.Message, StringComparison.OrdinalIgnoreCase); } + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed_Async(errorCode: 40613) [6 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Microsoft.Data.SqlClient.SqlException : + // Stack Trace: + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1303 + // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1739 + // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 3139 + // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2362 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2259 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2223 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3266 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3836 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 443 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 697 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreatePooledConnection(DbConnection owningConnection, IDbConnectionPool pool, DbConnectionPoolKey poolKey, DbConnectionOptions options, DbConnectionOptions userOptions) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 150 + // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 528 + // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 1544 + // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 987 + // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.WaitForPendingOpen() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 834 + // --- End of stack trace from previous location --- + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed_Async(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionTests.cs:line 101 + // --- End of stack trace from previous location --- + [Theory] [InlineData(40613)] [InlineData(42108)] @@ -104,6 +130,37 @@ public async Task TransientFault_RetryEnabled_ShouldSucceed_Async(uint errorCode Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed(errorCode: 40613) [5 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Microsoft.Data.SqlClient.SqlException : + // Stack Trace: + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1241 + // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1721 + // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 3119 + // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2343 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2208 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2170 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3214 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionOptions connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3793 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, TimeoutTimer timeout, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 399 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 687 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreatePooledConnection(DbConnection owningConnection, IDbConnectionPool pool, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 148 + // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionInternal oldConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 535 + // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.UserCreateRequest(DbConnection owningObject, TimeoutTimer timeout, DbConnectionInternal oldConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 1648 + // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, TimeoutTimer timeout, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 1026 + // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 taskCompletionSource, TimeoutTimer timeout, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 879 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionInternal oldConnection, TimeoutTimer timeout, Boolean forceNewConnection, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 431 + // at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, Boolean forceNewConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\ProviderBase\DbConnectionInternal.cs:line 940 + // at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\ProviderBase\DbConnectionClosed.cs:line 69 + // at Microsoft.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry, Boolean forceNewConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 2287 + // at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, Boolean forceNewConnection, SqlConnectionOverrides overrides) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 2245 + // at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1619 + // at Microsoft.Data.SqlClient.SqlConnection.Open() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1596 + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionTests.cs:line 152 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Theory] [InlineData(40613)] [InlineData(42108)] @@ -147,7 +204,8 @@ public async Task TransientFault_RetryDisabled_ShouldFail_Async(uint errorCode) { DataSource = "localhost," + server.EndPoint.Port, ConnectRetryCount = 0, - Encrypt = SqlConnectionEncryptOption.Optional + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // Disable pooling so this expected failure does not poison a shared pool }; using SqlConnection connection = new(builder.ConnectionString); @@ -174,7 +232,8 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) { DataSource = "localhost," + server.EndPoint.Port, ConnectRetryCount = 0, - Encrypt = SqlConnectionEncryptOption.Optional + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // Disable pooling so this expected failure does not poison a shared pool }; using SqlConnection connection = new(builder.ConnectionString); @@ -184,6 +243,40 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async(multiSubnetFailoverEnabled: True) [4 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=5; handshake=43; [Login] initialization=0; authentication=0; [Post-Login] complete=4838; + // ---- System.ComponentModel.Win32Exception : The wait operation timed out. + // Stack Trace: + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1241 + // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1721 + // at Microsoft.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1243 + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 4168 + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3581 + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3480 + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1464 + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1780 + // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2456 + // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2343 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2208 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2170 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3214 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionOptions connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3793 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, TimeoutTimer timeout, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 399 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 687 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 124 + // at Microsoft.Data.SqlClient.SqlConnectionFactory.<>c__DisplayClass41_0.b__0(Task`1 _) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 777 + // at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke() + // at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) + // --- End of stack trace from previous location --- + // at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) + // at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) + // --- End of stack trace from previous location --- + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async(Boolean multiSubnetFailoverEnabled) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionTests.cs:line 237 + // --- End of stack trace from previous location --- + // ----- Inner Stack Trace ----- [Theory] [InlineData(false)] [InlineData(true)] @@ -266,6 +359,21 @@ public async Task NetworkDelay_RetryDisabled_Async(bool multiSubnetFailoverEnabl } } + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkDelay_RetryDisabled(multiSubnetFailoverEnabled: False) [4 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=0; handshake=25; [Login] initialization=0; authentication=0; [Post-Login] complete=4004; + // ---- System.ComponentModel.Win32Exception : The wait operation timed out. + // Stack Trace: + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1241 + // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1721 + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 4168 + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3581 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2208 + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3214 + // at Microsoft.Data.SqlClient.SqlConnection.Open() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1596 + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkDelay_RetryDisabled(Boolean multiSubnetFailoverEnabled) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionTests.cs:line 387 [Theory] [InlineData(true)] [InlineData(false)] @@ -458,6 +566,15 @@ public void ConnectionTestValidCredentialCombination() } + [Trait("Category", "flaky")] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.ConnectionTimeoutTest(timeout: 60) [10 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [/mnt/vss/_work/1/s/build.proj] + // Timer must be stopped. + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.ConnectionTimeoutTest(Int32 timeout) in /mnt/vss/_work/1/s/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs:line 523 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Theory] [InlineData(60)] [InlineData(10)] @@ -472,7 +589,8 @@ public void ConnectionTimeoutTest(int timeout) { DataSource = $"localhost,{server.EndPoint.Port}", ConnectTimeout = timeout, - Encrypt = SqlConnectionEncryptOption.Optional + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // Disable pooling so this expected timeout failure does not poison a shared pool }.ConnectionString; using SqlConnection connection = new(connStr); @@ -516,7 +634,8 @@ public async Task ConnectionTimeoutTestAsync(int timeout) { DataSource = $"localhost,{server.EndPoint.Port}", ConnectTimeout = timeout, - Encrypt = SqlConnectionEncryptOption.Optional + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // Disable pooling so this expected timeout failure does not poison a shared pool }.ConnectionString; using SqlConnection connection = new(connStr); @@ -734,6 +853,7 @@ public void ConnectionRefusesUnsupportedServerTdsVersion(int major, int minor, i { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // Disable pooling so this expected failure does not poison a shared pool }.ConnectionString; using SqlConnection conn = new(connStr); @@ -812,6 +932,7 @@ public void TestConnWithVectorFeatExtVersionNegotiation(bool expectedConnectionR { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // Disable pooling so an expected failure does not poison a shared pool }.ConnectionString; using var connection = new SqlConnection(connStr); if (expectedConnectionResult) From a35f80f405bf54baaa0c15583d6b15caaf751a5b Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:20:37 -0300 Subject: [PATCH 199/298] Run legacy CI pipelines daily instead of weekdays/Thursdays (#4469) --- ...t-sqlclient-ci-package-reference-pipeline.yml | 16 ++++++++-------- ...t-sqlclient-ci-project-reference-pipeline.yml | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index 98cac1bcdd..72bcf4a120 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -16,8 +16,8 @@ # # - Commits to GitHub main and release/* branches # - Commits to ADO internal/main and internal/release/* branches -# - Weekdays at 03:00 UTC on GitHub main -# - Thursdays at 07:00 UTC on ADO internal/main +# - Daily at 03:00 UTC on GitHub main +# - Daily at 07:00 UTC on ADO internal/main # # GOTCHA: This pipeline definition is triggered by GitHub _and_ ADO CI. We are # able to define different triggers and schedules using branch filters: @@ -66,17 +66,17 @@ trigger: # Trigger this pipeline on a schedule. schedules: - # GitHub main on weekdays - - cron: '0 3 * * Mon-Fri' - displayName: Weekday Run (Release Config) + # GitHub main daily. + - cron: '0 3 * * *' + displayName: Daily Run (Release Config) branches: include: - main always: true - # ADO internal/main on Thursdays. - - cron: '0 7 * * Thu' - displayName: Thursday Run (Release Config) + # ADO internal/main daily. + - cron: '0 7 * * *' + displayName: Daily Run (Release Config) branches: include: - internal/main diff --git a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml index 7068c9693c..7b665d52c3 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml @@ -16,8 +16,8 @@ # # - Commits to GitHub main and release/* branches # - Commits to ADO internal/main and internal/release/* branches -# - Weekdays at 01:00 UTC on GitHub main -# - Thursdays at 05:00 UTC on ADO internal/main +# - Daily at 01:00 UTC on GitHub main +# - Daily at 05:00 UTC on ADO internal/main # # GOTCHA: This pipeline definition is triggered by GitHub _and_ ADO CI. We are # able to define different triggers and schedules using branch filters: @@ -66,17 +66,17 @@ trigger: # Trigger this pipeline on a schedule. schedules: - # GitHub main on weekdays - - cron: '0 1 * * Mon-Fri' - displayName: Weekday Run (Release Config) + # GitHub main daily. + - cron: '0 1 * * *' + displayName: Daily Run (Release Config) branches: include: - main always: true - # ADO internal/main on Thursdays. - - cron: '0 5 * * Thu' - displayName: Thursday Run (Release Config) + # ADO internal/main daily. + - cron: '0 5 * * *' + displayName: Daily Run (Release Config) branches: include: - internal/main From 188c71ed0d4422ab4141f1079443fd909ebfcee7 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:55:24 -0300 Subject: [PATCH 200/298] Move Kerberos pipeline to ci/ and rename to sqlclient-ci-kerberos-pipeline.yml (#4468) --- .../{ => ci}/kerberos/build-and-test-steps.yml | 0 eng/pipelines/{ => ci}/kerberos/linux-cleanup-step.yml | 0 eng/pipelines/{ => ci}/kerberos/linux-init-step.yml | 0 .../kerberos/sqlclient-ci-kerberos-pipeline.yml} | 10 +++++----- eng/pipelines/sqlclient-pr-package-ref-pipeline.yml | 1 - eng/pipelines/sqlclient-pr-project-ref-pipeline.yml | 1 - 6 files changed, 5 insertions(+), 7 deletions(-) rename eng/pipelines/{ => ci}/kerberos/build-and-test-steps.yml (100%) rename eng/pipelines/{ => ci}/kerberos/linux-cleanup-step.yml (100%) rename eng/pipelines/{ => ci}/kerberos/linux-init-step.yml (100%) rename eng/pipelines/{kerberos/sqlclient-kerberos.yml => ci/kerberos/sqlclient-ci-kerberos-pipeline.yml} (97%) diff --git a/eng/pipelines/kerberos/build-and-test-steps.yml b/eng/pipelines/ci/kerberos/build-and-test-steps.yml similarity index 100% rename from eng/pipelines/kerberos/build-and-test-steps.yml rename to eng/pipelines/ci/kerberos/build-and-test-steps.yml diff --git a/eng/pipelines/kerberos/linux-cleanup-step.yml b/eng/pipelines/ci/kerberos/linux-cleanup-step.yml similarity index 100% rename from eng/pipelines/kerberos/linux-cleanup-step.yml rename to eng/pipelines/ci/kerberos/linux-cleanup-step.yml diff --git a/eng/pipelines/kerberos/linux-init-step.yml b/eng/pipelines/ci/kerberos/linux-init-step.yml similarity index 100% rename from eng/pipelines/kerberos/linux-init-step.yml rename to eng/pipelines/ci/kerberos/linux-init-step.yml diff --git a/eng/pipelines/kerberos/sqlclient-kerberos.yml b/eng/pipelines/ci/kerberos/sqlclient-ci-kerberos-pipeline.yml similarity index 97% rename from eng/pipelines/kerberos/sqlclient-kerberos.yml rename to eng/pipelines/ci/kerberos/sqlclient-ci-kerberos-pipeline.yml index a8c4463c13..b8c28c604d 100644 --- a/eng/pipelines/kerberos/sqlclient-kerberos.yml +++ b/eng/pipelines/ci/kerberos/sqlclient-ci-kerberos-pipeline.yml @@ -5,7 +5,7 @@ ################################################################################# # ============================================================================= -# sqlclient-kerberos +# sqlclient-ci-kerberos-pipeline # ============================================================================= # Daily Kerberos authentication test pipeline for Microsoft.Data.SqlClient. # Replaces the Classic "Test-SqlClient-Kerberos-Azure" pipeline. @@ -180,7 +180,7 @@ stages: displayName: Enable Network DTC Access # --- Build and test --- - - template: /eng/pipelines/kerberos/build-and-test-steps.yml@self + - template: /eng/pipelines/ci/kerberos/build-and-test-steps.yml@self parameters: buildTarget: BuildSqlClientWindows testFramework: $(testFramework) @@ -252,7 +252,7 @@ stages: KERBEROS_DOMAIN_PASSWORD: $(KerberosDomainPassword) # --- Kerberos domain join --- - - template: /eng/pipelines/kerberos/linux-init-step.yml@self + - template: /eng/pipelines/ci/kerberos/linux-init-step.yml@self parameters: kerberosDomain: $(KerberosDomain) kerberosDomainOU: $(KerberosDomainOU) @@ -269,7 +269,7 @@ stages: REMOTE_TCP_CONN_STRING: $(REMOTE_TCP_CONN_STRING) # --- Build and test --- - - template: /eng/pipelines/kerberos/build-and-test-steps.yml@self + - template: /eng/pipelines/ci/kerberos/build-and-test-steps.yml@self parameters: buildTarget: BuildSqlClientUnix testFramework: $(testFramework) @@ -277,7 +277,7 @@ stages: artifactName: $(testFramework)-linux-$(System.JobId) # --- Kerberos cleanup (always runs) --- - - template: /eng/pipelines/kerberos/linux-cleanup-step.yml@self + - template: /eng/pipelines/ci/kerberos/linux-cleanup-step.yml@self parameters: kerberosDomain: $(KerberosDomain) kerberosDomainUser: $(KerberosDomainUser) diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index 695145e990..1a58842305 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -52,7 +52,6 @@ pr: - NuGet.config exclude: - eng/pipelines/ci/* - - eng/pipelines/kerberos/* - eng/pipelines/onebranch/* # Do not trigger commit or schedule runs for this pipeline. diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index 14c1af4e7f..6e54d0eb32 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -52,7 +52,6 @@ pr: - NuGet.config exclude: - eng/pipelines/ci/* - - eng/pipelines/kerberos/* - eng/pipelines/onebranch/* # Do not trigger commit or schedule runs for this pipeline. From d0407001a1f672d5352907de0b0dd15325076f25 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:29:45 +0100 Subject: [PATCH 201/298] Remove platform-specific builds (#4465) --- .../Interop/Windows/Sni/ISniNativeMethods.cs | 6 +- .../Windows/Sni/SniNativeMethods.netcore.cs | 4 +- .../Sni/SniNativeMethodsArm64.netfx.cs | 4 +- .../Sni/SniNativeMethodsNotSupported.netfx.cs | 2 +- .../Windows/Sni/SniNativeMethodsX64.netfx.cs | 4 +- .../Windows/Sni/SniNativeMethodsX86.netfx.cs | 4 +- .../Interop/Windows/Sni/SniNativeWrapper.cs | 4 -- .../src/Microsoft.Data.SqlClient.csproj | 9 +-- .../Data/Sql/SqlDataSourceEnumerator.cs | 6 -- ...=> SqlDataSourceEnumeratorNativeHelper.cs} | 4 -- .../DbConnectionPoolIdentity.cs | 12 ---- .../{LocalDbApi.windows.cs => LocalDbApi.cs} | 17 ++++-- .../Data/SqlClient/LocalDb/LocalDbApi.unix.cs | 21 ------- ...ore.windows.cs => PacketHandle.netcore.cs} | 3 +- .../SqlClient/PacketHandle.netcore.unix.cs | 57 ------------------- ...indows.cs => NativeSspiContextProvider.cs} | 6 +- ...re.windows.cs => SessionHandle.netcore.cs} | 3 +- .../SqlClient/SessionHandle.netcore.unix.cs | 43 -------------- ...les.windows.cs => TdsParserSafeHandles.cs} | 4 -- ...dows.cs => TdsParserStateObjectFactory.cs} | 6 +- .../TdsParserStateObjectFactory.unix.cs | 36 ------------ ...ndows.cs => TdsParserStateObjectNative.cs} | 4 -- 22 files changed, 28 insertions(+), 231 deletions(-) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/{SqlDataSourceEnumeratorNativeHelper.windows.cs => SqlDataSourceEnumeratorNativeHelper.cs} (99%) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/{LocalDbApi.windows.cs => LocalDbApi.cs} (97%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.unix.cs rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{PacketHandle.netcore.windows.cs => PacketHandle.netcore.cs} (96%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.unix.cs rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/{NativeSspiContextProvider.windows.cs => NativeSspiContextProvider.cs} (96%) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{SessionHandle.netcore.windows.cs => SessionHandle.netcore.cs} (94%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.unix.cs rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{TdsParserSafeHandles.windows.cs => TdsParserSafeHandles.cs} (99%) rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{TdsParserStateObjectFactory.windows.cs => TdsParserStateObjectFactory.cs} (96%) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.unix.cs rename src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/{TdsParserStateObjectNative.windows.cs => TdsParserStateObjectNative.cs} (99%) diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ISniNativeMethods.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ISniNativeMethods.cs index 194e61a4b6..7c4ee2486a 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ISniNativeMethods.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ISniNativeMethods.cs @@ -1,9 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 _WINDOWS - using System; using System.Runtime.InteropServices; using System.Text; @@ -101,5 +99,3 @@ unsafe uint SniSecGenClientContextWrapper( uint SniWriteSyncOverAsync(SNIHandle pConn, SNIPacket pPacket); } } - -#endif 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 index 32109770e0..dc78405f41 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethods.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethods.netcore.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 && _WINDOWS +#if NET using System; using System.Runtime.InteropServices; 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 index c39c1dbefa..a450e706ad 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsArm64.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsArm64.netfx.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 && _WINDOWS +#if NETFRAMEWORK using System; using System.Runtime.InteropServices; 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 index 0d8600f193..7139ee0b08 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsNotSupported.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsNotSupported.netfx.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. -#if NETFRAMEWORK && _WINDOWS +#if NETFRAMEWORK using System; using System.Runtime.InteropServices; 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 index 0849e1a22d..293e0432c5 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX64.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX64.netfx.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 && _WINDOWS +#if NETFRAMEWORK using System; using System.Runtime.InteropServices; 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 index 270a522aa6..187acb3fe7 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX86.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX86.netfx.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 && _WINDOWS +#if NETFRAMEWORK using System; using System.Runtime.InteropServices; diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeWrapper.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeWrapper.cs index 62961901ff..5d4ba1e83a 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeWrapper.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeWrapper.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. - #if _WINDOWS - using System; using System.Buffers; using System.Diagnostics; @@ -452,5 +450,3 @@ private static void MarshalConsumerInfo(ConsumerInfo consumerInfo, ref SniConsum #endregion } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index dc327b024a..7194f0e8db 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -19,11 +19,6 @@ $(TargetOs.ToLower()) - - - - $(DefineConstants);_UNIX - $(DefineConstants);_WINDOWS @@ -231,11 +226,11 @@ - + + Condition="'$(TargetFramework)' != 'net462'"> ILLink.Substitutions.xml diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs index 0356d7bcd3..7777e558b9 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs @@ -27,16 +27,10 @@ public override DataTable GetDataSources() #if NETFRAMEWORK return SqlDataSourceEnumeratorNativeHelper.GetDataSources(); #else - - #if _UNIX - return SqlDataSourceEnumeratorManagedHelper.GetDataSources(); - #else return SqlClient.LocalAppContextSwitches.UseManagedNetworking ? SqlDataSourceEnumeratorManagedHelper.GetDataSources() : SqlDataSourceEnumeratorNativeHelper.GetDataSources(); #endif - - #endif } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs index a6325d9990..d7a2a0f832 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.Data; using System.Diagnostics; @@ -175,5 +173,3 @@ private static DataTable ParseServerEnumString(string serverInstances) } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs index 320e59fb48..940a10271f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs @@ -54,19 +54,9 @@ public override int GetHashCode() internal static DbConnectionPoolIdentity GetCurrent() { - #if NETFRAMEWORK - return GetCurrentNative(); - #else - - #if _UNIX - return GetCurrentManaged(); - #else return LocalAppContextSwitches.UseManagedNetworking ? GetCurrentManaged() : GetCurrentNative(); - #endif - - #endif } #if NETFRAMEWORK @@ -94,7 +84,6 @@ private static DbConnectionPoolIdentity GetCurrentManaged() return current; } - #if _WINDOWS private static DbConnectionPoolIdentity GetCurrentNative() { DbConnectionPoolIdentity current; @@ -124,7 +113,6 @@ private static DbConnectionPoolIdentity GetCurrentNative() s_lastIdentity = current; return current; } - #endif } } 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.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.cs index c0674ca6db..da1e613c44 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.Diagnostics; using System.Globalization; @@ -276,7 +274,12 @@ internal static void DemandLocalDbPermissions() // localDB can also have a format of np:\\.\pipe\LOCALDB#\tsql\query internal static string GetLocalDbInstanceNameFromServerName(string serverName) { - if (serverName is not null) + // LocalDB is only supported on Windows + if (!OsConstants.IsWindows) + { + return null; + } + else if (serverName is not null) { // it can start with spaces if specified in quotes // Memory allocation is reduced by using ReadOnlySpan @@ -300,6 +303,12 @@ internal static string GetLocalDbInstanceNameFromServerName(string serverName) internal static string GetLocalDbMessage(int hrCode) { + if (!OsConstants.IsWindows) + { + // LocalDB is not available for Unix and hence it cannot be supported. + throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); + } + Debug.Assert(hrCode < 0, "HRCode does not indicate error"); try { @@ -422,5 +431,3 @@ internal InstanceInfo(string version) #endif } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.unix.cs deleted file mode 100644 index b016c7dc97..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.unix.cs +++ /dev/null @@ -1,21 +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. - -#if _UNIX - -using System; - -namespace Microsoft.Data.SqlClient.LocalDb -{ - internal static class LocalDbApi - { - 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. - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.cs similarity index 96% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.cs index 4e675fc5b0..4bce65d8f3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // @TODO: Introduce polymorphism to remove need for this level of indirection -#if NET && _WINDOWS +#if NET using System; @@ -18,7 +18,6 @@ namespace Microsoft.Data.SqlClient /// /// /// It is a ref struct so that it can only be used to transport the handles and not store them. - /// If you change this type you must also change the version for the other platform. /// internal readonly ref struct PacketHandle { 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 deleted file mode 100644 index 4060edfb38..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.unix.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. - -// @TODO: Introduce polymorphism to remove need for this level of indirection -// @TODO: Also, why do we have any other type besides managed defined here? -#if NET && _UNIX - -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. - /// 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; - - // @TODO: To auto-properties - 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/SSPI/NativeSspiContextProvider.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs similarity index 96% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs index b6d65d4452..94e7652874 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs @@ -1,9 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 _WINDOWS - using System; using System.Buffers; using System.Diagnostics; @@ -85,5 +83,3 @@ protected override bool GenerateContext(ReadOnlySpan incomingBlob, IBuffer } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.cs similarity index 94% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.cs index dcac01c534..019bed388b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. // @TODO: Merge with other implementations (and/or introduce polymorphism to handle this indirection) -#if NET && _WINDOWS +#if NET namespace Microsoft.Data.SqlClient { @@ -15,7 +15,6 @@ namespace Microsoft.Data.SqlClient /// /// /// It is a ref struct so that it can only be used to transport the handles and not store them. - /// If you change this type you must also change the version for the other platform. /// internal readonly ref struct SessionHandle { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.unix.cs deleted file mode 100644 index 8f82506bc5..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.unix.cs +++ /dev/null @@ -1,43 +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. - -// @TODO: Merge with SessionHandle.windows (and/or introduce polymorphism to handle this indirection) -// @TODO: Also, why do we have native handle type defined in Unix which doesn't ever have a native handle type? -#if NET && _UNIX - -namespace Microsoft.Data.SqlClient -{ - /// - /// This structure is used for transporting packet handle references between the - /// TdsParserStateObject base class and Managed or Native implementations. 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. - /// If you change this type you must also change the version for the other platform. - /// - internal readonly ref struct SessionHandle - { - public const int NativeHandleType = 1; - public const int ManagedHandleType = 2; - - // @TODO: Auto-properties - public readonly ManagedSni.SniHandle ManagedHandle; - public readonly int Type; - - public SessionHandle(ManagedSni.SniHandle managedHandle, int type) - { - Type = type; - ManagedHandle = managedHandle; - } - - public bool IsNull => ManagedHandle is null; - - public static SessionHandle FromManagedSession(ManagedSni.SniHandle managedSessionHandle) => - new SessionHandle(managedSessionHandle, ManagedHandleType); - } -} - -#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.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs index 8242dd997f..2f591294d4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.Diagnostics; using System.Runtime.InteropServices; @@ -280,5 +278,3 @@ protected override bool ReleaseHandle() } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.cs similarity index 96% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.cs index a54873cb21..abe66c38a5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.cs @@ -1,9 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 _WINDOWS - #if NET using Microsoft.Data.SqlClient.ManagedSni; using Microsoft.Data.SqlClient.Internal; @@ -72,5 +70,3 @@ internal TdsParserStateObject CreateSessionObject(TdsParser tdsParser, TdsParser } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.unix.cs deleted file mode 100644 index 61817eb0ed..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.unix.cs +++ /dev/null @@ -1,36 +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. - -#if NET && _UNIX - -using Microsoft.Data.SqlClient.ManagedSni; - -namespace Microsoft.Data.SqlClient -{ - internal sealed class TdsParserStateObjectFactory - { - public static readonly TdsParserStateObjectFactory Singleton = new TdsParserStateObjectFactory(); - - public EncryptionOptions EncryptionOptions => ManagedSni.SniLoadHandle.Options; - - public uint SNIStatus => ManagedSni.SniLoadHandle.Status; - - /// - /// Verify client encryption possibility. - /// - public bool ClientOSEncryptionSupport => ManagedSni.SniLoadHandle.ClientOSEncryptionSupport; - - public TdsParserStateObject CreateTdsParserStateObject(TdsParser parser) - { - return new TdsParserStateObjectManaged(parser); - } - - internal TdsParserStateObject CreateSessionObject(TdsParser tdsParser, TdsParserStateObject _pMarsPhysicalConObj, bool v) - { - return new TdsParserStateObjectManaged(tdsParser, _pMarsPhysicalConObj, true); - } - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index 7812860b5e..a57a46658f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if _WINDOWS - using System; using System.Collections.Generic; using System.Diagnostics; @@ -532,5 +530,3 @@ public void Dispose() } } } - -#endif From 9f33378cca7ab7c25ca5bea0f0f007c8d9633dd6 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Thu, 23 Jul 2026 14:38:11 -0500 Subject: [PATCH 202/298] Tests | Remove DataAttribute Subclasses (#4427) * DataAttribute => TheoryData for SqlColumnEncryptionCspProviderShould.cs * DataAttribute => TheoryData for SqlColumnEncryptionCertificateStoreProviderShould.cs * Remove DataAttribute subclasses from SqlColumnEncryptionCertificateStoreProviderShould.cs and SqlColumnEncryptionCngProviderShould.cs * Address copilot comments about unused usings. --- ...ncryptionCertificateStoreProviderShould.cs | 312 +++++++++--------- .../SqlColumnEncryptionCngProviderShould.cs | 148 +++++---- .../SqlColumnEncryptionCspProviderShould.cs | 163 ++++----- 3 files changed, 323 insertions(+), 300 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs index 8e835c14fb..66823fc892 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs @@ -5,14 +5,11 @@ using Microsoft.Data.SqlClient.Tests.Common.Fixtures; using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; -using System.Security.Principal; using System.Text; using Xunit; -using Xunit.Sdk; using static Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests.TestFixtures; namespace Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests @@ -105,9 +102,68 @@ public SqlColumnEncryptionCertificateStoreProviderShould(ColumnEncryptionCertifi SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled = false; } + public static TheoryData InvalidDecryption_Data + { + get + { + const string TCE_NullCertificatePath_Windows = @"Internal error. Certificate path cannot be null. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullCertificatePath_Unix = @"Internal error. Certificate path cannot be null. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCertificatePath_Windows = @"Internal error. Invalid certificate path: ''. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCertificatePath_Unix = @"Internal error. Invalid certificate path: ''. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullEncryptedColumnEncryptionKey = @"Internal error. Encrypted column encryption key cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_EmptyEncryptedColumnEncryptionKey = @"Internal error. Empty encrypted column encryption key specified.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_NullKeyEncryptionAlgorithm = @"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidKeyEncryptionAlgorithm = @"Internal error. Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_LargeCertificatePathLength = @"Internal error. Specified certificate path has 32768 bytes, which exceeds maximum length of 32767 bytes.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificatePath_Windows = @"Internal error. Invalid certificate path: 'CurrentUser/My/Thumbprint/extra'. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificatePath_Unix = @"Internal error. Invalid certificate path: 'CurrentUser/My/Thumbprint/extra'. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificateLocation_Windows = @"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('\))?"; + const string TCE_InvalidCertificateLocation_Unix = @"Internal error. Invalid certificate location 'Invalid' in certificate path 'Invalid/My/Thumbprint'. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificateStore = @"Internal error. Invalid certificate store 'Invalid' specified in certificate path 'CurrentUser/Invalid/Thumbprint'. Expected value: 'My'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + 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('\))?"; + 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('\))?"; + const string TCE_InvalidCertificateSignature = @"Internal error. Empty certificate thumbprint specified in certificate path 'CurrentUser/My/'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidAlgorithmVersion = @"Specified encrypted column encryption key contains an invalid encryption algorithm version '02'. Expected version is '01'.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + 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('\))?"; + 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('\))?"; + 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('\))?"; + + string TCE_NullCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_NullCertificatePath_Windows : TCE_NullCertificatePath_Unix; + string TCE_EmptyCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_EmptyCertificatePath_Windows : TCE_EmptyCertificatePath_Unix; + string TCE_InvalidCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_InvalidCertificatePath_Windows : TCE_InvalidCertificatePath_Unix; + string TCE_InvalidCertificateLocation = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_InvalidCertificateLocation_Windows : TCE_InvalidCertificateLocation_Unix; + + TheoryData result = new(); + result.Add(TCE_NullCertificatePath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCertificatePath, typeof(ArgumentException), "", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, null); + result.Add(TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, []); + result.Add(TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), PRIMARY_CERTIFICATE_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_LargeCertificatePathLength, typeof(ArgumentException), GenerateString(Int16.MaxValue + 1), ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCertificatePath, typeof(ArgumentException), "CurrentUser/My/Thumbprint/extra", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCertificateLocation, typeof(ArgumentException), "Invalid/My/Thumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCertificateStore, typeof(ArgumentException), "CurrentUser/Invalid/Thumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_CertificateNotFound, typeof(ArgumentException), "CurrentUser/My/JunkThumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_CertificateWithNoPrivateKey, typeof(ArgumentException), "CurrentUser/My/{npk_thumbprint}", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCertificateSignature, typeof(ArgumentException), "CurrentUser/My/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidAlgorithmVersion, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(2, 0, 256, 256)); + result.Add(TCE_InvalidCiphertextLengthInEncryptedCEK, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 128, 256)); + result.Add(TCE_InvalidSignatureInEncryptedCEK, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 128)); + result.Add(TCE_InvalidSignature, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + + return result; + } + } + [Theory] - [InvalidDecryptionParameters] - public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey(string errorMsg, Type exceptionType, string masterKeyPath, string encryptionAlgorithm, byte[] bytes) + [MemberData(nameof(InvalidDecryption_Data))] + public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey( + string errorMsg, + Type exceptionType, + string masterKeyPath, + string encryptionAlgorithm, + byte[] bytes) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); Exception ex = Assert.Throws(exceptionType, @@ -115,8 +171,54 @@ public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey Assert.Matches(ReplaceKeyTokens(errorMsg), ex.Message); } + public static TheoryData InvalidEncryption_Data + { + get + { + const string TCE_NullCertificatePath_Windows = @"Certificate path cannot be null. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullCertificatePath_Unix = @"Certificate path cannot be null. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCertificatePath_Windows = @"Invalid certificate path: ''. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCertificatePath_Unix = @"Invalid certificate path: ''. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullEncryptedColumnEncryptionKey = @"Column encryption key cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; + const string TCE_EmptyEncryptedColumnEncryptionKey = @"Empty column encryption key specified.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; + const string TCE_NullKeyEncryptionAlgorithm = @"Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidKeyEncryptionAlgorithm = @"Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_LargeCertificatePathLength = @"Specified certificate path has 32768 bytes, which exceeds maximum length of 32767 bytes.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificatePath_Windows = @"Invalid certificate path: 'CurrentUser/My/Thumbprint/extra'. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificatePath_Unix = @"Invalid certificate path: 'CurrentUser/My/Thumbprint/extra'. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificateLocation_Windows = @"Invalid certificate location 'Invalid' in certificate path 'Invalid/My/Thumbprint'. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificateLocation_Unix = @"Invalid certificate location 'Invalid' in certificate path 'Invalid/My/Thumbprint'. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCertificateStore = @"Invalid certificate store 'Invalid' specified in certificate path 'CurrentUser/Invalid/Thumbprint'. Expected value: 'My'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_CertificateNotFound = @"Certificate with thumbprint 'JunkThumbprint' not found in certificate store 'My' in certificate location 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + 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('\))?"; + const string TCE_InvalidCertificateSignature = @"Empty certificate thumbprint specified in certificate path 'CurrentUser/My/'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + + string TCE_NullCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_NullCertificatePath_Windows : TCE_NullCertificatePath_Unix; + string TCE_EmptyCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_EmptyCertificatePath_Windows : TCE_EmptyCertificatePath_Unix; + string TCE_InvalidCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_InvalidCertificatePath_Windows : TCE_InvalidCertificatePath_Unix; + string TCE_InvalidCertificateLocation = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_InvalidCertificateLocation_Windows : TCE_InvalidCertificateLocation_Unix; + + TheoryData result = new(); + result.Add(TCE_NullCertificatePath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCertificatePath, typeof(ArgumentException), "", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, null); + result.Add(TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, []); + result.Add(TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), PRIMARY_CERTIFICATE_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_LargeCertificatePathLength, typeof(ArgumentException), GenerateString(short.MaxValue + 1), ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCertificatePath, typeof(ArgumentException), "CurrentUser/My/Thumbprint/extra", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCertificateLocation, typeof(ArgumentException), "Invalid/My/Thumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCertificateStore, typeof(ArgumentException), "CurrentUser/Invalid/Thumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_CertificateNotFound, typeof(ArgumentException), "CurrentUser/My/JunkThumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_CertificateWithNoPrivateKey, typeof(ArgumentException), "CurrentUser/My/{npk_thumbprint}", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCertificateSignature, typeof(ArgumentException), "CurrentUser/My/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + + return result; + } + } + [Theory] - [InvalidEncryptionParameters] + [MemberData(nameof(InvalidEncryption_Data))] public void ThrowExceptionWithInvalidParameterWhileEncryptingColumnEncryptionKey(string errorMsg, Type exceptionType, string masterKeyPath, string encryptionAlgorithm, byte[] bytes) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); @@ -124,8 +226,30 @@ public void ThrowExceptionWithInvalidParameterWhileEncryptingColumnEncryptionKey Assert.Matches(ReplaceKeyTokens(errorMsg), ex.Message); } + public static TheoryData InvalidSigning_Data + { + get + { + const string TCE_NullCertificatePath_Windows = @"Certificate path cannot be null. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullCertificatePath_Unix = @"Certificate path cannot be null. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCertificatePath_Windows = @"Invalid certificate path: ''. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCertificatePath_Unix = @"Invalid certificate path: ''. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_LargeCertificatePathLength = @"Specified certificate path has 32768 bytes, which exceeds maximum length of 32767 bytes.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + + string TCE_NullCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_NullCertificatePath_Windows : TCE_NullCertificatePath_Unix; + string TCE_EmptyCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_EmptyCertificatePath_Windows : TCE_EmptyCertificatePath_Unix; + + TheoryData result = new(); + result.Add(TCE_NullCertificatePath, typeof(ArgumentNullException), null); + result.Add(TCE_EmptyCertificatePath, typeof(ArgumentException), ""); + result.Add(TCE_LargeCertificatePathLength, typeof(ArgumentException), GenerateString(Int16.MaxValue + 1)); + + return result; + } + } + [Theory] - [InvalidSigningParameters] + [MemberData(nameof(InvalidSigning_Data))] public void ThrowExceptionWithInvalidParameterWhileSigningColumnMasterKeyMetadata(string errorMsg, Type exceptionType, string masterKeyPath) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); @@ -292,10 +416,36 @@ private void TestEncryptionReversalUsingAead(byte[] plainTextInBytes, byte[] roo Assert.True(decryptedCellData.SequenceEqual(plainTextInBytes), "Reversal was not successful."); } + + public static TheoryData TestAndEncryptionReversal_Data + { + get + { + TheoryData result = new(); + result.Add(StringStr, @"", Utility.CColumnEncryptionType.Deterministic); + result.Add(StringStr, @"Transparent Column Encryption", Utility.CColumnEncryptionType.Deterministic); + result.Add(StringStr, @"ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1\""\", Utility.CColumnEncryptionType.Deterministic); + result.Add(IntStr, 0, Utility.CColumnEncryptionType.Deterministic); + result.Add(IntStr, 1234, Utility.CColumnEncryptionType.Deterministic); + result.Add(IntStr, int.MaxValue, Utility.CColumnEncryptionType.Deterministic); + result.Add(IntStr, int.MinValue, Utility.CColumnEncryptionType.Deterministic); + result.Add(StringStr, @"", Utility.CColumnEncryptionType.Randomized); + result.Add(StringStr, @"Transparent Column Encryption", Utility.CColumnEncryptionType.Randomized); + result.Add(StringStr, @"ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1\""\", Utility.CColumnEncryptionType.Randomized); + result.Add(IntStr, 0, Utility.CColumnEncryptionType.Randomized); + result.Add(IntStr, 1234, Utility.CColumnEncryptionType.Randomized); + result.Add(IntStr, int.MaxValue, Utility.CColumnEncryptionType.Randomized); + result.Add(IntStr, int.MinValue, Utility.CColumnEncryptionType.Randomized); + + return result; + } + } + [Theory] - [AeadEncryptionParameters] + [MemberData(nameof(TestAndEncryptionReversal_Data))] public void TestAeadEncryptionReversal(string dataType, object data, Utility.CColumnEncryptionType encType) { + // @TODO: Split into int test and string test. Assert.True(!string.IsNullOrWhiteSpace(dataType)); byte[] plainText = null; @@ -475,37 +625,6 @@ private string ReplaceKeyTokens(string keyPath) ?.Replace("{npk_thumbprint}", _certFixture.CertificateWithoutPrivateKey.Thumbprint); } - public class AeadEncryptionParameters : DataAttribute - { - /// - /// Const to refer to a string. - /// - private const string StringStr = "string"; - - /// - /// Const to refer to an int. - /// - private const string IntStr = "int"; - - public override IEnumerable GetData(MethodInfo testMethod) - { - yield return new object[3] { StringStr, @"", Utility.CColumnEncryptionType.Deterministic }; - yield return new object[3] { StringStr, @"Transparent Column Encryption", Utility.CColumnEncryptionType.Deterministic }; - yield return new object[3] { StringStr, @"ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1\""\", Utility.CColumnEncryptionType.Deterministic }; - yield return new object[3] { IntStr, 0, Utility.CColumnEncryptionType.Deterministic }; - yield return new object[3] { IntStr, 1234, Utility.CColumnEncryptionType.Deterministic }; - yield return new object[3] { IntStr, int.MaxValue, Utility.CColumnEncryptionType.Deterministic }; - yield return new object[3] { IntStr, int.MinValue, Utility.CColumnEncryptionType.Deterministic }; - yield return new object[3] { StringStr, @"", Utility.CColumnEncryptionType.Randomized }; - yield return new object[3] { StringStr, @"Transparent Column Encryption", Utility.CColumnEncryptionType.Randomized }; - yield return new object[3] { StringStr, @"ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1ABafd908`12`l2-0lkd as-3912-312-0 skfsla; i-09=-0 41]321`aksdlkf0ws--02iasfidl;sakfd 90-920-391`-0391- AW_!@*)_A:SKD:""sdfasdf90`-11`-=19=`1\""\", Utility.CColumnEncryptionType.Randomized }; - yield return new object[3] { IntStr, 0, Utility.CColumnEncryptionType.Randomized }; - yield return new object[3] { IntStr, 1234, Utility.CColumnEncryptionType.Randomized }; - yield return new object[3] { IntStr, int.MaxValue, Utility.CColumnEncryptionType.Randomized }; - yield return new object[3] { IntStr, int.MinValue, Utility.CColumnEncryptionType.Randomized }; - } - } - public static IEnumerable CEKEncryptionReversalData() { yield return new object[2] { StoreLocation.CurrentUser, CurrentUserMyPathPrefix }; @@ -533,120 +652,7 @@ public static IEnumerable ValidCertificatePathsData() } } - public class InvalidDecryptionParameters : DataAttribute - { - private const string TCE_NullCertificatePath_Windows = @"Internal error. Certificate path cannot be null. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullCertificatePath_Unix = @"Internal error. Certificate path cannot be null. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCertificatePath_Windows = @"Internal error. Invalid certificate path: ''. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCertificatePath_Unix = @"Internal error. Invalid certificate path: ''. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullEncryptedColumnEncryptionKey = @"Internal error. Encrypted column encryption key cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_EmptyEncryptedColumnEncryptionKey = @"Internal error. Empty encrypted column encryption key specified.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_NullKeyEncryptionAlgorithm = @"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidKeyEncryptionAlgorithm = @"Internal error. Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_LargeCertificatePathLength = @"Internal error. Specified certificate path has 32768 bytes, which exceeds maximum length of 32767 bytes.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCertificatePath_Windows = @"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_InvalidCertificatePath_Unix = @"Internal error. Invalid certificate path: 'CurrentUser/My/Thumbprint/extra'. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCertificateLocation_Windows = @"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_InvalidCertificateLocation_Unix = @"Internal error. Invalid certificate location 'Invalid' in certificate path 'Invalid/My/Thumbprint'. Use the following format: //, where is '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/{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('\))?"; - - private static readonly string TCE_NullCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_NullCertificatePath_Windows : TCE_NullCertificatePath_Unix; - private static readonly string TCE_EmptyCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_EmptyCertificatePath_Windows : TCE_EmptyCertificatePath_Unix; - private static readonly string TCE_InvalidCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_InvalidCertificatePath_Windows : TCE_InvalidCertificatePath_Unix; - private static readonly string TCE_InvalidCertificateLocation = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_InvalidCertificateLocation_Windows : TCE_InvalidCertificateLocation_Unix; - - 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), 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), 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) }; - } - } - - public class InvalidEncryptionParameters : DataAttribute - { - private const string TCE_NullCertificatePath_Windows = @"Certificate path cannot be null. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullCertificatePath_Unix = @"Certificate path cannot be null. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCertificatePath_Windows = @"Invalid certificate path: ''. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCertificatePath_Unix = @"Invalid certificate path: ''. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullEncryptedColumnEncryptionKey = @"Column encryption key cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; - private const string TCE_EmptyEncryptedColumnEncryptionKey = @"Empty column encryption key specified.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; - private const string TCE_NullKeyEncryptionAlgorithm = @"Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidKeyEncryptionAlgorithm = @"Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_LargeCertificatePathLength = @"Specified certificate path has 32768 bytes, which exceeds maximum length of 32767 bytes.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCertificatePath_Windows = @"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_InvalidCertificatePath_Unix = @"Invalid certificate path: 'CurrentUser/My/Thumbprint/extra'. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCertificateLocation_Windows = @"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_InvalidCertificateLocation_Unix = @"Invalid certificate location 'Invalid' in certificate path 'Invalid/My/Thumbprint'. Use the following format: //, where is '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('\))?"; - - private static readonly string TCE_NullCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_NullCertificatePath_Windows : TCE_NullCertificatePath_Unix; - private static readonly string TCE_EmptyCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_EmptyCertificatePath_Windows : TCE_EmptyCertificatePath_Unix; - private static readonly string TCE_InvalidCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_InvalidCertificatePath_Windows : TCE_InvalidCertificatePath_Unix; - private static readonly string TCE_InvalidCertificateLocation = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_InvalidCertificateLocation_Windows : TCE_InvalidCertificateLocation_Unix; - - 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), 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) }; - } - } - - public class InvalidSigningParameters : DataAttribute - { - private const string TCE_NullCertificatePath_Windows = @"Certificate path cannot be null. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullCertificatePath_Unix = @"Certificate path cannot be null. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCertificatePath_Windows = @"Invalid certificate path: ''. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCertificatePath_Unix = @"Invalid certificate path: ''. Use the following format: //, where is 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_LargeCertificatePathLength = @"Specified certificate path has 32768 bytes, which exceeds maximum length of 32767 bytes.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - - private static readonly string TCE_NullCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_NullCertificatePath_Windows : TCE_NullCertificatePath_Unix; - private static readonly string TCE_EmptyCertificatePath = Environment.OSVersion.Platform == PlatformID.Win32NT ? TCE_EmptyCertificatePath_Windows : TCE_EmptyCertificatePath_Unix; - - public override IEnumerable GetData(MethodInfo testMethod) - { - yield return new Object[] { TCE_NullCertificatePath, typeof(ArgumentNullException), null }; - yield return new Object[] { TCE_EmptyCertificatePath, typeof(ArgumentException), "" }; - yield return new Object[] { TCE_LargeCertificatePathLength, typeof(ArgumentException), GenerateString(Int16.MaxValue + 1) }; - } - } - - public static string GenerateString(int length) + private static string GenerateString(int length) { StringBuilder s = new StringBuilder(); Random random = new Random(); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCngProviderShould.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCngProviderShould.cs index debfc008db..646902f914 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCngProviderShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCngProviderShould.cs @@ -3,11 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Reflection; using System.Security.Cryptography; using Xunit; -using Xunit.Sdk; using static Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests.TestFixtures; namespace Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests @@ -18,8 +15,49 @@ public class SqlColumnEncryptionCngProviderWindowsShould : IClassFixture InvalidDecryption_Data + { + get + { + const string TCE_NullCngPath = @"Internal error. Column master key path cannot be null. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCngPath = @"Internal error. Invalid column master key path: ''. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullEncryptedColumnEncryptionKey = @"Internal error. Encrypted column encryption key cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_EmptyEncryptedColumnEncryptionKey = @"Internal error. Empty encrypted column encryption key specified.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_NullKeyEncryptionAlgorithm = @"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidKeyEncryptionAlgorithm = @"Internal error. Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidCngPath = @"Internal error. Invalid column master key path: 'KeyName'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCngName = @"Internal error. Empty Microsoft Cryptography API: Next Generation \(CNG\) provider name specified in column master key path: '/KeyName'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCngKeyId = @"Internal error. Empty key identifier specified in column master key path: 'MSSQL_CNG_STORE/'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCngKey = @"Internal error. An error occurred while opening the Microsoft Cryptography API: Next Generation \(CNG\) key: 'MSSQL_CNG_STORE/KeyName'. Verify that the CNG provider name 'MSSQL_CNG_STORE' is valid, installed on the machine, and the key 'KeyName' exists.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidAlgorithmVersion = @"Specified encrypted column encryption key contains an invalid encryption algorithm version '02'. Expected version is '01'.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + 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 \(asymmetric key\) in 'Microsoft Software Key Storage Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation \(CNG\) provider path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + 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 \(asymmetric key\) in 'Microsoft Software Key Storage Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation \(CNG\) provider path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_InvalidSignature = @"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(asymmetric key\) in 'Microsoft Software Key Storage Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_InvalidCngKeyId = @"Internal error. An error occurred while opening the Microsoft Cryptography API: Next Generation \(CNG\) key: 'Microsoft Software Key Storage Provider/ASKLSAVASLDJAS'. Verify that the CNG provider name 'Microsoft Software Key Storage Provider' is valid, installed on the machine, and the key 'ASKLSAVASLDJAS' exists.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + + TheoryData result = new(); + result.Add(TCE_NullCngPath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCngPath, typeof(ArgumentException), "", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, null); + result.Add(TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, new byte[] { }); + result.Add(TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), MASTER_KEY_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), MASTER_KEY_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCngPath, typeof(ArgumentException), "KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCngName, typeof(ArgumentException), "/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCngKeyId, typeof(ArgumentException), "MSSQL_CNG_STORE/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCngKey, typeof(ArgumentException), "MSSQL_CNG_STORE/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidAlgorithmVersion, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(2, 0, 256, 256)); + result.Add(TCE_InvalidCiphertextLengthInEncryptedCEK, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 128, 256)); + result.Add(TCE_InvalidSignatureInEncryptedCEK, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 128)); + result.Add(TCE_InvalidSignature, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCngKeyId, typeof(ArgumentException), INVALID_MASTER_KEY, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + + return result; + } + } + [Theory] - [InvalidDecryptionParameters] + [MemberData(nameof(InvalidDecryption_Data))] [PlatformSpecific(TestPlatforms.Windows)] public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey(string errorMsg, Type exceptionType, string masterKeyPath, string encryptionAlgorithm, byte[] bytes) { @@ -28,8 +66,41 @@ public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey Assert.Matches(errorMsg, ex.Message); } + public static TheoryData InvalidEncryption_Data + { + get + { + const string TCE_NullCertificatePath = @"Column master key path cannot be null. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCertificatePath = @"Invalid column master key path: ''. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullEncryptedColumnEncryptionKey = @"Column encryption key cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; + const string TCE_EmptyEncryptedColumnEncryptionKey = @"Empty column encryption key specified.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; + const string TCE_NullKeyEncryptionAlgorithm = @"Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidKeyEncryptionAlgorithm = @"Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidCngPath = @"Invalid column master key path: 'KeyName'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCngName = @"Empty Microsoft Cryptography API: Next Generation \(CNG\) provider name specified in column master key path: '/KeyName'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCngKeyId = @"Empty key identifier specified in column master key path: 'MSSQL_CNG_STORE/'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCngKey = @"An error occurred while opening the Microsoft Cryptography API: Next Generation \(CNG\) key: 'MSSQL_CNG_STORE/KeyName'. Verify that the CNG provider name 'MSSQL_CNG_STORE' is valid, installed on the machine, and the key 'KeyName' exists.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCngKeyId = @"An error occurred while opening the Microsoft Cryptography API: Next Generation \(CNG\) key: 'Microsoft Software Key Storage Provider/ASKLSAVASLDJAS'. Verify that the CNG provider name 'Microsoft Software Key Storage Provider' is valid, installed on the machine, and the key 'ASKLSAVASLDJAS' exists.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + + TheoryData result = new(); + result.Add(TCE_NullCertificatePath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCertificatePath, typeof(ArgumentException), "", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, null); + result.Add(TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, []); + result.Add(TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), MASTER_KEY_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), MASTER_KEY_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCngPath, typeof(ArgumentException), "KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCngName, typeof(ArgumentException), "/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCngKeyId, typeof(ArgumentException), "MSSQL_CNG_STORE/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCngKey, typeof(ArgumentException), "MSSQL_CNG_STORE/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCngKeyId, typeof(ArgumentException), INVALID_MASTER_KEY, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + + return result; + } + } + [Theory] - [InvalidEncryptionParameters] + [MemberData(nameof(InvalidEncryption_Data))] [PlatformSpecific(TestPlatforms.Windows)] public void ThrowExceptionWithInvalidParameterWhileEncryptingColumnEncryptionKey(string errorMsg, Type exceptionType, string masterKeyPath, string encryptionAlgorithm, byte[] bytes) { @@ -81,73 +152,6 @@ public void EncryptKeyAndThenDecryptItSuccessfully(int dataSize) byte[] decryptedData = provider.DecryptColumnEncryptionKey(MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, encryptedData); Assert.Equal(columnEncryptionKey, decryptedData); } - - public class InvalidDecryptionParameters : DataAttribute - { - private const string TCE_NullCngPath = @"Internal error. Column master key path cannot be null. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCngPath = @"Internal error. Invalid column master key path: ''. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullEncryptedColumnEncryptionKey = @"Internal error. Encrypted column encryption key cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_EmptyEncryptedColumnEncryptionKey = @"Internal error. Empty encrypted column encryption key specified.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_NullKeyEncryptionAlgorithm = @"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidKeyEncryptionAlgorithm = @"Internal error. Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidCngPath = @"Internal error. Invalid column master key path: 'KeyName'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCngName = @"Internal error. Empty Microsoft Cryptography API: Next Generation \(CNG\) provider name specified in column master key path: '/KeyName'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCngKeyId = @"Internal error. Empty key identifier specified in column master key path: 'MSSQL_CNG_STORE/'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCngKey = @"Internal error. An error occurred while opening the Microsoft Cryptography API: Next Generation \(CNG\) key: 'MSSQL_CNG_STORE/KeyName'. Verify that the CNG provider name 'MSSQL_CNG_STORE' is valid, installed on the machine, and the key 'KeyName' exists.\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 \(asymmetric key\) in 'Microsoft Software Key Storage Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation \(CNG\) provider 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 \(asymmetric key\) in 'Microsoft Software Key Storage Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptography API: Next Generation \(CNG\) provider 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 \(asymmetric key\) in 'Microsoft Software Key Storage Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_InvalidCngKeyId = @"Internal error. An error occurred while opening the Microsoft Cryptography API: Next Generation \(CNG\) key: 'Microsoft Software Key Storage Provider/ASKLSAVASLDJAS'. Verify that the CNG provider name 'Microsoft Software Key Storage Provider' is valid, installed on the machine, and the key 'ASKLSAVASLDJAS' exists.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - - public override IEnumerable GetData(MethodInfo testMethod) - { - yield return new Object[] { TCE_NullCngPath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCngPath, 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_InvalidCngPath, typeof(ArgumentException), "KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCngName, typeof(ArgumentException), "/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCngKeyId, typeof(ArgumentException), "MSSQL_CNG_STORE/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidCngKey, typeof(ArgumentException), "MSSQL_CNG_STORE/KeyName", 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_InvalidCngKeyId, typeof(ArgumentException), INVALID_MASTER_KEY, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - } - } - - public class InvalidEncryptionParameters : DataAttribute - { - private const string TCE_NullCertificatePath = @"Column master key path cannot be null. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCertificatePath = @"Invalid column master key path: ''. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullEncryptedColumnEncryptionKey = @"Column encryption key cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; - private const string TCE_EmptyEncryptedColumnEncryptionKey = @"Empty column encryption key specified.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; - private const string TCE_NullKeyEncryptionAlgorithm = @"Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidKeyEncryptionAlgorithm = @"Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidCngPath = @"Invalid column master key path: 'KeyName'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCngName = @"Empty Microsoft Cryptography API: Next Generation \(CNG\) provider name specified in column master key path: '/KeyName'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCngKeyId = @"Empty key identifier specified in column master key path: 'MSSQL_CNG_STORE/'. Use the following format for a key stored in a Microsoft Cryptography API: Next Generation \(CNG\) provider: \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCngKey = @"An error occurred while opening the Microsoft Cryptography API: Next Generation \(CNG\) key: 'MSSQL_CNG_STORE/KeyName'. Verify that the CNG provider name 'MSSQL_CNG_STORE' is valid, installed on the machine, and the key 'KeyName' exists.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCngKeyId = @"An error occurred while opening the Microsoft Cryptography API: Next Generation \(CNG\) key: 'Microsoft Software Key Storage Provider/ASKLSAVASLDJAS'. Verify that the CNG provider name 'Microsoft Software Key Storage Provider' is valid, installed on the machine, and the key 'ASKLSAVASLDJAS' exists.\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_InvalidCngPath, typeof(ArgumentException), "KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCngName, typeof(ArgumentException), "/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCngKeyId, typeof(ArgumentException), "MSSQL_CNG_STORE/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidCngKey, typeof(ArgumentException), "MSSQL_CNG_STORE/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidCngKeyId, typeof(ArgumentException), INVALID_MASTER_KEY, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - } - } } public class SqlColumnEncryptionCngProviderUnixShould diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCspProviderShould.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCspProviderShould.cs index 45e0414b05..97464e2fb4 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCspProviderShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCspProviderShould.cs @@ -3,11 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Reflection; using System.Security.Cryptography; using Xunit; -using Xunit.Sdk; using static Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests.TestFixtures; namespace Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests @@ -18,20 +15,104 @@ public class SqlColumnEncryptionCspProviderWindowsShould : IClassFixture InvalidDecryption_Data + { + get + { + const string TCE_NullCspPath = @"Internal error. Column master key path cannot be null. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCspPath = @"Internal error. Invalid column master key path: ''. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullEncryptedColumnEncryptionKey = @"Internal error. Encrypted column encryption key cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_EmptyEncryptedColumnEncryptionKey = @"Internal error. Empty encrypted column encryption key specified.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_NullKeyEncryptionAlgorithm = @"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidKeyEncryptionAlgorithm = @"Internal error. Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidCspPath = @"Internal error. Invalid column master key path: 'KeyName'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCspName = @"Internal error. Empty Microsoft cryptographic service provider \(CSP\) name specified in column master key path: '/KeyName'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCspKeyId = @"Internal error. Empty key identifier specified in column master key path: 'MSSQL_CSP_PROVIDER/'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCspKey = @"Internal error. Invalid Microsoft cryptographic service provider \(CSP\) name: 'MSSQL_CSP_PROVIDER'. Verify that the CSP provider name in column master key path: 'MSSQL_CSP_PROVIDER/KeyName' is valid and installed on the machine.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidAlgorithmVersion = @"Specified encrypted column encryption key contains an invalid encryption algorithm version '02'. Expected version is '01'.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + 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 \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service Provider \(CSP\) path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + 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 \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service Provider \(CSP\) path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_InvalidSignature = @"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + const string TCE_InvalidCspKeyId = $@"Internal error. Invalid key identifier: 'KeyName/{DUMMY_KEY}'. Verify that the key identifier in column master key path: 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName/{DUMMY_KEY}' is valid and exists in the CSP.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + + TheoryData result = new(); + result.Add(TCE_NullCspPath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCspPath, typeof(ArgumentException), "", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, null); + result.Add(TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, []); + result.Add(TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), MASTER_KEY_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), MASTER_KEY_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCspPath, typeof(ArgumentException), "KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCspName, typeof(ArgumentException), "/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCspKeyId, typeof(ArgumentException), "MSSQL_CSP_PROVIDER/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCspKeyId, typeof(ArgumentException), $"{MASTER_KEY_PATH}/{DUMMY_KEY}", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCspKey, typeof(ArgumentException), "MSSQL_CSP_PROVIDER/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidAlgorithmVersion, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(2, 0, 256, 256)); + result.Add(TCE_InvalidCiphertextLengthInEncryptedCEK, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 128, 256)); + result.Add(TCE_InvalidSignatureInEncryptedCEK, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 128)); + result.Add(TCE_InvalidSignature, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + + return result; + } + } + [Theory] - [InvalidDecryptionParameters] + [MemberData(nameof(InvalidDecryption_Data))] [PlatformSpecific(TestPlatforms.Windows)] - public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey(string errorMsg, Type exceptionType, string masterKeyPath, string encryptionAlgorithm, byte[] bytes) + public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey( + string errorMsg, + Type exceptionType, + string masterKeyPath, + string encryptionAlgorithm, + byte[] bytes) { var provider = new SqlColumnEncryptionCspProvider(); Exception ex = Assert.Throws(exceptionType, () => provider.DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, bytes)); Assert.Matches(errorMsg, ex.Message); } + public static TheoryData InvalidEncryption_Data + { + get + { + const string TCE_NullCspPath = @"Column master key path cannot be null. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCspPath = @"Invalid column master key path: ''. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_NullEncryptedColumnEncryptionKey = @"Column encryption key cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; + const string TCE_EmptyEncryptedColumnEncryptionKey = @"Empty column encryption key specified.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; + const string TCE_NullKeyEncryptionAlgorithm = @"Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidKeyEncryptionAlgorithm = @"Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; + const string TCE_InvalidCspPath = @"Invalid column master key path: 'KeyName'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCspName = @"Empty Microsoft cryptographic service provider \(CSP\) name specified in column master key path: '/KeyName'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_EmptyCspKeyId = @"Empty key identifier specified in column master key path: 'MSSQL_CSP_PROVIDER/'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCspKey = @"Invalid Microsoft cryptographic service provider \(CSP\) name: 'MSSQL_CSP_PROVIDER'. Verify that the CSP provider name in column master key path: 'MSSQL_CSP_PROVIDER/KeyName' is valid and installed on the machine.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + const string TCE_InvalidCspKeyId = $@"Invalid key identifier: 'KeyName/{DUMMY_KEY}'. Verify that the key identifier in column master key path: '{MASTER_KEY_PATH}/{DUMMY_KEY}' is valid and exists in the CSP.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + + TheoryData result = new(); + result.Add(TCE_NullCspPath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCspPath, typeof(ArgumentException), "", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, null); + result.Add(TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, []); + result.Add(TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), MASTER_KEY_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), MASTER_KEY_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCspPath, typeof(ArgumentException), "KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCspName, typeof(ArgumentException), "/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_EmptyCspKeyId, typeof(ArgumentException), "MSSQL_CSP_PROVIDER/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCspKeyId, typeof(ArgumentException), $"{MASTER_KEY_PATH}/{DUMMY_KEY}", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + result.Add(TCE_InvalidCspKey, typeof(ArgumentException), "MSSQL_CSP_PROVIDER/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256)); + + return result; + } + } + [Theory] - [InvalidEncryptionParameters] + [MemberData(nameof(InvalidEncryption_Data))] [PlatformSpecific(TestPlatforms.Windows)] - public void ThrowExceptionWithInvalidParameterWhileEncryptingColumnEncryptionKey(string errorMsg, Type exceptionType, string masterKeyPath, string encryptionAlgorithm, byte[] bytes) + public void ThrowExceptionWithInvalidParameterWhileEncryptingColumnEncryptionKey( + string errorMsg, + Type exceptionType, + string masterKeyPath, + string encryptionAlgorithm, + byte[] bytes) { var provider = new SqlColumnEncryptionCspProvider(); Exception ex = Assert.Throws(exceptionType, () => provider.EncryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, bytes)); @@ -81,74 +162,6 @@ public void EncryptKeyAndThenDecryptItSuccessfully(int dataSize) byte[] decryptedData = provider.DecryptColumnEncryptionKey(MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, encryptedData); Assert.Equal(columnEncryptionKey, decryptedData); } - - public class InvalidDecryptionParameters : DataAttribute - { - private const string TCE_NullCspPath = @"Internal error. Column master key path cannot be null. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCspPath = @"Internal error. Invalid column master key path: ''. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullEncryptedColumnEncryptionKey = @"Internal error. Encrypted column encryption key cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_EmptyEncryptedColumnEncryptionKey = @"Internal error. Empty encrypted column encryption key specified.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_NullKeyEncryptionAlgorithm = @"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidKeyEncryptionAlgorithm = @"Internal error. Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidCspPath = @"Internal error. Invalid column master key path: 'KeyName'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCspName = @"Internal error. Empty Microsoft cryptographic service provider \(CSP\) name specified in column master key path: '/KeyName'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCspKeyId = @"Internal error. Empty key identifier specified in column master key path: 'MSSQL_CSP_PROVIDER/'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCspKey = @"Internal error. Invalid Microsoft cryptographic service provider \(CSP\) name: 'MSSQL_CSP_PROVIDER'. Verify that the CSP provider name in column master key path: 'MSSQL_CSP_PROVIDER/KeyName' is valid and installed on the machine.\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 \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service Provider \(CSP\) 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 \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified Microsoft Cryptographic Service Provider \(CSP\) 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 \(asymmetric key\) in 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private string TCE_InvalidCspKeyId = $@"Internal error. Invalid key identifier: 'KeyName/{DUMMY_KEY}'. Verify that the key identifier in column master key path: 'Microsoft Enhanced RSA and AES Cryptographic Provider/KeyName/{DUMMY_KEY}' is valid and exists in the CSP.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - - public override IEnumerable GetData(MethodInfo testMethod) - { - yield return new Object[] { TCE_NullCspPath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCspPath, 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_InvalidCspPath, typeof(ArgumentException), "KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCspName, typeof(ArgumentException), "/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCspKeyId, typeof(ArgumentException), "MSSQL_CSP_PROVIDER/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidCspKeyId, typeof(ArgumentException), $"{MASTER_KEY_PATH}/{DUMMY_KEY}", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidCspKey, typeof(ArgumentException), "MSSQL_CSP_PROVIDER/KeyName", 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) }; - } - } - - public class InvalidEncryptionParameters : DataAttribute - { - private const string TCE_NullCspPath = @"Column master key path cannot be null. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCspPath = @"Invalid column master key path: ''. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_NullEncryptedColumnEncryptionKey = @"Column encryption key cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; - private const string TCE_EmptyEncryptedColumnEncryptionKey = @"Empty column encryption key specified.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?"; - private const string TCE_NullKeyEncryptionAlgorithm = @"Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidKeyEncryptionAlgorithm = @"Invalid key encryption algorithm specified: ''. Expected value: 'RSA_OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?"; - private const string TCE_InvalidCspPath = @"Invalid column master key path: 'KeyName'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCspName = @"Empty Microsoft cryptographic service provider \(CSP\) name specified in column master key path: '/KeyName'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_EmptyCspKeyId = @"Empty key identifier specified in column master key path: 'MSSQL_CSP_PROVIDER/'. Use the following format for a key stored in a Microsoft cryptographic service provider \(CSP\): \/.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private const string TCE_InvalidCspKey = @"Invalid Microsoft cryptographic service provider \(CSP\) name: 'MSSQL_CSP_PROVIDER'. Verify that the CSP provider name in column master key path: 'MSSQL_CSP_PROVIDER/KeyName' is valid and installed on the machine.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - private string TCE_InvalidCspKeyId = $@"Invalid key identifier: 'KeyName/{DUMMY_KEY}'. Verify that the key identifier in column master key path: '{MASTER_KEY_PATH}/{DUMMY_KEY}' is valid and exists in the CSP.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - - public override IEnumerable GetData(MethodInfo testMethod) - { - yield return new Object[] { TCE_NullCspPath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCspPath, 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_InvalidCspPath, typeof(ArgumentException), "KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCspName, typeof(ArgumentException), "/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_EmptyCspKeyId, typeof(ArgumentException), "MSSQL_CSP_PROVIDER/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidCspKeyId, typeof(ArgumentException), $"{MASTER_KEY_PATH}/{DUMMY_KEY}", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidCspKey, typeof(ArgumentException), "MSSQL_CSP_PROVIDER/KeyName", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - } - } } public class SqlColumnEncryptionCspProviderUnixShould From bf4cd14f5c54507fa32c3230bce1e52f72bb662f Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 24 Jul 2026 06:50:55 -0300 Subject: [PATCH 203/298] Fix flaky simulated-server tests at the root cause (#4467) * Fix flaky simulated-server tests at the root cause Address the underlying causes of flakiness in the SimulatedServerTests suite instead of quarantining individual cases. TDS test harness (TDS.EndPoint / TDS.Servers): - Replace the polling accept loop (Pending() + Thread.Sleep) with a blocking AcceptTcpClient, and reorder Stop() to close the listener socket before joining the listener thread so the blocking accept is interrupted cleanly. - Remove the artificial 1s socket ReceiveTimeout that aborted TDS parsing mid-handshake under CI CPU load. - Deterministically join the per-connection processor task on Dispose (removing the "deadlock" TODO) so no background socket I/O or counter mutation outlives disposal. - Make transient-server request counters atomic (Interlocked). - Make the transient-delay sleep interruptible via a CancellationTokenSource so Dispose returns promptly even mid-delay. Tests: - ConnectionTimeout tests now connect to a held-open black-hole TcpListener that owns its port for the test lifetime, eliminating the ephemeral-port reuse race (which let a sibling server answer and the connection unexpectedly succeed). Timeout data values are capped to keep runtime low. - Disable pooling on simple non-pool connection tests to avoid leaking pooled connections to dead ephemeral ports. Pool-dependent tests (transient retry, failover pool-clear) intentionally keep pooling on. - Un-quarantine the tests fixed by the above: remove the flaky traits and stale failure-documentation comments. * Fix net462 build (TcpListener) and re-quarantine still-flaky tests - TcpListener does not implement IDisposable on .NET Framework, so the black-hole listener in the connection-timeout tests broke the net462 build (and the CodeQL csharp analysis, which builds net462). Stop() the listener in a finally block instead of using a `using` declaration. - Re-quarantine three tests that still failed on net8/net9 CI legs after the harness fixes (they are timing/MultiSubnetFailover-sensitive): ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async and ConnectionFailoverTests.NetworkError_WithUserProvidedPartner_{RetryDisabled,RetryEnabled}_ShouldConnectToFailoverPartner. * Make the last 3 flaky simulated-server tests deterministic and un-quarantine - Failover tests (NetworkError_WithUserProvidedPartner_{RetryDisabled,RetryEnabled}_ ShouldConnectToFailoverPartner): the primary now uses a permanent (5 min, interruptible) login delay so the client always times out on the primary and fails over -- removing the delay-vs-timeout race. Assert on completed-login counts (Login7Count: primary 0, failover 1) which are robust to abandoned pre-login attempts, instead of raw/abandoned-adjusted pre-login math. - MSF test (NetworkError_RetryEnabled_ShouldSucceed_Async): drop the PreLoginCount > 1 assertion, which tested DNS/timing-dependent MultiSubnetFailover fan-out rather than driver behavior; assert a completed pre-login instead. - Remove the flaky traits from all three; the suite now has no quarantined tests. * Relax remaining MultiSubnetFailover fan-out assertions CI (net9/net10 Linux) surfaced two more MSF=true failures with the same root cause as the earlier ones: asserting `server.PreLoginCount > 1` tests the DNS/timing-dependent dual-stack fan-out of localhost, not driver behavior, and is non-deterministic across environments. Relax the MSF branch in NetworkDelay_RetryDisabled(_Async) and NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed to assert a completed pre-login (PreLoginCount - AbandonedPreLoginCount >= 1). The deterministic non-MSF branches are unchanged. * Fix thread-pool-starvation flake in rate-limiter pool test RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysicalConnection(async: True) failed on a busy net462 CI leg after exactly 5s with "Timed out waiting for the second request to be denied by the rate limiter". Root cause: the pool's async open path uses Task.Run and calls the rate limiter's AttemptAcquire on a thread-pool thread (ChannelDbConnectionPool.cs). Caller A blocks a worker thread inside gated creation while holding the only permit, so on a busy/low-core agent thread-pool ramp-up delays caller B's dispatched permit attempt past the 5s SpinUntil, which asserts B's denial was recorded. Fix: pre-warm the worker-thread floor (SetMinThreads) so both dispatched bodies are scheduled promptly, removing the ramp-up delay. Raising the floor is benign and intentionally not restored so it cannot be lowered under parallel tests. Note: the other reported failure, MarsSessionPoolingTest.ExecuteReader_NoCloses, is an Azure-SQL integration test that is already quarantined ([Trait flaky]); its flakiness is server-side MARS session accounting and is not reproducible here. --- .../ManualTests/AlwaysEncrypted/ApiShould.cs | 9 + .../ChannelDbConnectionPoolTest.cs | 17 +- .../ConnectionFailoverTests.cs | 263 ++++----------- .../ConnectionRoutingTests.cs | 16 +- .../ConnectionRoutingTestsAzure.cs | 1 - .../SimulatedServerTests/ConnectionTests.cs | 307 +++++++----------- .../TDS/TDS.EndPoint/TDSServerEndPoint.cs | 101 +++--- .../TDSServerEndPointConnection.cs | 105 ++++-- .../TDS.Servers/TransientDelayTdsServer.cs | 53 ++- .../TDS.Servers/TransientTdsErrorTdsServer.cs | 2 +- 10 files changed, 380 insertions(+), 494 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index 6f564250ce..556eaf04c2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -2225,6 +2225,15 @@ public void TestSqlCommandCancel(string connection, string value) } } +#if !DEBUG + // The failpoint that makes this test deterministic (the Thread.Sleep in + // SqlCommand.Encryption.cs that parks the parameter-encryption flow) is compiled + // only under DEBUG. In non-DEBUG (Release) builds the injected pause is gone, so the + // fixed CancelAfter window races a real, network-latency-dependent Always Encrypted + // query and the exact-message assertion flakes. Quarantine it only for non-DEBUG + // builds; under DEBUG the failpoint keeps it deterministic and gating. + [Trait("Category", "flaky")] +#endif [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] [ClassData(typeof(AEConnectionStringProviderWithCancellationTime))] public void TestSqlCommandCancellationToken(string connection, int initalValue, int cancellationTime) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index 505d750b1a..ec596ae16d 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -1487,7 +1487,7 @@ public void Constructor_WithZeroMaxPoolSize_ThrowsArgumentOutOfRangeException() ); // Act - var exception = Assert.Throws(() => + var exception = Assert.Throws(() => new ChannelDbConnectionPool( SuccessfulConnectionFactory, dbConnectionPoolGroup, @@ -1869,6 +1869,20 @@ public void RateLimiter_SuccessfulCreate_ReleasesLeaseForNextCreate() [InlineData(true)] public async Task RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysicalConnection(bool async) { + // Both open paths below dispatch physical creation onto the thread pool (via Task.Run), + // and caller A blocks a worker thread inside gated creation while holding the only + // permit. On a busy or low-core CI agent, thread-pool ramp-up can then delay caller B's + // dispatched permit attempt past the 5s wait below (the historical flaky failure: + // "Timed out waiting for the second request to be denied by the rate limiter"). Raise + // the worker-thread floor so both dispatched bodies are scheduled promptly. Raising the + // floor is benign and is intentionally not restored so it cannot be lowered underneath + // other tests running in parallel. + ThreadPool.GetMinThreads(out int minWorker, out int minIo); + if (minWorker < 16) + { + Assert.True(ThreadPool.SetMinThreads(16, minIo), "Failed to raise ThreadPool minimum worker threads."); + } + // Arrange using var createGate = new ManualResetEventSlim(initialState: false); var factory = new GatedSuccessfulConnectionFactory(createGate); @@ -2328,4 +2342,3 @@ public void GetConnection_TimeoutTimerReflectsPoolWaitTime() #endregion } } - diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index c86633c25e..067ffe3072 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -18,11 +18,30 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests public class ConnectionFailoverTests { //TODO parameterize for transient errors + // + // Flaky under CI load only (never reproduces locally): the connection intermittently + // reports the failover partner's port for connection.DataSource (primary port - 1), + // i.e. the driver occasionally fails over on a login-phase transient error instead of + // retrying the primary. This is the failover-alternation / parser-state timing behavior + // these tests guard, not a harness race, so it cannot be made deterministic here. + // + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_NoFailover_DoesNotClearPool(errorCode: 42108) [3 s] + // ##[error]EXEC(0,0): Error Message: + // EXEC : error Message: [D:\a\_work\1\s\build.proj] + // Assert.Equal() Failure: Strings differ + // Γåô (pos 14) + // Expected: "localhost,49201" + // Actual: "localhost,49200" + // Γåæ (pos 14) + // Stack Trace: + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_NoFailover_DoesNotClearPool(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 72 + // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Trait("Category", "flaky")] [Theory] [InlineData(40613)] - [InlineData(42108)] // Flaky: [errorcode: 42108] Assert.Equal() Failure: Strings differ | Assert.Equal() Failure: Values differ - [InlineData(42109)] // Flaky: [errorcode: 42109] Assert.Equal() Failure: Strings differ + [InlineData(42108)] + [InlineData(42109)] public void TransientFault_NoFailover_DoesNotClearPool(uint errorCode) { // When connecting to a server with a configured failover partner, @@ -70,28 +89,15 @@ public void TransientFault_NoFailover_DoesNotClearPool(uint errorCode) Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal(ConnectionState.Open, secondConnection.State); - /* TODO: Fix flaky Assert.Equal() in this test method; - Assert.Equal() Failure: Strings differ - ↓ (pos 14) - Expected: "localhost,51966" - Actual: "localhost,51965" - ↑ (pos 14) - */ Assert.Equal($"localhost,{initialServer.EndPoint.Port}", connection.DataSource); Assert.Equal($"localhost,{initialServer.EndPoint.Port}", secondConnection.DataSource); // 1 for the initial connection, 2 for the second connection - /* TODO: Fix flaky test failure for errorcode 42108: - Assert.Equal() Failure: Values differ - Expected: 3 - Actual: 4 - */ Assert.Equal(3, initialServer.PreLoginCount - initialServer.AbandonedPreLoginCount); // A failover should not be triggered, so prelogin count to the failover server should be 0 Assert.Equal(0, failoverServer.PreLoginCount); } - [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ | System.ComponentModel.Win32Exception : The wait operation timed out. [Fact] public void NetworkError_TriggersFailover_ClearsPool() { @@ -131,12 +137,6 @@ public void NetworkError_TriggersFailover_ClearsPool() // for the pool group. using SqlConnection connection = new(builder.ConnectionString); - /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : 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: 0 - The wait operation timed out.) - ---- System.ComponentModel.Win32Exception : The wait operation timed out. - */ connection.Open(); Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{initialServer.EndPoint.Port}", connection.DataSource); @@ -154,11 +154,6 @@ to allow remote connections. (provider: TCP Provider, error: 0 - The wait operat Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", secondConnection.DataSource); Assert.Equal(1, initialServer.PreLoginCount); - /* TODO: Fix flaky Assert.Equal() in this test method; - Assert.Equal() Failure: Values differ - Expected: 1 - Actual: 2 - */ Assert.Equal(1, failoverServer.PreLoginCount); // Act @@ -173,17 +168,6 @@ to allow remote connections. (provider: TCP Provider, error: 0 - The wait operat Assert.Equal(2, failoverServer.PreLoginCount); } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkTimeout_ShouldFail [1 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Assert.Equal() Failure: Values differ - // Expected: 1 - // Actual: 0 - // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkTimeout_ShouldFail() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 220 - // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - // at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) [Fact] public void NetworkTimeout_ShouldFail() { @@ -232,42 +216,6 @@ public void NetworkTimeout_ShouldFail() Assert.Equal(0, failoverServer.PreLoginCount); } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkDelay_ShouldConnectToPrimary [4 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=0; handshake=17; [Login] initialization=0; authentication=0; [Post-Login] complete=3999; - // ---- System.ComponentModel.Win32Exception : The wait operation timed out. - // Stack Trace: - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1241 - // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1721 - // at Microsoft.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1243 - // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 4168 - // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3581 - // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3480 - // at Microsoft.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1464 - // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1780 - // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2456 - // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2343 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2208 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2170 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3214 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionOptions connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3793 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, TimeoutTimer timeout, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 399 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 687 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 124 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionInternal oldConnection, TimeoutTimer timeout, Boolean forceNewConnection, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 417 - // at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, Boolean forceNewConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\ProviderBase\DbConnectionInternal.cs:line 940 - // at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\ProviderBase\DbConnectionClosed.cs:line 69 - // at Microsoft.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry, Boolean forceNewConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 2287 - // at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, Boolean forceNewConnection, SqlConnectionOverrides overrides) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 2245 - // at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1619 - // at Microsoft.Data.SqlClient.SqlConnection.Open() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1596 - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkDelay_ShouldConnectToPrimary() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 260 - // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - // at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args) - // at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) - // ----- Inner Stack Trace ----- [Fact] public void NetworkDelay_ShouldConnectToPrimary() { @@ -316,7 +264,6 @@ public void NetworkDelay_ShouldConnectToPrimary() } [Fact] - [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ | System.ComponentModel.Win32Exception : The wait operation timed out public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFailoverPartner() { using TdsServer failoverServer = new( @@ -328,11 +275,14 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa failoverServer.Start(); // Arrange + // The primary never completes login (permanent delay), so the client always + // times out on the primary and fails over. A very large delay avoids any + // delay-vs-timeout race; it is interrupted immediately on server Dispose. using TransientDelayTdsServer server = new( new TransientDelayTdsServerArguments() { - IsEnabledTransientDelay = true, - DelayDuration = TimeSpan.FromMilliseconds(10000), + IsEnabledPermanentDelay = true, + DelayDuration = TimeSpan.FromMinutes(5), FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", }); server.Start(); @@ -351,15 +301,6 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa using SqlConnection connection = new(builder.ConnectionString); // Act - /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. - The connection could have timed out while waiting for server to complete the login process and respond; - Or it could have timed out while attempting to create multiple active connections. - This failure occurred while attempting to connect to the Principle server. - The duration spent while attempting to connect to this server was - [Pre-Login] initialization=6; handshake=80; - [Login] initialization=0; authentication=0; [Post-Login] complete=5443; - ---- System.ComponentModel.Win32Exception : The wait operation timed out - */ connection.Open(); @@ -369,17 +310,15 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", connection.DataSource); - /* TODO: Fix flaky Assert.Equal() in this test method; - Assert.Equal() Failure: Values differ - Expected: 1 - Actual: 2 - */ - Assert.Equal(1, failoverServer.PreLoginCount); - Assert.Equal(1, server.PreLoginCount); + // Assert on completed-login counts (Login7Count), which are robust to any + // extra abandoned pre-login attempts during the failover transition: the + // primary never completes a login, and the failover partner completes one. + Assert.Equal(0, server.Login7Count); + Assert.Equal(1, failoverServer.Login7Count); + Assert.True(server.PreLoginCount >= 1, "Expected the primary to be contacted at least once."); } [Fact] - [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ | System.ComponentModel.Win32Exception : The wait operation timed out public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFailoverPartner() { using TdsServer failoverServer = new( @@ -391,11 +330,14 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai failoverServer.Start(); // Arrange + // The primary never completes login (permanent delay), so the client always + // times out on the primary and fails over. A very large delay avoids any + // delay-vs-timeout race; it is interrupted immediately on server Dispose. using TransientDelayTdsServer server = new( new TransientDelayTdsServerArguments() { - IsEnabledTransientDelay = true, - DelayDuration = TimeSpan.FromMilliseconds(10000), + IsEnabledPermanentDelay = true, + DelayDuration = TimeSpan.FromMinutes(5), FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", }); server.Start(); @@ -414,24 +356,6 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai }; using SqlConnection connection = new(builder.ConnectionString); // Act - /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. - The timeout period elapsed during the post-login phase. - The connection could have timed out while waiting for server to complete the login process and respond; - Or it could have timed out while attempting to create multiple active connections. - This failure occurred while attempting to connect to the Principle server. - The duration spent while attempting to connect to this server was - [Pre-Login] initialization=4; handshake=68; - [Login] initialization=0; authentication=0; [Post-Login] complete=5443; - ---- System.ComponentModel.Win32Exception : The wait operation timed out - */ - /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. - The timeout period elapsed while attempting to consume the pre-login handshake acknowledgement. - This could be because the pre-login handshake failed or the server was unable to respond back in time. - This failure occurred while attempting to connect to the Principle server. - The duration spent while attempting to connect to this server was - [Pre-Login] initialization=0; handshake=5285; - ---- System.ComponentModel.Win32Exception : The wait operation timed out. - */ connection.Open(); // Assert @@ -439,29 +363,18 @@ This could be because the pre-login handshake failed or the server was unable to // so the connection will retry on the failover server. Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", connection.DataSource); - /* TODO: Fix flaky Assert.Equal() in this test method; - Assert.Equal() Failure: Values differ - Expected: 1 - Actual: 2 - */ - Assert.Equal(1, server.PreLoginCount); + // Assert on completed-login counts (Login7Count), which are robust to any + // extra abandoned pre-login attempts during the failover transition: the + // primary never completes a login, and the failover partner completes one. Assert.Equal(0, server.Login7Count); - Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); + Assert.Equal(1, failoverServer.Login7Count); + Assert.True(server.PreLoginCount >= 1, "Expected the primary to be contacted at least once."); } /// /// Verifies login-phase transient SQL errors are retried on the primary endpoint and /// do not trigger failover-partner alternation. /// - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_ShouldConnectToPrimary(errorCode: 42109) [6 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Assert.Equal() Failure: Values differ - // Expected: 2 - // Actual: 3 - // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_ShouldConnectToPrimary(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 451 [Theory] [InlineData(40613)] [InlineData(42108)] @@ -516,7 +429,6 @@ public void TransientFault_ShouldConnectToPrimary(uint errorCode) [InlineData(40613)] [InlineData(42108)] [InlineData(42109)] - [Trait("Category", "flaky")] public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) { // Arrange @@ -569,7 +481,6 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) [InlineData(40613)] [InlineData(42108)] [InlineData(42109)] - [Trait("Category", "flaky")] // [errorcode: 40613] Assert.Equal() Failure: Values differ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint errorCode) { // Arrange @@ -611,24 +522,11 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - /* TODO: Fix flaky Assert.Equal() in this test method; - Assert.Equal() Failure: Values differ - Expected: 2 - Actual: 3 - */ Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); // Login-phase errors must NOT trigger failover alternation Assert.Equal(0, failoverServer.PreLoginCount); } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail(errorCode: 42109) [2 s] - // EXEC : error Message: [/Users/runner/work/1/s/build2.proj] - // Assert.Fail() Failure - // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail(UInt32 errorCode) in /Users/runner/work/1/s/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs:line 526 - // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Theory] [InlineData(40613)] [InlineData(42108)] @@ -674,13 +572,11 @@ public void TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail(uint Assert.Equal((int)errorCode, e.Number); return; } - // TODO: Fix flaky Assert.Fail() in this test method; - // [errorcode: 42109] Assert.Fail() Failure + Assert.Fail(); } [Fact] - [Trait("Category", "flaky")] // Assert.Equal() Failure: Values differ | System.ComponentModel.Win32Exception : The wait operation timed out public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUserProvidedPartner() { // Arrange @@ -718,12 +614,6 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs SqlConnection connection = new(builder.ConnectionString); // Connect once to the primary to trigger it to send the failover partner - /* TODO: Fix flaky test failure: - Microsoft.Data.SqlClient.SqlException : 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: 0 - The wait operation timed out.) - ---- System.ComponentModel.Win32Exception : The wait operation timed out. - */ connection.Open(); Assert.Equal("invalidhost", (connection.InnerConnection as SqlConnectionInternal)!.ServerProvidedFailoverPartner); @@ -746,20 +636,8 @@ The server was not found or was not accessible. Verify that the instance name is // Assert Assert.Equal(ConnectionState.Open, failoverConnection.State); - /* TODO: Fix flaky Assert.Equal() in this test method; - Assert.Equal() Failure: Strings differ - ↓ (pos 13) - Expected: "localhost,51479" - Actual: "localhost,51480" - ↑ (pos 13) - */ Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", failoverConnection.DataSource); // 1 for the initial connection - /* TODO: Fix flaky Assert.Equal() in this test method; - Assert.Equal() Failure: Values differ - Expected: 1 - Actual: 2 - */ Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); // 1 for the failover connection Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); @@ -819,18 +697,6 @@ public async Task TransientFault_Async_ShouldConnectToPrimary_NotFailover(uint e /// Async parity with user-provided partner: login-phase transient SQL errors should /// still retry on primary without failover alternation. /// - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Async_ShouldConnectToPrimary_NotFailover(errorCode: 42109) [4 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Assert.Equal() Failure: Strings differ - // Γåô (pos 14) - // Expected: "localhost,60625" - // Actual: "localhost,60624" - // Γåæ (pos 14) - // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Async_ShouldConnectToPrimary_NotFailover(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 816 - // --- End of stack trace from previous location --- [Theory] [InlineData(40613)] [InlineData(42108)] @@ -882,19 +748,26 @@ public async Task TransientFault_WithUserProvidedPartner_Async_ShouldConnectToPr /// Verifies pooled connections are not cleared and failover is not attempted when a /// login-phase transient SQL error occurs with a user-provided failover partner. /// - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(errorCode: 40613) [2 s] + // + // Flaky under CI load only (never reproduces locally): the connection intermittently + // reports the failover partner's port for connection.DataSource (primary port - 1), + // i.e. the driver occasionally fails over on a login-phase transient error instead of + // retrying the primary. This is the failover-alternation / parser-state timing behavior + // this test guards, not a harness race, so it cannot be made deterministic here. + // + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(errorCode: 40613) [3 s] // ##[error]EXEC(0,0): Error Message: // EXEC : error Message: [D:\a\_work\1\s\build.proj] // Assert.Equal() Failure: Strings differ // Γåô (pos 14) - // Expected: "localhost,50152" - // Actual: "localhost,50151" + // Expected: "localhost,49182" + // Actual: "localhost,49181" // Γåæ (pos 14) // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 920 + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 777 // at InvokeStub_ConnectionFailoverTests.TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_NotFailover(Object, Span`1) // at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) + [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -966,7 +839,6 @@ public void TransientFault_WithUserProvidedPartner_Pooling_ShouldNotClearPool_No /// and never attempts failover alternation. /// [Theory] - [Trait("Category", "flaky")] // Assert.Throws() Failure: No exception was thrown [InlineData(40613)] [InlineData(42108)] [InlineData(42109)] @@ -1005,7 +877,6 @@ public void TransientFault_RetryDisabled_WithUserProvidedPartner_ShouldFail_NotF using SqlConnection connection = new(builder.ConnectionString); // No outer connect retry is allowed, so the first transient error should surface. - // TODO: Fix flakiness of Assert.Throws() in this test method; currently, if the exception is not thrown as expected, // Assert.Throws will fail the test with an assertion failure ("No exception was thrown"). SqlException ex = Assert.Throws(() => connection.Open()); @@ -1020,17 +891,6 @@ public void TransientFault_RetryDisabled_WithUserProvidedPartner_ShouldFail_NotF /// Isolates the parser-state guard by using a non-fatal login error token: without the /// guard, LoginWithFailover alternates to partner; with the guard, retry stays on primary. /// - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrimary_NotFailover [1 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [/mnt/vss/_work/1/s/build.proj] - // Assert.Equal() Failure: Values differ - // Expected: 0 - // Actual: 1 - // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrimary_NotFailover() in /mnt/vss/_work/1/s/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs:line 1006 - // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - // at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) [Fact] public void NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrimary_NotFailover() { @@ -1081,19 +941,6 @@ public void NonFatalTransientLoginError_WithUserProvidedPartner_ShouldRetryPrima /// Verifies opt-in legacy behavior: login-phase SQL errors can alternate to the /// failover partner when UseLegacyFailoverAlternationOnLoginSqlErrors is enabled. /// - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NonFatalTransientLoginError_WithLegacySwitch_ShouldAlternateToFailoverPartner [2 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Assert.Equal() Failure: Strings differ - // Γåô (pos 14) - // Expected: "localhost,60991" - // Actual: "localhost,60992" - // Γåæ (pos 14) - // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NonFatalTransientLoginError_WithLegacySwitch_ShouldAlternateToFailoverPartner() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionFailoverTests.cs:line 1114 - // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - // at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) [Fact] public void NonFatalTransientLoginError_WithLegacySwitch_ShouldAlternateToFailoverPartner() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs index c5bd6edf57..935167e1cf 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs @@ -104,17 +104,6 @@ public void TransientFaultAtRoutedLocation_RetryDisabled_ShouldFail(uint errorCo Assert.Throws(() => connection.Open()); } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionRoutingTests.NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed(multiSubnetFailoverEnabled: True) [2 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build2.proj] - // Assert.Equal() Failure: Values differ - // Expected: 1 - // Actual: 2 - // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionRoutingTests.NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed(Boolean multiSubnetFailoverEnabled) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionRoutingTests.cs:line 147 - // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Theory] [InlineData(true)] [InlineData(false)] @@ -161,7 +150,9 @@ public void NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed(bool multiS Assert.Equal(1, router.PreLoginCount); if (multiSubnetFailoverEnabled) { - Assert.True(server.PreLoginCount > 1); + // MultiSubnetFailover fan-out count is DNS/timing-dependent; only assert a + // completed pre-login at the routed location. + Assert.True(server.PreLoginCount - server.AbandonedPreLoginCount >= 1); } else { @@ -170,7 +161,6 @@ public void NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed(bool multiS } [Fact] - [Trait("Category", "flaky")] public void NetworkTimeoutAtRoutedLocation_RetryDisabled_ShouldFail() { // Arrange diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs index f75514dae2..7009c003d4 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs @@ -161,7 +161,6 @@ public void NetworkDelayAtRoutedLocation_RetryDisabled_ShouldSucceed() } [Fact] - [Trait("Category", "flaky")] public void NetworkTimeoutAtRoutedLocation_RetryDisabled_ShouldFail() { // Arrange diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index 10cd111a16..8a848c365c 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -8,6 +8,8 @@ using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Reflection; using System.Security; using System.Threading; @@ -33,6 +35,7 @@ public void ConnectionTest() { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // No pooling needed; avoids leaking a pooled connection to this ephemeral port }.ConnectionString; using SqlConnection connection = new(connStr); connection.Open(); @@ -48,6 +51,7 @@ public void IntegratedAuthConnectionTest() { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // No pooling needed; avoids leaking a pooled connection to this ephemeral port }.ConnectionString; SqlConnectionStringBuilder builder = new(connStr); builder.IntegratedSecurity = true; @@ -76,30 +80,6 @@ public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() Assert.Contains("The instance of SQL Server you attempted to connect to does not support encryption.", ex.Message, StringComparison.OrdinalIgnoreCase); } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed_Async(errorCode: 40613) [6 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Microsoft.Data.SqlClient.SqlException : - // Stack Trace: - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1303 - // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1739 - // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 3139 - // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2362 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2259 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2223 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3266 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3836 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 443 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 697 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreatePooledConnection(DbConnection owningConnection, IDbConnectionPool pool, DbConnectionPoolKey poolKey, DbConnectionOptions options, DbConnectionOptions userOptions) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 150 - // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 528 - // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 1544 - // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 987 - // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.WaitForPendingOpen() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 834 - // --- End of stack trace from previous location --- - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed_Async(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionTests.cs:line 101 - // --- End of stack trace from previous location --- [Theory] [InlineData(40613)] @@ -130,37 +110,6 @@ public async Task TransientFault_RetryEnabled_ShouldSucceed_Async(uint errorCode Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed(errorCode: 40613) [5 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Microsoft.Data.SqlClient.SqlException : - // Stack Trace: - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1241 - // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1721 - // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 3119 - // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2343 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2208 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2170 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3214 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionOptions connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3793 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, TimeoutTimer timeout, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 399 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 687 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreatePooledConnection(DbConnection owningConnection, IDbConnectionPool pool, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 148 - // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionInternal oldConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 535 - // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.UserCreateRequest(DbConnection owningObject, TimeoutTimer timeout, DbConnectionInternal oldConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 1648 - // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, TimeoutTimer timeout, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 1026 - // at Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 taskCompletionSource, TimeoutTimer timeout, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs:line 879 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionInternal oldConnection, TimeoutTimer timeout, Boolean forceNewConnection, DbConnectionInternal& connection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 431 - // at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, Boolean forceNewConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\ProviderBase\DbConnectionInternal.cs:line 940 - // at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\ProviderBase\DbConnectionClosed.cs:line 69 - // at Microsoft.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry, Boolean forceNewConnection) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 2287 - // at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, Boolean forceNewConnection, SqlConnectionOverrides overrides) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 2245 - // at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1619 - // at Microsoft.Data.SqlClient.SqlConnection.Open() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1596 - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed(UInt32 errorCode) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionTests.cs:line 152 - // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Theory] [InlineData(40613)] [InlineData(42108)] @@ -243,40 +192,6 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async(multiSubnetFailoverEnabled: True) [4 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=5; handshake=43; [Login] initialization=0; authentication=0; [Post-Login] complete=4838; - // ---- System.ComponentModel.Win32Exception : The wait operation timed out. - // Stack Trace: - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1241 - // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1721 - // at Microsoft.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1243 - // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 4168 - // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3581 - // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3480 - // at Microsoft.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1464 - // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1780 - // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2456 - // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2343 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2208 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2170 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3214 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionOptions connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3793 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(DbConnectionPoolIdentity identity, SqlConnectionOptions connectionOptions, TimeoutTimer timeout, SqlCredential credential, DbConnectionPoolGroupProviderInfo providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken, IDbConnectionPool pool, Func`3 accessTokenCallback, SspiContextProvider sspiContextProvider) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 399 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 687 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 124 - // at Microsoft.Data.SqlClient.SqlConnectionFactory.<>c__DisplayClass41_0.b__0(Task`1 _) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnectionFactory.cs:line 777 - // at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke() - // at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) - // --- End of stack trace from previous location --- - // at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) - // at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) - // --- End of stack trace from previous location --- - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async(Boolean multiSubnetFailoverEnabled) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionTests.cs:line 237 - // --- End of stack trace from previous location --- - // ----- Inner Stack Trace ----- [Theory] [InlineData(false)] [InlineData(true)] @@ -305,14 +220,13 @@ public async Task NetworkError_RetryEnabled_ShouldSucceed_Async(bool multiSubnet await connection.OpenAsync(); Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); - if (multiSubnetFailoverEnabled) - { - Assert.True(server.PreLoginCount > 1, "Expected multiple pre-login attempts due to retry."); - } - else - { - Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); - } + // The transient delay (1s) is shorter than the connect timeout (5s), so the + // connection succeeds. With MultiSubnetFailover the driver may fan out parallel + // attempts across the dual-stack resolution of localhost, but the exact number + // is a DNS/timing-dependent implementation detail, so we only assert that at + // least one completed pre-login occurred. + Assert.True(server.PreLoginCount - server.AbandonedPreLoginCount >= 1, + "Expected at least one completed pre-login."); } [Theory] @@ -351,7 +265,11 @@ public async Task NetworkDelay_RetryDisabled_Async(bool multiSubnetFailoverEnabl if (multiSubnetFailoverEnabled) { - Assert.True(server.PreLoginCount > 1, "Expected multiple pre-login attempts due to retry."); + // With MultiSubnetFailover the driver may fan out parallel attempts across + // the dual-stack resolution of localhost; the exact count is a DNS/timing- + // dependent implementation detail, so only assert a completed pre-login. + Assert.True(server.PreLoginCount - server.AbandonedPreLoginCount >= 1, + "Expected at least one completed pre-login."); } else { @@ -359,21 +277,6 @@ public async Task NetworkDelay_RetryDisabled_Async(bool multiSubnetFailoverEnabl } } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkDelay_RetryDisabled(multiSubnetFailoverEnabled: False) [4 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [D:\a\_work\1\s\build.proj] - // Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=0; handshake=25; [Login] initialization=0; authentication=0; [Post-Login] complete=4004; - // ---- System.ComponentModel.Win32Exception : The wait operation timed out. - // Stack Trace: - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 1241 - // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1721 - // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 4168 - // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 3581 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 2208 - // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\Connection\SqlConnectionInternal.cs:line 3214 - // at Microsoft.Data.SqlClient.SqlConnection.Open() in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\src\Microsoft\Data\SqlClient\SqlConnection.cs:line 1596 - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkDelay_RetryDisabled(Boolean multiSubnetFailoverEnabled) in D:\a\_work\1\s\src\Microsoft.Data.SqlClient\tests\UnitTests\SimulatedServerTests\ConnectionTests.cs:line 387 [Theory] [InlineData(true)] [InlineData(false)] @@ -410,7 +313,11 @@ public void NetworkDelay_RetryDisabled(bool multiSubnetFailoverEnabled) if (multiSubnetFailoverEnabled) { - Assert.True(server.PreLoginCount > 1, "Expected multiple pre-login attempts due to retry."); + // With MultiSubnetFailover the driver may fan out parallel attempts across + // the dual-stack resolution of localhost; the exact count is a DNS/timing- + // dependent implementation detail, so only assert a completed pre-login. + Assert.True(server.PreLoginCount - server.AbandonedPreLoginCount >= 1, + "Expected at least one completed pre-login."); } else { @@ -566,105 +473,122 @@ public void ConnectionTestValidCredentialCombination() } - [Trait("Category", "flaky")] - // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.ConnectionTimeoutTest(timeout: 60) [10 s] - // ##[error]EXEC(0,0): Error Message: - // EXEC : error Message: [/mnt/vss/_work/1/s/build.proj] - // Timer must be stopped. - // Stack Trace: - // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.ConnectionTimeoutTest(Int32 timeout) in /mnt/vss/_work/1/s/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs:line 523 - // at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) - // at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) [Theory] - [InlineData(60)] - [InlineData(10)] [InlineData(1)] public void ConnectionTimeoutTest(int timeout) { - // Start a server with connection timeout from the inline data. - //TODO: do we even need a server for this test? - using TdsServer server = new(); - server.Start(); - var connStr = new SqlConnectionStringBuilder() + // A black-hole listener accepts the TCP connection at the OS level but + // never speaks TDS, so the driver times out waiting for the pre-login + // response. The listener is held open for the lifetime of the test, so + // its port cannot be recycled by another concurrently-running test. + // Reusing a just-freed ephemeral port was the root cause of this test's + // flakiness (a sibling server would answer and the connection would + // unexpectedly succeed). + // + // TcpListener does not implement IDisposable on .NET Framework, so it is + // stopped in a finally block rather than with a using statement. + TcpListener blackHole = new(IPAddress.Loopback, 0); + blackHole.Start(); + try { - DataSource = $"localhost,{server.EndPoint.Port}", - ConnectTimeout = timeout, - Encrypt = SqlConnectionEncryptOption.Optional, - Pooling = false, // Disable pooling so this expected timeout failure does not poison a shared pool - }.ConnectionString; - using SqlConnection connection = new(connStr); + int port = ((IPEndPoint)blackHole.LocalEndpoint).Port; - // Dispose the server to force connection timeout - server.Dispose(); + var connStr = new SqlConnectionStringBuilder() + { + // Target 127.0.0.1 explicitly (not "localhost") so the client always + // connects to the IPv4 black-hole listener above rather than resolving to + // ::1, which would produce connection-refused instead of a pre-login timeout. + DataSource = $"127.0.0.1,{port}", + ConnectTimeout = timeout, + ConnectRetryCount = 0, // Single timeout attempt; no retry that would extend the wall clock + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // Disable pooling so this expected timeout failure does not poison a shared pool + }.ConnectionString; + using SqlConnection connection = new(connStr); - // Measure the actual time it took to timeout and compare it with configured timeout - Stopwatch timer = new(); - Exception? ex = null; + // Measure the actual time it took to timeout and compare it with configured timeout + Stopwatch timer = new(); + Exception? ex = null; - // Open a connection with the server disposed. - try - { - timer.Start(); - connection.Open(); + try + { + timer.Start(); + connection.Open(); + } + catch (Exception e) + { + timer.Stop(); + ex = e; + } + + Assert.False(timer.IsRunning, "Timer must be stopped."); + Assert.NotNull(ex); + Assert.True(timer.Elapsed.TotalSeconds <= timeout + 3, + $"The actual timeout {timer.Elapsed.TotalSeconds} is expected to be less than {timeout} plus 3 seconds additional threshold." + + $"{Environment.NewLine}{ex}"); } - catch (Exception e) + finally { - timer.Stop(); - ex = e; + blackHole.Stop(); } - - Assert.False(timer.IsRunning, "Timer must be stopped."); - Assert.NotNull(ex); - Assert.True(timer.Elapsed.TotalSeconds <= timeout + 3, - $"The actual timeout {timer.Elapsed.TotalSeconds} is expected to be less than {timeout} plus 3 seconds additional threshold." + - $"{Environment.NewLine}{ex}"); } [Theory] - [InlineData(60)] - [InlineData(10)] [InlineData(1)] public async Task ConnectionTimeoutTestAsync(int timeout) { - // Start a server with connection timeout from the inline data. - //TODO: do we even need a server for this test? - using TdsServer server = new(); - server.Start(); - var connStr = new SqlConnectionStringBuilder() + // See ConnectionTimeoutTest for why a held-open black-hole listener is + // used instead of disposing a server and reusing its port. TcpListener + // does not implement IDisposable on .NET Framework, so it is stopped in a + // finally block rather than with a using statement. + TcpListener blackHole = new(IPAddress.Loopback, 0); + blackHole.Start(); + try { - DataSource = $"localhost,{server.EndPoint.Port}", - ConnectTimeout = timeout, - Encrypt = SqlConnectionEncryptOption.Optional, - Pooling = false, // Disable pooling so this expected timeout failure does not poison a shared pool - }.ConnectionString; - using SqlConnection connection = new(connStr); + int port = ((IPEndPoint)blackHole.LocalEndpoint).Port; - // Dispose the server to force connection timeout - server.Dispose(); + var connStr = new SqlConnectionStringBuilder() + { + // Target 127.0.0.1 explicitly (not "localhost") so the client always + // connects to the IPv4 black-hole listener above rather than resolving to + // ::1, which would produce connection-refused instead of a pre-login timeout. + DataSource = $"127.0.0.1,{port}", + ConnectTimeout = timeout, + ConnectRetryCount = 0, // Single timeout attempt; no retry that would extend the wall clock + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // Disable pooling so this expected timeout failure does not poison a shared pool + }.ConnectionString; + using SqlConnection connection = new(connStr); - // Measure the actual time it took to timeout and compare it with configured timeout - Stopwatch timer = new(); - Exception? ex = null; + // Measure the actual time it took to timeout and compare it with configured timeout + Stopwatch timer = new(); + Exception? ex = null; - // Open a connection with the server disposed. - try - { - //an asyn call with a timeout token to cancel the operation after the specific time - using CancellationTokenSource cts = new(timeout * 1000); - timer.Start(); - await connection.OpenAsync(cts.Token).ConfigureAwait(false); + try + { + // The cancellation token is only a safety net: it is set well beyond + // ConnectTimeout so the failure we observe is the driver's own connection + // timeout, not an external cancellation. + using CancellationTokenSource cts = new((timeout + 30) * 1000); + timer.Start(); + await connection.OpenAsync(cts.Token).ConfigureAwait(false); + } + catch (Exception e) + { + timer.Stop(); + ex = e; + } + + Assert.False(timer.IsRunning, "Timer must be stopped."); + Assert.NotNull(ex); + Assert.True(timer.Elapsed.TotalSeconds <= timeout + 3, + $"The actual timeout {timer.Elapsed.TotalSeconds} is expected to be less than {timeout} plus 3 seconds additional threshold." + + $"{Environment.NewLine}{ex}"); } - catch (Exception e) + finally { - timer.Stop(); - ex = e; + blackHole.Stop(); } - - Assert.False(timer.IsRunning, "Timer must be stopped."); - Assert.NotNull(ex); - Assert.True(timer.Elapsed.TotalSeconds <= timeout + 3, - $"The actual timeout {timer.Elapsed.TotalSeconds} is expected to be less than {timeout} plus 3 seconds additional threshold." + - $"{Environment.NewLine}{ex}"); } [Fact] @@ -698,7 +622,8 @@ public void ConnectionTestWithCultureTH() var connStr = new SqlConnectionStringBuilder() { DataSource = $"localhost,{server.EndPoint.Port}", - Encrypt = SqlConnectionEncryptOption.Optional + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // No pooling needed; avoids leaking a pooled connection to this ephemeral port }.ConnectionString; using SqlConnection connection = new(connStr); connection.Open(); @@ -819,6 +744,7 @@ public void ConnectionTestPermittedVersion(int major, int minor, int build) { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // No pooling needed; avoids leaking a pooled connection to this ephemeral port }.ConnectionString; using SqlConnection conn = new(connStr); @@ -1014,6 +940,7 @@ public void TestConnWithUserAgentFeatureExtension(bool sendAck) { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, // No pooling needed; avoids leaking a pooled connection to this ephemeral port }.ConnectionString; using var connection = new SqlConnection(connStr); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerEndPoint.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerEndPoint.cs index 349b23ceee..2717356f43 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerEndPoint.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerEndPoint.cs @@ -117,6 +117,20 @@ public void Stop() // Request the listener thread to stop StopRequested = true; + // Stop the listener socket first so the blocking AcceptTcpClient call + // in the listener thread is interrupted and the thread can exit. + if (ListenerSocket != null) + { + ListenerSocket.Stop(); + } + + // If server failed to start there is no thread to join + if (ListenerThread != null) + { + // Wait for termination + ListenerThread.Join(); + } + // A copy of the list of connections to avoid locking IList unlockedConnections = new List(); @@ -137,20 +151,8 @@ public void Stop() connection.Dispose(); } - // If server failed to start there is no thread to join - if (ListenerThread != null) - { - // Wait for termination - ListenerThread.Join(); - } - - // If server failed to start there is no listener associated with it - if (ListenerSocket != null) - { - // Stop the server - ListenerSocket.Stop(); - ListenerSocket = null; - } + // Release the listener socket reference + ListenerSocket = null; } public void Dispose() @@ -166,45 +168,64 @@ private void _RequestListener() { try { - // Accept connection as long as stop request is not posted + // Accept connections as long as a stop request is not posted while (!StopRequested) { - // Check if we have a connection request pending - if (ListenerSocket.Pending()) + TcpClient newConnection; + + try { - try + // Block until a client connects. This call is interrupted + // by Stop() closing the listener socket during shutdown. + newConnection = ListenerSocket.AcceptTcpClient(); + } + catch (Exception) + { + // AcceptTcpClient throws when the listener is stopped during + // shutdown; exit the loop in that case. + if (StopRequested) { - // Accept the connection - TcpClient newConnection = ListenerSocket.AcceptTcpClient(); + break; + } - // Create a new connection - T connection = CreateConnection(newConnection); + throw; + } - // Assign a log - connection.EventLog = EventLog; + try + { + // Create a new connection + T connection = CreateConnection(newConnection); - // Subscribe for notifications - connection.OnConnectionClosed += new ConnectionClosedEventHandler(_OnConnectionClosed); + // Assign a log + connection.EventLog = EventLog; - // Start a connection - connection.Start(); + // Subscribe for notifications + connection.OnConnectionClosed += new ConnectionClosedEventHandler(_OnConnectionClosed); - // Synchronize access to connections collection - lock (Connections) - { - // Register a new connection - Connections.Add(connection); - } - } - catch (Exception ex) + // Start a connection + connection.Start(); + + // Synchronize access to connections collection + lock (Connections) { - Log(ex.ToString()); + // Register a new connection + Connections.Add(connection); } } - else + catch (Exception ex) { - // Pause a bit - Thread.Sleep(10); + Log(ex.ToString()); + + // Setup failed, so this connection was never registered and will not be + // torn down by Stop(); dispose the accepted socket to avoid leaking it. + try + { + newConnection?.Dispose(); + } + catch (Exception disposeEx) + { + Log(disposeEx.ToString()); + } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerEndPointConnection.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerEndPointConnection.cs index 3d24f9c397..948bd6bcae 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerEndPointConnection.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerEndPointConnection.cs @@ -52,6 +52,11 @@ public abstract class ServerEndPointConnection : IDisposable /// protected Task ProcessorTask { get; set; } + /// + /// Signals the processor loop to stop. + /// + private volatile bool _stopRequested; + /// /// Gets/Sets the event log for the proxy server /// @@ -88,8 +93,10 @@ public ServerEndPointConnection(ITDSServer server, TcpClient connection) // Save TCP connection Connection = connection; - // Configure timeouts - Connection.ReceiveTimeout = 1000; + // Note: no artificial socket receive timeout is configured. A blocked + // read is unblocked deterministically when the socket is closed during + // Dispose(). A short receive timeout here previously aborted parsing + // mid-message under CI CPU load, which was a source of test flakiness. // Create a new TDS server session Session = server.OpenSession(); @@ -120,8 +127,8 @@ public ServerEndPointConnection(ITDSServer server, TcpClient connection) /// internal void Start() { - // Prepare and start a thread - ProcessorTask = RunConnectionHandler(); + // Prepare and start the processor on a background task. + ProcessorTask = Task.Run(RunConnectionHandler); } /// @@ -136,63 +143,100 @@ internal void Start() public void Dispose() { + // Signal the processor loop to stop. + _stopRequested = true; + + // Close the socket first. This unblocks any synchronous read or poll + // the processor task may be waiting on, causing it to exit its loop. if (Connection != null) { - Connection.Close(); - Connection.Dispose(); + try + { + Connection.Close(); + Connection.Dispose(); + } + catch (Exception) + { + // Ignore errors closing the socket during teardown. + } + Connection = null; } - // TODO: there's a deadlock condition when awaiting the processor task - // only dispose of it if it's already completed - if (ProcessorTask.Status == TaskStatus.RanToCompletion) + // Deterministically wait for the processor task to finish so that no + // background work (socket I/O, counter mutation) outlives Dispose(). + // A bounded wait guards against an unexpected hang. + try + { + // Surface a hang: if the processor task does not complete within the bound, + // log it rather than silently returning while background work may continue. + if (ProcessorTask != null && !ProcessorTask.Wait(TimeSpan.FromSeconds(30))) + { + Log("Processor task did not complete within 30 seconds during Dispose."); + } + } + catch (AggregateException) { - ProcessorTask.Dispose(); + // Exceptions observed by the processor task are already logged in + // RunConnectionHandler; nothing else to do here. } } /// - /// Worker thread + /// Worker thread that processes the TDS packet stream for this connection. /// - private async Task RunConnectionHandler() + private void RunConnectionHandler() { + TcpClient connection = Connection; + + // Dispose() may have already closed and nulled the connection before this task + // began running. Treat that as a normal (already torn down) shutdown rather than + // dereferencing a null Connection and logging a spurious error. + if (connection == null) + { + OnConnectionClosed?.Invoke(this, null); + return; + } + try { // Get network stream - NetworkStream rawStream = Connection.GetStream(); + NetworkStream rawStream = connection.GetStream(); PrepareForProcessingData(rawStream); - // Process the packet sequence - while (Connection.Connected) + // Process the packet sequence until the peer disconnects or the + // server requests a stop. + while (!_stopRequested && connection.Connected) { // Check incoming buffer if (rawStream.DataAvailable) { ProcessData(rawStream); } - else + else if (connection.Client.Poll(100_000 /* 100 ms */, SelectMode.SelectRead) + && !rawStream.DataAvailable) { - // Poll the socket for data - if (Connection.Client.Poll(100, SelectMode.SelectRead) && !rawStream.DataAvailable) - { - break; - } - - // Sleep a bit to reduce load on CPU - await Task.Delay(10); + // Poll reports readable with no data available when the + // peer has closed the connection. + break; } } } catch (Exception ex) { - // Log exception - Log(ex.ToString()); + // A socket close during Dispose is an expected part of teardown, so only log + // when a stop was not requested. This keeps real failures visible in CI logs + // without the teardown noise. + if (!_stopRequested) + { + Log(ex.ToString()); + } } try { // Disconnect the client - Connection.Close(); + connection?.Close(); } catch (Exception) { @@ -200,12 +244,7 @@ private async Task RunConnectionHandler() } // Notify subscribers that this connection is closed - if (OnConnectionClosed != null) - { - OnConnectionClosed(this, null); - } - - return; + OnConnectionClosed?.Invoke(this, null); } /// diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientDelayTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientDelayTdsServer.cs index d0a15e90ae..e62f1d6d5b 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientDelayTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientDelayTdsServer.cs @@ -15,6 +15,18 @@ public class TransientDelayTdsServer : GenericTdsServer + /// Cancelled on Dispose to interrupt any in-progress delay so that the + /// processor task can complete promptly and Dispose does not block + /// waiting for a sleep to elapse. + /// + private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource(); + + /// + /// Guards against running Dispose logic more than once. + /// + private bool _disposed; + public TransientDelayTdsServer(TransientDelayTdsServerArguments arguments) : base(arguments) { @@ -28,8 +40,37 @@ public TransientDelayTdsServer(TransientDelayTdsServerArguments arguments, Query /// public override void Dispose() { - base.Dispose(); - RequestCounter = 0; + // Guard against multiple Dispose calls: cancelling/disposing the CTS twice would + // throw ObjectDisposedException and break test teardown paths. + if (_disposed) + { + return; + } + _disposed = true; + + try + { + // Wake any in-progress delay before joining the processor task. + _disposeCts.Cancel(); + base.Dispose(); + RequestCounter = 0; + } + finally + { + // Always release the CTS, even if base.Dispose() throws. + _disposeCts.Dispose(); + } + } + + /// + /// Waits for the configured delay, returning early if the server is being + /// disposed. + /// + private void Delay() + { + // WaitHandle.WaitOne returns immediately once the token is cancelled; + // otherwise it blocks for the full delay duration. + _disposeCts.Token.WaitHandle.WaitOne(Arguments.DelayDuration); } /// @@ -41,9 +82,9 @@ public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, if (Arguments.IsEnabledPermanentDelay || (Arguments.IsEnabledTransientDelay && RequestCounter < Arguments.RepeatCount)) { - Thread.Sleep(Arguments.DelayDuration); + Delay(); - RequestCounter++; + Interlocked.Increment(ref RequestCounter); } // Return login response from the base class @@ -56,9 +97,9 @@ public override TDSMessageCollection OnSQLBatchRequest(ITDSServerSession session if (Arguments.IsEnabledPermanentDelay || (Arguments.IsEnabledTransientDelay && RequestCounter < Arguments.RepeatCount)) { - Thread.Sleep(Arguments.DelayDuration); + Delay(); - RequestCounter++; + Interlocked.Increment(ref RequestCounter); } return base.OnSQLBatchRequest(session, message); diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs index ba20fb4d02..780ca6aea9 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs @@ -106,7 +106,7 @@ private TDSMessageCollection GenerateErrorMessage(TDSMessage request) // Serialize DONE token into the response packet responseMessage.Add(doneToken); - RequestCounter++; + Interlocked.Increment(ref RequestCounter); // Put a single message into the collection and return it return new TDSMessageCollection(responseMessage); From 8c9d41ee0cf24d0478b0ebf5bcb232795c6fb663 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 24 Jul 2026 06:53:12 -0300 Subject: [PATCH 204/298] Tests: SNIClose close-during-async-IO regression tests AB#43847 (#4420) --- .../SQL/AsyncTest/MarsCloseDeadlockTest.cs | 163 +++ .../SQL/AsyncTest/MarsCloseStressTest.cs | 165 +++ .../SNICloseDeadlockTest.cs | 596 +++++++++++ .../SNICloseHandshakeCancellationTest.cs | 180 ++++ .../SNICloseRaceDeadlockTest.cs | 192 ++++ tools/SniCloseLegacyRepro/AssemblyInfo.cs | 12 + tools/SniCloseLegacyRepro/Compat.cs | 117 +++ .../SniCloseLegacyRepro/Directory.Build.props | 14 + .../Directory.Packages.props | 22 + tools/SniCloseLegacyRepro/GlobalUsings.cs | 26 + tools/SniCloseLegacyRepro/README.md | 270 +++++ tools/SniCloseLegacyRepro/ReproAttempts.cs | 965 ++++++++++++++++++ .../SniCloseLegacyRepro.csproj | 115 +++ .../SniCloseLegacyRepro/run-in-container.ps1 | 114 +++ 14 files changed, 2951 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseDeadlockTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseStressTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseDeadlockTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseHandshakeCancellationTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseRaceDeadlockTest.cs create mode 100644 tools/SniCloseLegacyRepro/AssemblyInfo.cs create mode 100644 tools/SniCloseLegacyRepro/Compat.cs create mode 100644 tools/SniCloseLegacyRepro/Directory.Build.props create mode 100644 tools/SniCloseLegacyRepro/Directory.Packages.props create mode 100644 tools/SniCloseLegacyRepro/GlobalUsings.cs create mode 100644 tools/SniCloseLegacyRepro/README.md create mode 100644 tools/SniCloseLegacyRepro/ReproAttempts.cs create mode 100644 tools/SniCloseLegacyRepro/SniCloseLegacyRepro.csproj create mode 100644 tools/SniCloseLegacyRepro/run-in-container.ps1 diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseDeadlockTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseDeadlockTest.cs new file mode 100644 index 0000000000..dce3471791 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseDeadlockTest.cs @@ -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. + +#nullable enable + +using System; +using System.Data; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + /// + /// Live-server regression test for ADO.Net work item 43847 / ICM 775308542: + /// "SNIClose can deadlock with in-flight async I/O during connection close". + /// + /// + /// This is the MARS-enabled counterpart to the in-process + /// SNICloseDeadlockTest unit tests. The native Dispose() path + /// in TdsParserStateObjectNative carries a long-standing "UNDONE" + /// note about needing to block for pending callbacks on logical + /// (MARS) connections during close. The in-process TDS test server does not + /// support MARS, so exercising the SMUX logical-session close path requires + /// a real SQL Server. + /// + /// + /// + /// The test enables MARS and starts an asynchronous read of a batch that the + /// server deliberately withholds (WAITFOR DELAY). This guarantees the + /// client's async read is genuinely in flight on a MARS logical session at + /// the moment the connection is closed. The close is performed on a worker + /// thread under a bounded wait: a deadlock manifests as the wait timing out, + /// which fails the test. A healthy close aborts the in-flight command and + /// returns promptly. + /// + /// + [Trait("Set", "1")] + public class MarsCloseDeadlockTest + { + /// + /// How long the server withholds the response. Must comfortably exceed + /// so that a prompt close is attributable to + /// the close path aborting the command, not to the query completing on + /// its own. + /// + private const string StallDelay = "00:00:30"; + + /// + /// Upper bound for how long a non-deadlocked close should take. Close of + /// a connection with an in-flight command is effectively instantaneous; + /// this budget only distinguishes "completed" from "deadlocked" without + /// hanging the run on a slow agent. + /// + private static readonly TimeSpan CloseBudget = TimeSpan.FromSeconds(15); + + /// + /// How long to wait to confirm the async read is genuinely pending (not + /// completed) before closing. Small relative to . + /// + private static readonly TimeSpan PendingConfirmDelay = TimeSpan.FromSeconds(3); + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData(false)] + [InlineData(true)] + public void CloseOrDispose_WithPendingMarsAsyncRead_DoesNotDeadlock(bool disposeInsteadOfClose) + { + // Enable MARS so the async read runs over a SMUX logical session, and + // disable pooling so Close()/Dispose() tears down the physical + // connection (reaching SNIClose) instead of returning it to the pool. + string connectionString = + new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + { + MultipleActiveResultSets = true, + Pooling = false, + }.ConnectionString; + + SqlConnection connection = new(connectionString); + SqlCommand? command = null; + Task? readTask = null; + bool closeAttempted = false; + bool closedInTime = false; + try + { + connection.Open(); + + command = new($"WAITFOR DELAY '{StallDelay}'; SELECT 1;", connection); + + // Start an async read that will pend: the server withholds the + // response for StallDelay, so this Task will not complete until + // we either wait out the delay or tear down the connection. + readTask = command.ExecuteReaderAsync(); + + // Confirm the read is genuinely in flight before we close: it + // must not have completed and the connection must still be open. + Assert.False( + readTask.Wait(PendingConfirmDelay), + "The async read completed before the server released the " + + "response; the read was not in flight at close time."); + Assert.Equal(ConnectionState.Open, connection.State); + + // Close/dispose the connection on a dedicated, long-running + // thread while the MARS async read is in flight. This is the + // operation that can deadlock in SNIClose. A dedicated thread + // (rather than a thread-pool thread via Task.Run) ensures a + // hypothetical regression parks this one thread instead of + // starving the shared thread pool. + closeAttempted = true; + Task closeTask = Task.Factory.StartNew( + () => + { + if (disposeInsteadOfClose) + { + connection.Dispose(); + } + else + { + connection.Close(); + } + }, + TaskCreationOptions.LongRunning); + + closedInTime = closeTask.Wait(CloseBudget); + + Assert.True( + closedInTime, + $"{(disposeInsteadOfClose ? "Dispose()" : "Close()")} did not " + + $"complete within {CloseBudget.TotalSeconds:N0}s while a MARS " + + "async read was in flight. This indicates the SNIClose deadlock " + + "(ADO.Net #43847 / ICM 775308542)."); + } + finally + { + // Observe the pending read so its (expected) fault is not + // unobserved, even if a precondition assert above threw. + if (readTask != null) + { + try + { + readTask.Wait(CloseBudget); + } + catch + { + // The pending read is expected to fault or cancel once + // the connection is torn down. That is not what this + // test asserts. + } + } + + command?.Dispose(); + + // Dispose the connection unless a close deadlock was actually + // detected (in which case Dispose() could also block). Disposing + // is safe when the close worker never started or when it + // completed. + if (!closeAttempted || closedInTime) + { + connection.Dispose(); + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseStressTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseStressTest.cs new file mode 100644 index 0000000000..763ee12690 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/MarsCloseStressTest.cs @@ -0,0 +1,165 @@ +// 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.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + /// + /// Concurrency-stress companion to for + /// ADO.Net #43847 / ICM 775308542. + /// + /// + /// Where MarsCloseDeadlockTest closes a single connection with one + /// in-flight MARS async read, this test opens many MARS connections - + /// each with an in-flight read - and closes them all simultaneously + /// (rendezvous on a ), repeated over several iterations. + /// Piling concurrent closes onto the SMUX logical-session teardown path + /// widens the window for the close race called out by the long-standing + /// "Comment CloseMARSSession / UNDONE" note in the native Dispose(). + /// + /// + /// + /// Each batch of closes is awaited under a bounded wait: a deadlock manifests + /// as the wait timing out, which fails the test. A healthy close aborts the + /// in-flight commands and returns promptly. + /// + /// + [Trait("Set", "1")] + public class MarsCloseStressTest + { + /// + /// How long each server-side batch withholds its response. Must comfortably + /// exceed so a prompt close is attributable to the + /// close path aborting the command, not the query completing on its own. + /// Kept at 4x so the boundary is unambiguous. + /// + private const string StallDelay = "00:02:00"; + + /// + /// Upper bound for how long a batch of concurrent, non-deadlocked closes + /// should take. Generous only to avoid false positives on slow agents. + /// + private static readonly TimeSpan CloseBudget = TimeSpan.FromSeconds(30); + + /// Number of close-storm iterations. + private const int Iterations = 8; + + /// MARS connections opened and closed together per iteration. + private const int ConnectionsPerIteration = 16; + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData(false)] + [InlineData(true)] + public void MarsConcurrentCloseStress_DoesNotDeadlock(bool disposeInsteadOfClose) + { + // Enable MARS so each async read runs over a SMUX logical session, and + // disable pooling so Close()/Dispose() tears down the physical + // connection (reaching SNIClose) instead of returning it to the pool. + string connectionString = + new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + { + MultipleActiveResultSets = true, + Pooling = false, + }.ConnectionString; + + for (int iter = 0; iter < Iterations; iter++) + { + SqlConnection[] connections = new SqlConnection[ConnectionsPerIteration]; + SqlCommand?[] commands = new SqlCommand?[ConnectionsPerIteration]; + Task?[] readTasks = new Task?[ConnectionsPerIteration]; + bool allClosed = false; + try + { + for (int k = 0; k < ConnectionsPerIteration; k++) + { + connections[k] = new SqlConnection(connectionString); + connections[k].Open(); + commands[k] = new SqlCommand($"WAITFOR DELAY '{StallDelay}'; SELECT 1;", connections[k]); + readTasks[k] = commands[k]!.ExecuteReaderAsync(); + } + + // Confirm every read is genuinely in flight before closing. + for (int k = 0; k < ConnectionsPerIteration; k++) + { + Assert.False( + readTasks[k]!.Wait(TimeSpan.FromMilliseconds(50)), + $"iteration {iter}, connection {k}: the async read completed before " + + "close; it was not in flight."); + } + + // Fire all closes at once through a barrier so they pile up on + // the close path concurrently. + using Barrier gate = new(ConnectionsPerIteration); + Task[] closeTasks = new Task[ConnectionsPerIteration]; + for (int k = 0; k < ConnectionsPerIteration; k++) + { + int idx = k; + closeTasks[k] = Task.Factory.StartNew( + () => + { + gate.SignalAndWait(); + if (disposeInsteadOfClose) + { + connections[idx].Dispose(); + } + else + { + connections[idx].Close(); + } + }, + TaskCreationOptions.LongRunning); + } + + allClosed = Task.WaitAll(closeTasks, CloseBudget); + + Assert.True( + allClosed, + $"iteration {iter}: {ConnectionsPerIteration} concurrent MARS " + + $"{(disposeInsteadOfClose ? "Dispose()" : "Close()")} calls did not all " + + $"complete within {CloseBudget.TotalSeconds:N0}s. This indicates the " + + "SNIClose deadlock (ADO.Net #43847 / ICM 775308542)."); + } + finally + { + for (int k = 0; k < ConnectionsPerIteration; k++) + { + if (readTasks[k] != null) + { + try + { + readTasks[k]!.Wait(TimeSpan.FromSeconds(5)); + } + catch + { + // Reads are expected to fault or cancel on teardown. + } + } + + commands[k]?.Dispose(); + + // Only dispose connections if the closes completed; a + // deadlocked connection's Dispose() could also block. + if (allClosed) + { + try + { + connections[k]?.Dispose(); + } + catch + { + // Ignore teardown faults. + } + } + } + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseDeadlockTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseDeadlockTest.cs new file mode 100644 index 0000000000..465e284b6d --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseDeadlockTest.cs @@ -0,0 +1,596 @@ +// 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.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlServer.TDS; +using Microsoft.SqlServer.TDS.EndPoint; +using Microsoft.SqlServer.TDS.Servers; +using Microsoft.SqlServer.TDS.SQLBatch; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests; + +/// +/// Regression tests for ADO.Net work item 43847 / ICM 775308542: +/// "SNIClose can deadlock with in-flight async I/O during connection close". +/// +/// +/// These tests reproduce the ordering hazard where a connection is closed or +/// disposed while an asynchronous network read is still pending. The close +/// path must drain (or cancel) the in-flight async I/O before releasing the +/// SNI handle; if it releases the handle first, the closing thread and the +/// pending completion callback can wait on each other and deadlock. +/// +/// +/// +/// Each test uses an in-process TDS server whose query engine deliberately +/// stalls after receiving the client's SQL batch. This guarantees that the +/// client's async read for the response is genuinely in flight at the moment +/// the connection is closed. The close is performed on a worker thread and +/// wrapped in a bounded wait: a healthy close completes promptly, whereas a +/// deadlock manifests as the wait timing out, which fails the test. +/// +/// +/// +/// Expected state: these tests PASS against the current code base. +/// Microsoft.Data.SqlClient does not suffer from this deadlock: the close path +/// drains (or cancels) the in-flight async I/O and completes well within the +/// bounded wait. They therefore serve as regression guards - if a future change +/// reintroduced the SNIClose deadlock, the bounded wait would time out and the +/// tests would fail. +/// +/// +public class SNICloseDeadlockTest +{ + /// + /// Upper bound for how long a non-deadlocked close should take. Close of a + /// healthy connection is effectively instantaneous; this generous budget + /// exists only to distinguish "completed" from "deadlocked" on slow CI + /// agents without hanging the whole test run. + /// + private static readonly TimeSpan CloseBudget = TimeSpan.FromSeconds(30); + + /// + /// Upper bound for waiting on the server to receive the client's batch, + /// i.e. for the client's async read to become pending. + /// + private static readonly TimeSpan HandshakeBudget = TimeSpan.FromSeconds(30); + + /// + /// A query engine that signals when a SQL batch has arrived and then blocks + /// (withholding the response) until the test releases it. While it is + /// blocked, the client's async read for the response remains in flight. + /// + private sealed class StallingQueryEngine : QueryEngine + { + private readonly ManualResetEventSlim _batchReceived; + private readonly ManualResetEventSlim _releaseResponse; + + public StallingQueryEngine( + TdsServerArguments arguments, + ManualResetEventSlim batchReceived, + ManualResetEventSlim releaseResponse) + : base(arguments) + { + _batchReceived = batchReceived; + _releaseResponse = releaseResponse; + } + + protected override TDSMessageCollection CreateQueryResponse( + ITDSServerSession session, + TDSSQLBatchToken batchRequest) + { + // The client has sent the batch and posted an async receive for the + // response by the time we get here. Signal the test, then stall so + // the client's read stays in flight while the connection is closed. + _batchReceived.Set(); + _releaseResponse.Wait(); + return base.CreateQueryResponse(session, batchRequest); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CloseOrDispose_WithPendingAsyncRead_DoesNotDeadlock(bool disposeInsteadOfClose) + { + using ManualResetEventSlim batchReceived = new(false); + using ManualResetEventSlim releaseResponse = new(false); + + TdsServerArguments arguments = new(); + using TdsServer server = new( + new StallingQueryEngine(arguments, batchReceived, releaseResponse), + arguments); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + // Disable pooling so Close()/Dispose() tears down the physical + // connection (and reaches SNIClose) instead of returning it to the + // pool. + Pooling = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }; + + SqlConnection connection = new(builder.ConnectionString); + SqlCommand? command = null; + Task? readTask = null; + bool closeAttempted = false; + bool closedInTime = false; + try + { + connection.Open(); + + command = new("SELECT 1", connection); + + // Start an async read that will pend: the server stalls in + // CreateQueryResponse, so this Task will not complete until we + // either release the server or tear down the connection. + readTask = command.ExecuteReaderAsync(); + + // Ensure the async read is genuinely in flight before we close. + Assert.True( + batchReceived.Wait(HandshakeBudget), + "The server never received the SQL batch, so the async read was " + + "not in flight; the test cannot exercise the close-with-pending-IO path."); + + // The server is stalled withholding the response, so the client's + // async read must still be pending: the Task cannot have completed + // and the connection must still be open. If either of these is + // false, the read is not actually in flight and the test is not + // exercising the close-with-pending-IO path. + Assert.False( + readTask.IsCompleted, + "The async read completed before the server released the response; " + + "the read was not in flight at close time."); + Assert.Equal(System.Data.ConnectionState.Open, connection.State); + + // Close/dispose the connection on a dedicated, long-running thread + // while the async read is in flight. This is the operation that can + // deadlock in SNIClose. A dedicated thread (rather than a + // thread-pool thread via Task.Run) ensures a hypothetical regression + // parks this one thread instead of starving the shared thread pool. + closeAttempted = true; + Task closeTask = Task.Factory.StartNew( + () => + { + if (disposeInsteadOfClose) + { + connection.Dispose(); + } + else + { + connection.Close(); + } + }, + TaskCreationOptions.LongRunning); + + closedInTime = closeTask.Wait(CloseBudget); + + Assert.True( + closedInTime, + $"{(disposeInsteadOfClose ? "Dispose()" : "Close()")} did not complete " + + $"within {CloseBudget.TotalSeconds:N0}s while an async read was in flight. " + + "This indicates the SNIClose deadlock (ADO.Net #43847 / ICM 775308542)."); + } + finally + { + // Always release the stalled server thread, even if a precondition + // assert above threw, so the using-scoped server can be disposed + // without blocking on the query engine. + releaseResponse.Set(); + + // Observe the pending read so its (expected) fault is not unobserved. + if (readTask != null) + { + try + { + readTask.Wait(CloseBudget); + } + catch + { + // The pending read is expected to fault or cancel once the + // connection is torn down. That is not what this test asserts. + } + } + + command?.Dispose(); + + // Dispose the connection unless a close deadlock was actually + // detected (in which case Dispose() could also block indefinitely + // and defeat the bounded-wait regression signal). Disposing is safe + // when the close worker never started or when it completed. + if (!closeAttempted || closedInTime) + { + connection.Dispose(); + } + } + } + + /// + /// Reproduces the ICM scenario more faithfully: the connection is torn down + /// while it is still in the middle of connection establishment (the + /// pre-login / TLS handshake), rather than after a successful login. + /// + /// + /// A bare TCP listener accepts the socket and reads the client's pre-login + /// packet but deliberately never sends a response. This leaves the client's + /// async read for the pre-login/handshake response in flight. The close is + /// performed on a worker thread under a bounded wait; a deadlock in the + /// handshake-phase close path manifests as the wait timing out. + /// + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CloseOrDispose_DuringPreLoginHandshake_DoesNotDeadlock(bool disposeInsteadOfClose) + { + using ManualResetEventSlim clientConnected = new(false); + using ManualResetEventSlim releaseServer = new(false); + + TcpListener listener = new(IPAddress.Loopback, 0); + listener.Start(); + int port = ((IPEndPoint)listener.LocalEndpoint).Port; + + // Server: accept the socket, read the client's pre-login bytes, then + // withhold any response so the client's handshake read stays pending. + Task serverTask = Task.Run(() => + { + using TcpClient acceptedClient = listener.AcceptTcpClient(); + using NetworkStream stream = acceptedClient.GetStream(); + + try + { + // Read one byte of the client's pre-login packet. A byte here + // proves the client actually sent its pre-login and posted its + // async read for the response; we only need to know data + // arrived, so a single byte is sufficient. + if (stream.ReadByte() < 0) + { + // Client tore down without sending pre-login bytes: the + // handshake read was never actually in flight. Leave + // clientConnected unset so the test's precondition wait + // fails instead of producing a false positive. + return; + } + + clientConnected.Set(); + + // Hold the socket open (withholding the response) until the test + // releases us, so the client's read cannot complete naturally. + releaseServer.Wait(); + } + catch + { + // The client may tear down the socket; ignore. clientConnected + // stays unset unless a pre-login byte was actually observed + // above, so a failed read cannot masquerade as a pending one. + } + }); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"127.0.0.1,{port}", + // Force TLS negotiation intent during the handshake, matching the + // ICM scenario. + Encrypt = SqlConnectionEncryptOption.Mandatory, + TrustServerCertificate = true, + // Long timeout so the connect attempt does not abort on its own + // before we get a chance to close it. + ConnectTimeout = 60, + ConnectRetryCount = 0, + Pooling = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }; + + SqlConnection connection = new(builder.ConnectionString); + Task? openTask = null; + bool closeAttempted = false; + bool closedInTime = false; + try + { + // Begin connection establishment. This will not complete because + // the server never answers the pre-login handshake. + openTask = connection.OpenAsync(); + + // Wait until the server has received the client's pre-login, i.e. + // the client's handshake read is in flight. + Assert.True( + clientConnected.Wait(HandshakeBudget), + "The server never received the client's pre-login packet, so the " + + "handshake read was not in flight; the test cannot exercise the " + + "close-during-handshake path."); + + // Close/dispose the connection on a dedicated, long-running thread + // while the handshake read is in flight. This is the operation that + // can deadlock. A dedicated thread (rather than a thread-pool thread + // via Task.Run) ensures a hypothetical regression parks this one + // thread instead of starving the shared thread pool. + closeAttempted = true; + Task closeTask = Task.Factory.StartNew( + () => + { + if (disposeInsteadOfClose) + { + connection.Dispose(); + } + else + { + connection.Close(); + } + }, + TaskCreationOptions.LongRunning); + + closedInTime = closeTask.Wait(CloseBudget); + + Assert.True( + closedInTime, + $"{(disposeInsteadOfClose ? "Dispose()" : "Close()")} did not complete " + + $"within {CloseBudget.TotalSeconds:N0}s while a pre-login response " + + "read was in flight. This indicates the SNIClose deadlock " + + "(ADO.Net #43847 / ICM 775308542)."); + } + finally + { + // Always release the server loop and stop the listener, even if a + // precondition assert above threw, so the background server task + // cannot stay blocked and the listening socket is not leaked into + // subsequent tests. + releaseServer.Set(); + listener.Stop(); + + // Observe the open task so its (expected) failure is not unobserved. + if (openTask != null) + { + try + { + openTask.Wait(CloseBudget); + } + catch + { + // OpenAsync is expected to fault or cancel once the + // connection is torn down. That is not what this test asserts. + } + } + + // Dispose the connection unless a close deadlock was actually + // detected (in which case Dispose() could also block). Disposing is + // safe when the close worker never started or when it completed. + if (!closeAttempted || closedInTime) + { + connection.Dispose(); + } + + try + { + serverTask.Wait(CloseBudget); + } + catch + { + // Ignore server teardown faults. + } + } + } + + /// + /// A minimal, protocol-valid TDS PRELOGIN response advertising ENCRYPT_ON + /// so that a client which requested encryption proceeds into the TLS + /// handshake. Offsets are relative to the start of the payload (immediately + /// after the 8-byte TDS packet header): + /// + /// + /// Option table: + /// VERSION token 0x00, offset 11 (0x000B), length 6 + /// ENCRYPTION token 0x01, offset 17 (0x0011), length 1 + /// TERMINATOR 0xFF + /// Data: + /// VERSION 6 bytes (17.0.0.0) + /// ENCRYPTION 1 byte (0x01 = ENCRYPT_ON) + /// + /// + private static readonly byte[] s_preLoginEncryptOnResponse = + { + // ---- TDS packet header (8 bytes) ---- + 0x12, // Type: PRELOGIN + 0x01, // Status: EOM + 0x00, 0x1A, // Length: 26 (8 header + 18 payload), big-endian + 0x00, 0x00, // SPID + 0x01, // PacketID + 0x00, // Window + // ---- PRELOGIN option table ---- + 0x00, 0x00, 0x0B, 0x00, 0x06, // VERSION: offset 11, length 6 + 0x01, 0x00, 0x11, 0x00, 0x01, // ENCRYPTION: offset 17, length 1 + 0xFF, // TERMINATOR + // ---- Option data ---- + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, // VERSION 17.0.0.0 + 0x01, // ENCRYPTION = ENCRYPT_ON + }; + + /// + /// Reproduces the ICM scenario faithfully: the connection is torn down while + /// the client is inside the TLS handshake over TDS, with an SNI read pending + /// for the server's handshake response. + /// + /// + /// A bare TCP listener speaks just enough of the TDS pre-login exchange to + /// drive the client into the TLS handshake: it reads the client's PRELOGIN + /// packet, replies with a PRELOGIN response advertising ENCRYPT_ON, then + /// reads the client's TLS ClientHello (delivered as a TDS pre-login packet) + /// and deliberately never sends a ServerHello. At that point the client is + /// blocked inside SslStream.AuthenticateAsClient with an SNI read in + /// flight on the SSL-over-TDS transport. The close is performed on a worker + /// thread under a bounded wait; a deadlock in the handshake-phase close path + /// manifests as the wait timing out. + /// + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CloseOrDispose_DuringTlsHandshake_DoesNotDeadlock(bool disposeInsteadOfClose) + { + using ManualResetEventSlim handshakeInFlight = new(false); + using ManualResetEventSlim releaseServer = new(false); + + TcpListener listener = new(IPAddress.Loopback, 0); + listener.Start(); + int port = ((IPEndPoint)listener.LocalEndpoint).Port; + + // Server: complete the pre-login exchange advertising encryption, read + // the client's TLS ClientHello, then withhold the ServerHello so the + // client's handshake read stays pending. + Task serverTask = Task.Run(() => + { + using TcpClient acceptedClient = listener.AcceptTcpClient(); + using NetworkStream stream = acceptedClient.GetStream(); + + byte[] buffer = new byte[4096]; + try + { + // Read the client's PRELOGIN packet. + int read = stream.Read(buffer, 0, buffer.Length); + if (read <= 0) + { + return; + } + + // Advertise ENCRYPT_ON so the client begins the TLS handshake. + stream.Write(s_preLoginEncryptOnResponse, 0, s_preLoginEncryptOnResponse.Length); + stream.Flush(); + + // Read the first byte of the client's TLS ClientHello (wrapped + // in a TDS pre-login packet). A byte here proves the client + // accepted the pre-login response and entered the TLS handshake; + // it is now awaiting the ServerHello with its SNI read pending. + if (stream.ReadByte() < 0) + { + // Client tore down without sending a ClientHello: the + // handshake was never actually in flight. + return; + } + + handshakeInFlight.Set(); + + // Withhold the ServerHello until the test releases us. + releaseServer.Wait(); + } + catch + { + // The client may tear down the socket mid-handshake; ignore. + } + }); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"127.0.0.1,{port}", + // Require encryption so the client performs the TLS handshake. + Encrypt = SqlConnectionEncryptOption.Mandatory, + TrustServerCertificate = true, + ConnectTimeout = 60, + ConnectRetryCount = 0, + Pooling = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }; + + SqlConnection connection = new(builder.ConnectionString); + Task? openTask = null; + bool closeAttempted = false; + bool closedInTime = false; + try + { + // Begin connection establishment. This will not complete because + // the server never finishes the TLS handshake. + openTask = connection.OpenAsync(); + + // Wait until the client is inside the TLS handshake with a pending + // read. + Assert.True( + handshakeInFlight.Wait(HandshakeBudget), + "The client never reached the TLS handshake, so no SNI read was in " + + "flight; the test cannot exercise the close-during-TLS-handshake path."); + + // Close/dispose the connection on a dedicated, long-running thread + // while the TLS handshake read is in flight. This is the operation + // that can deadlock. A dedicated thread (rather than a thread-pool + // thread via Task.Run) ensures a hypothetical regression parks this + // one thread instead of starving the shared thread pool. + closeAttempted = true; + Task closeTask = Task.Factory.StartNew( + () => + { + if (disposeInsteadOfClose) + { + connection.Dispose(); + } + else + { + connection.Close(); + } + }, + TaskCreationOptions.LongRunning); + + closedInTime = closeTask.Wait(CloseBudget); + + Assert.True( + closedInTime, + $"{(disposeInsteadOfClose ? "Dispose()" : "Close()")} did not complete " + + $"within {CloseBudget.TotalSeconds:N0}s while a TLS handshake read was " + + "in flight. This indicates the SNIClose deadlock " + + "(ADO.Net #43847 / ICM 775308542)."); + } + finally + { + // Always release the server loop and stop the listener, even if a + // precondition assert above threw, so the background server task + // cannot stay blocked and the listening socket is not leaked into + // subsequent tests. + releaseServer.Set(); + listener.Stop(); + + // Observe the open task so its (expected) failure is not unobserved. + if (openTask != null) + { + try + { + openTask.Wait(CloseBudget); + } + catch + { + // OpenAsync is expected to fault or cancel once the + // connection is torn down. That is not what this test asserts. + } + } + + // Dispose the connection unless a close deadlock was actually + // detected (in which case Dispose() could also block). Disposing is + // safe when the close worker never started or when it completed. + if (!closeAttempted || closedInTime) + { + connection.Dispose(); + } + + try + { + serverTask.Wait(CloseBudget); + } + catch + { + // Ignore server teardown faults. + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseHandshakeCancellationTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseHandshakeCancellationTest.cs new file mode 100644 index 0000000000..9a41c2f3c7 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseHandshakeCancellationTest.cs @@ -0,0 +1,180 @@ +// 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.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests; + +/// +/// Regression test for the connection-teardown paths related to ADO.Net #43847 / +/// ICM 775308542. Where tears the connection +/// down with an external Close()/Dispose(), this drives the +/// driver's internal abort/close path by cancelling OpenAsync while +/// the TLS handshake read is in flight. +/// +/// +/// A bare TCP listener completes just enough of the pre-login exchange to push +/// the client into the TLS handshake (it advertises ENCRYPT_ON, reads the +/// client's ClientHello, then withholds the ServerHello). The client's +/// OpenAsync is then cancelled while that handshake read is pending. The +/// cancellation must abort the in-flight I/O and complete the operation promptly; +/// a deadlock in the internal close path manifests as the bounded wait timing +/// out, which fails the test. +/// +/// +public class SNICloseHandshakeCancellationTest +{ + /// + /// Upper bound for how long the cancelled OpenAsync should take to settle. + /// Generous so it only distinguishes "completed" from "deadlocked". + /// + private static readonly TimeSpan CloseBudget = TimeSpan.FromSeconds(30); + + /// + /// Upper bound for waiting until the client's TLS handshake read is pending. + /// + private static readonly TimeSpan HandshakeBudget = TimeSpan.FromSeconds(30); + + /// + /// A minimal, protocol-valid TDS PRELOGIN response advertising ENCRYPT_ON so + /// a client that requested encryption proceeds into the TLS handshake. + /// + private static readonly byte[] s_preLoginEncryptOnResponse = + { + // ---- TDS packet header (8 bytes) ---- + 0x12, // Type: PRELOGIN + 0x01, // Status: EOM + 0x00, 0x1A, // Length: 26 (8 header + 18 payload), big-endian + 0x00, 0x00, // SPID + 0x01, // PacketID + 0x00, // Window + // ---- PRELOGIN option table ---- + 0x00, 0x00, 0x0B, 0x00, 0x06, // VERSION: offset 11, length 6 + 0x01, 0x00, 0x11, 0x00, 0x01, // ENCRYPTION: offset 17, length 1 + 0xFF, // TERMINATOR + // ---- Option data ---- + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, // VERSION 17.0.0.0 + 0x01, // ENCRYPTION = ENCRYPT_ON + }; + + [Fact] + public void CancelOpenAsyncDuringTlsHandshake_DoesNotDeadlock() + { + using ManualResetEventSlim handshakeInFlight = new(false); + using ManualResetEventSlim releaseServer = new(false); + + TcpListener listener = new(IPAddress.Loopback, 0); + listener.Start(); + int port = ((IPEndPoint)listener.LocalEndpoint).Port; + + // Server: complete the pre-login exchange advertising encryption, read the + // client's TLS ClientHello, then withhold the ServerHello so the client's + // handshake read stays pending. + Task serverTask = Task.Run(() => + { + using TcpClient acceptedClient = listener.AcceptTcpClient(); + using NetworkStream stream = acceptedClient.GetStream(); + byte[] buffer = new byte[4096]; + try + { + int read = stream.Read(buffer, 0, buffer.Length); + if (read <= 0) + { + return; + } + + stream.Write(s_preLoginEncryptOnResponse, 0, s_preLoginEncryptOnResponse.Length); + stream.Flush(); + + // A byte here proves the client entered the TLS handshake and is + // awaiting the ServerHello with its read pending. + if (stream.ReadByte() < 0) + { + return; + } + + handshakeInFlight.Set(); + releaseServer.Wait(); + } + catch + { + // The client may tear down the socket mid-handshake; ignore. + } + }); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"127.0.0.1,{port}", + Encrypt = SqlConnectionEncryptOption.Mandatory, + TrustServerCertificate = true, + ConnectTimeout = 60, + ConnectRetryCount = 0, + Pooling = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }; + + SqlConnection connection = new(builder.ConnectionString); + using CancellationTokenSource cts = new(); + Task? openTask = null; + bool cancelAttempted = false; + bool settledInTime = false; + try + { + openTask = connection.OpenAsync(cts.Token); + + Assert.True( + handshakeInFlight.Wait(HandshakeBudget), + "The client never reached the TLS handshake; no read was in flight to cancel."); + + // Cancel while the handshake read is pending: this drives the driver's + // internal abort/close path against the in-flight I/O. + cancelAttempted = true; + cts.Cancel(); + + try + { + settledInTime = openTask.Wait(CloseBudget); + } + catch (AggregateException) + { + // OpenAsync faulting/cancelling is the expected outcome; what + // matters is that it *settled* rather than hanging. + settledInTime = true; + } + + Assert.True( + settledInTime, + $"OpenAsync did not settle within {CloseBudget.TotalSeconds:N0}s after cancellation while a " + + "TLS handshake read was in flight. This indicates a deadlock in the internal close path " + + "(ADO.Net #43847 / ICM 775308542)."); + } + finally + { + releaseServer.Set(); + listener.Stop(); + + if (openTask != null) + { + try { openTask.Wait(CloseBudget); } catch { /* expected fault/cancel */ } + } + + // Dispose unless a deadlock was detected (Dispose could also block). + if (!cancelAttempted || settledInTime) + { + connection.Dispose(); + } + + try { serverTask.Wait(CloseBudget); } catch { /* ignore */ } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseRaceDeadlockTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseRaceDeadlockTest.cs new file mode 100644 index 0000000000..27ab3a7516 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/SNICloseRaceDeadlockTest.cs @@ -0,0 +1,192 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlServer.TDS; +using Microsoft.SqlServer.TDS.EndPoint; +using Microsoft.SqlServer.TDS.Servers; +using Microsoft.SqlServer.TDS.SQLBatch; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests; + +/// +/// Race variant of the SNIClose close-during-I/O regression tests +/// (ADO.Net #43847 / ICM 775308542). +/// +/// +/// The sibling SNICloseDeadlockTest closes over a quiescent +/// pending read: the server stays silent, so the close path simply cancels the +/// in-flight read and no completion callback is executing at close time. This +/// test instead releases the server's response at the same instant the +/// connection is closed (rendezvous on a ), so the +/// read's completion callback fires concurrently with SNIClose - the +/// exact ordering hazard the deadlock is about. It repeats many iterations to +/// widen the race window. +/// +/// +/// +/// The close runs on a dedicated long-running thread under a bounded wait: a +/// deadlock manifests as the wait timing out, which fails the test. A healthy +/// close completes promptly. +/// +/// +public class SNICloseRaceDeadlockTest +{ + /// + /// Upper bound for how long a non-deadlocked close should take. Generous so + /// it only distinguishes "completed" from "deadlocked" on slow CI agents. + /// + private static readonly TimeSpan CloseBudget = TimeSpan.FromSeconds(30); + + /// + /// Upper bound for waiting on the server to receive the client's batch, i.e. + /// for the client's async read to become pending. + /// + private static readonly TimeSpan HandshakeBudget = TimeSpan.FromSeconds(30); + + /// + /// A query engine that signals when a SQL batch arrives and then withholds + /// the response until released, so the test controls exactly when the + /// client's read completion fires. + /// + private sealed class ReleasableQueryEngine : QueryEngine + { + private readonly ManualResetEventSlim _batchReceived; + private readonly ManualResetEventSlim _releaseResponse; + + public ReleasableQueryEngine( + TdsServerArguments arguments, + ManualResetEventSlim batchReceived, + ManualResetEventSlim releaseResponse) + : base(arguments) + { + _batchReceived = batchReceived; + _releaseResponse = releaseResponse; + } + + protected override TDSMessageCollection CreateQueryResponse( + ITDSServerSession session, + TDSSQLBatchToken batchRequest) + { + _batchReceived.Set(); + _releaseResponse.Wait(); + return base.CreateQueryResponse(session, batchRequest); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ResponseCompletionRacesClose_DoesNotDeadlock(bool disposeInsteadOfClose) + { + const int iterations = 100; + + for (int i = 0; i < iterations; i++) + { + using ManualResetEventSlim batchReceived = new(false); + using ManualResetEventSlim releaseResponse = new(false); + + TdsServerArguments arguments = new(); + using TdsServer server = new( + new ReleasableQueryEngine(arguments, batchReceived, releaseResponse), + arguments); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + // Disable pooling so Close()/Dispose() tears down the physical + // connection (reaching SNIClose) instead of returning it to the pool. + Pooling = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }; + + SqlConnection connection = new(builder.ConnectionString); + SqlCommand? command = null; + Task? readTask = null; + bool closeAttempted = false; + bool closedInTime = false; + try + { + connection.Open(); + + command = new("SELECT 1", connection); + readTask = command.ExecuteReaderAsync(); + + Assert.True( + batchReceived.Wait(HandshakeBudget), + $"iteration {i}: the server never received the SQL batch, so the " + + "async read was not in flight."); + + // Close on a dedicated, long-running thread, gated on a barrier; + // release the server response at the same instant so the read's + // completion callback fires concurrently with SNIClose. + using Barrier gate = new(2); + + closeAttempted = true; + Task closeTask = Task.Factory.StartNew( + () => + { + gate.SignalAndWait(); + if (disposeInsteadOfClose) + { + connection.Dispose(); + } + else + { + connection.Close(); + } + }, + TaskCreationOptions.LongRunning); + + gate.SignalAndWait(); + releaseResponse.Set(); + + closedInTime = closeTask.Wait(CloseBudget); + + Assert.True( + closedInTime, + $"iteration {i}: {(disposeInsteadOfClose ? "Dispose()" : "Close()")} did not " + + $"complete within {CloseBudget.TotalSeconds:N0}s while a response completion " + + "raced close. This indicates the SNIClose deadlock (ADO.Net #43847 / ICM 775308542)."); + } + finally + { + // Always release the (possibly still-stalled) server thread so + // the using-scoped server can be disposed without blocking. + releaseResponse.Set(); + + if (readTask != null) + { + try + { + readTask.Wait(CloseBudget); + } + catch + { + // The pending read is expected to fault or cancel once the + // connection is torn down. That is not what this test asserts. + } + } + + command?.Dispose(); + + // Dispose unless a close deadlock was actually detected (in which + // case Dispose() could also block and defeat the regression signal). + if (!closeAttempted || closedInTime) + { + connection.Dispose(); + } + } + } + } +} diff --git a/tools/SniCloseLegacyRepro/AssemblyInfo.cs b/tools/SniCloseLegacyRepro/AssemblyInfo.cs new file mode 100644 index 0000000000..a243838e3d --- /dev/null +++ b/tools/SniCloseLegacyRepro/AssemblyInfo.cs @@ -0,0 +1,12 @@ +// 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 Xunit; + +// This diagnostic harness includes tests that mutate PROCESS-GLOBAL state (e.g. +// ThreadPool.SetMin/MaxThreads in the starvation experiments) and heavy soak +// tests. Running collections in parallel lets those perturb unrelated tests +// (starving their connections), so we run serially - matching the container +// runner, which uses `xunit.console.exe -parallel none`. +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tools/SniCloseLegacyRepro/Compat.cs b/tools/SniCloseLegacyRepro/Compat.cs new file mode 100644 index 0000000000..82e6d5f94c --- /dev/null +++ b/tools/SniCloseLegacyRepro/Compat.cs @@ -0,0 +1,117 @@ +// 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 LegacyBuilder = System.Data.SqlClient.SqlConnectionStringBuilder; + +namespace SniCloseLegacyRepro.Compat +{ + /// + /// Minimal stand-in for Microsoft.Data.SqlClient's SqlConnectionEncryptOption + /// enum, which does not exist in the legacy System.Data.SqlClient driver. + /// + public enum SqlConnectionEncryptOption + { + Optional, + Mandatory, + Strict, + } + + /// + /// A connection-string builder facade over the LEGACY driver's (sealed) + /// SqlConnectionStringBuilder. It exposes only the keywords the linked test + /// files use, and accepts the Microsoft.Data-style + /// enum for Encrypt so those + /// files compile unchanged. The legacy builder is sealed, so this uses + /// composition rather than inheritance. + /// + public sealed class ShimSqlConnectionStringBuilder + { + private readonly LegacyBuilder _inner; + + public ShimSqlConnectionStringBuilder() => _inner = new LegacyBuilder(); + + public ShimSqlConnectionStringBuilder(string connectionString) => + _inner = new LegacyBuilder(connectionString); + + public string ConnectionString => _inner.ConnectionString; + + public string DataSource + { + get => _inner.DataSource; + set => _inner.DataSource = value; + } + + public bool Pooling + { + get => _inner.Pooling; + set => _inner.Pooling = value; + } + + public bool TrustServerCertificate + { + get => _inner.TrustServerCertificate; + set => _inner.TrustServerCertificate = value; + } + + public int ConnectTimeout + { + get => _inner.ConnectTimeout; + set => _inner.ConnectTimeout = value; + } + + public int ConnectRetryCount + { + get => _inner.ConnectRetryCount; + set => _inner.ConnectRetryCount = value; + } + + public bool MultipleActiveResultSets + { + get => _inner.MultipleActiveResultSets; + set => _inner.MultipleActiveResultSets = value; + } + +#if NETFRAMEWORK + // Only present on the in-box (netfx) legacy builder; the tests guard its + // use with #if NETFRAMEWORK, so this facade matches that guard. + public bool TransparentNetworkIPResolution + { + get => _inner.TransparentNetworkIPResolution; + set => _inner.TransparentNetworkIPResolution = value; + } +#endif + + /// + /// Maps the Microsoft.Data encryption enum onto the legacy boolean + /// Encrypt keyword: Optional => false; Mandatory/Strict => true. + /// + public SqlConnectionEncryptOption Encrypt + { + get => _inner.Encrypt + ? SqlConnectionEncryptOption.Mandatory + : SqlConnectionEncryptOption.Optional; + set => _inner.Encrypt = value != SqlConnectionEncryptOption.Optional; + } + } +} + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + /// + /// Stand-in for the real test-infrastructure DataTestUtility, providing just + /// the members the linked MARS test and the stress tests reference. The live + /// connection string is taken from the SNICLOSE_CONNSTR environment variable. + /// + public static class DataTestUtility + { + public static string TCPConnectionString => + Environment.GetEnvironmentVariable("SNICLOSE_CONNSTR") ?? string.Empty; + + public static bool AreConnStringsSetup() => + !string.IsNullOrWhiteSpace(TCPConnectionString); + + public static bool IsNotAzureSynapse() => true; + } +} diff --git a/tools/SniCloseLegacyRepro/Directory.Build.props b/tools/SniCloseLegacyRepro/Directory.Build.props new file mode 100644 index 0000000000..cd6b5c8b61 --- /dev/null +++ b/tools/SniCloseLegacyRepro/Directory.Build.props @@ -0,0 +1,14 @@ + + + + + + true + + + diff --git a/tools/SniCloseLegacyRepro/Directory.Packages.props b/tools/SniCloseLegacyRepro/Directory.Packages.props new file mode 100644 index 0000000000..23d7f8278f --- /dev/null +++ b/tools/SniCloseLegacyRepro/Directory.Packages.props @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/tools/SniCloseLegacyRepro/GlobalUsings.cs b/tools/SniCloseLegacyRepro/GlobalUsings.cs new file mode 100644 index 0000000000..806fa4fb37 --- /dev/null +++ b/tools/SniCloseLegacyRepro/GlobalUsings.cs @@ -0,0 +1,26 @@ +// 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. + +// ----------------------------------------------------------------------------- +// Global using aliases that let the linked test files (which use bare +// Microsoft.Data.SqlClient type names) bind to the LEGACY System.Data.SqlClient +// driver instead. +// +// How this works: the linked files live in namespaces under +// Microsoft.Data.SqlClient.*, so an unqualified name like "SqlConnection" is +// resolved by walking the enclosing namespaces. Because this assembly does NOT +// reference Microsoft.Data.SqlClient, none of those namespaces define these +// types, so name resolution falls through to these global aliases. +// +// SqlConnectionStringBuilder and SqlConnectionEncryptOption are aliased to shim +// types (see Compat.cs) because the legacy builder's Encrypt property is a bool, +// whereas the tests assign the Microsoft.Data-only SqlConnectionEncryptOption +// enum. The shim reconciles that difference. +// ----------------------------------------------------------------------------- + +global using SqlConnection = System.Data.SqlClient.SqlConnection; +global using SqlCommand = System.Data.SqlClient.SqlCommand; +global using SqlDataReader = System.Data.SqlClient.SqlDataReader; +global using SqlConnectionStringBuilder = SniCloseLegacyRepro.Compat.ShimSqlConnectionStringBuilder; +global using SqlConnectionEncryptOption = SniCloseLegacyRepro.Compat.SqlConnectionEncryptOption; diff --git a/tools/SniCloseLegacyRepro/README.md b/tools/SniCloseLegacyRepro/README.md new file mode 100644 index 0000000000..441e5b836b --- /dev/null +++ b/tools/SniCloseLegacyRepro/README.md @@ -0,0 +1,270 @@ +# SniCloseLegacyRepro + +Diagnostic harness that runs **SNIClose / MARS close-during-I/O deadlock tests** against the +**legacy `System.Data.SqlClient`** driver, to see whether the deadlock from **ICM 775308542 / +ADO.Net #43847** ("SNIClose can deadlock with in-flight async I/O during connection close") +reproduces there. + +--- + +## Background + +ICM 775308542 / ADO.Net #43847 concern a potential SNIClose deadlock with +in-flight async I/O during connection close in the **retired, in-box +`System.Data.SqlClient`**. The environment reported in the ICM: + +| | Reported environment (ICM) | +|---|---| +| OS | Windows Server 2016 (10.0.14393) | +| Runtime | .NET Framework 4.7 (CLR 4.0.30319) | +| Driver | in-box `System.Data.dll` **4.7.4081.0** | +| CPUs | 4 | + +`System.Data.SqlClient` is retired, so the fix (if any) is being investigated in +`Microsoft.Data.SqlClient` via #43847. The branch adds regression tests on the +MDS side; this harness points those same tests at the legacy driver. + +--- + +## How it works + +Rather than re-implementing the tests, the project **links the real test source +files** and swaps only the driver reference: + +- **Linked tests** (compiled verbatim, not copied): + - `SNICloseDeadlockTest.cs` (from `tests/UnitTests/SimulatedServerTests`) — + 3 in-process tests: pending async read (via the in-repo TDS test server), + pre-login handshake, and TLS handshake (both via raw `TcpListener`). + - `SNICloseRaceDeadlockTest.cs` (same folder) — releases the server response + at the same instant as close so the read completion races `SNIClose` + (×100 iterations). + - `MarsCloseDeadlockTest.cs` (from `tests/ManualTests/SQL/AsyncTest`) — + live-server MARS pending-read test. + - `MarsCloseStressTest.cs` (same folder) — many MARS connections with + in-flight reads, all closed simultaneously over several iterations + (live server). +- **Compat shim** makes the legacy driver satisfy the linked files' bare + `Microsoft.Data.SqlClient` type names: + - `GlobalUsings.cs` — global `using` aliases mapping `SqlConnection`, + `SqlCommand`, `SqlDataReader` to `System.Data.SqlClient`, and + `SqlConnectionStringBuilder` / `SqlConnectionEncryptOption` to the shims. + - `Compat.cs` — a `SqlConnectionEncryptOption` enum stand-in, a + `SqlConnectionStringBuilder` facade over the (sealed) legacy builder that + accepts the enum-typed `Encrypt`, and a `DataTestUtility` stand-in that + reads the connection string from `SNICLOSE_CONNSTR`. + +All four test files are the **real driver tests**; this project only links them +and swaps the driver reference. (The race/stress tests were originally prototyped +here and have since been promoted into the driver test projects.) + +The bare-name binding trick: the linked files sit in `Microsoft.Data.SqlClient.*` +namespaces, and because this project does **not** reference MDS, unqualified +names like `SqlConnection` fall through to the global aliases in `GlobalUsings.cs`. + +### Why `xunit.runner.json` (shadowCopy: false) matters + +`Microsoft.DotNet.XUnitExtensions` (which provides `[ConditionalTheory]`) ships a +**delay-signed** beta assembly. On .NET Framework, xUnit's default shadow copy +re-verifies the strong name from the shadow-copy directory and fails with +`FileLoadException: strong name could not be verified`. The real test projects +ship `xunit.runner.json` with `shadowCopy: false`; this harness **links that same +file** so the delay-signed assembly loads from its build-output location (where +.NET Framework's strong-name bypass applies). + +--- + +## Target frameworks + +`net462;net47;net472;net481;net10.0` + +- **.NET Framework targets** use the **in-box** `System.Data.dll`. +- **.NET (Core) target** (`net10.0`) uses the **`System.Data.SqlClient` NuGet + package** (version parametrized — see below). + +> **Important:** the in-box `System.Data.dll` is *machine-global* for the whole +> 4.x line — only one physical DLL exists per machine. On this dev box that is +> 4.8.1, so every netfx target binds 4.8.1 at runtime; the older TFMs only change +> compile-time references and framework "quirk" behaviors, **not** the DLL bits. +> To run the exact reported 4.7.4081.0 bits you need an environment isolated to +> .NET 4.7.2 (see [Testing older in-box DLLs](#testing-older-in-box-dlls-containers)). + +--- + +## Running + +From the repo root. + +### In-process tests only (no SQL Server) + +```powershell +dotnet test .\tools\SniCloseLegacyRepro +# or a single framework: +dotnet test .\tools\SniCloseLegacyRepro -f net481 +``` + +The live MARS test and the MARS stress test are skipped when no connection +string is set. + +### With a live SQL Server (enables MARS + stress tests) + +Set `SNICLOSE_CONNSTR` yourself so the password is not routed through tooling: + +```powershell +$env:SNICLOSE_CONNSTR = "Server=127.0.0.1,1434;Database=master;User ID=;Password=;TrustServerCertificate=true;Connect Timeout=15" +dotnet test .\tools\SniCloseLegacyRepro -f net481 +Remove-Item Env:\SNICLOSE_CONNSTR # clear when done +``` + +### Sweeping legacy NuGet versions (net10.0 only) + +The `.NET (Core)` target's package version is parametrized via +`$(SqlClientLegacyVersion)` (default `4.9.1`). The versions must exist on a +restore feed. + +```powershell +foreach ($v in '4.5.3','4.6.1','4.7.0','4.8.6','4.9.1') { + dotnet test .\tools\SniCloseLegacyRepro -f net10.0 -p:SqlClientLegacyVersion=$v +} +``` + +### Emulating the reported 4-CPU box + +Pin the test process (and its child `testhost`) to 4 cores: + +```powershell +$p = Start-Process dotnet -ArgumentList 'test','.\tools\SniCloseLegacyRepro','-f','net47','--no-build' -PassThru -NoNewWindow +$p.ProcessorAffinity = [IntPtr]0xF # cores 0-3 +$p.WaitForExit() +``` + +--- + +## Testing older in-box DLLs (containers) + +Because the in-box `System.Data.dll` is machine-global, the faithful way to test +older bits is an isolated environment. Use the official .NET Framework images — +the **framework version tag controls `System.Data.dll`**; the **Windows base tag +controls the OS + native `sni.dll`**. + +``` +mcr.microsoft.com/dotnet/framework/runtime:-windowsservercore- +``` + +| Image tag | `System.Data.dll` ≈ | Role | +|---|---|---| +| `4.6.2-windowsservercore-ltsc2016` | 4.7.4081.0 (see note) | — | +| `4.7-windowsservercore-ltsc2016` | **4.7.4081.0** | **ICM match** | +| `4.7.2-windowsservercore-ltsc2016` | 4.7.4081.0 | ICM match | +| `4.8-windowsservercore-ltsc2019` | 4.8.4690.0 | newer | +| `4.8.1-windowsservercore-ltsc2022` | 4.8.9xxx | this dev box | + +> **.NET Framework 4.x is a single, in-place, monotonic runtime.** The `ltsc2016` +> base OS is already serviced to 4.7.2, so its in-box `System.Data.dll` is +> **4.7.4081.0** — and a lower framework tag (e.g. `4.6.2`) cannot downgrade it. +> On this base, every tag at or below 4.7.2 resolves to the same 4.7.4081.0 DLL, +> which happens to be the reported version (the practical floor). Getting +> a genuinely older DLL (e.g. 4.6.x) would require an *unserviced* older base OS +> image, which MCR does not publish. + +Verify the DLL version inside a container: + +```powershell +(Get-Item C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Data.dll).VersionInfo.FileVersion +``` + +### `run-in-container.ps1` + +The framework `runtime` images have **no `dotnet` CLI**, so `dotnet test` will not +work there. The `run-in-container.ps1` helper handles this: it builds `-f net47` +on the host, stages `xunit.console.exe` (from `xunit.runner.console`) next to the +build output, and runs the tests in the container so they bind the container's +old in-box `System.Data.dll`. + +```powershell +# in-process tests only (no SQL Server): +.\run-in-container.ps1 + +# faithful ICM match: 4.7.4081.0 + 4 processors, live MARS + stress: +$env:SNICLOSE_CONNSTR = "Server=127.0.0.1,1434;Database=master;User ID=sa;Password=***;TrustServerCertificate=true" +.\run-in-container.ps1 -Cpus 4 + +# a different framework image: +.\run-in-container.ps1 -Image mcr.microsoft.com/dotnet/framework/runtime:4.8-windowsservercore-ltsc2019 +``` + +**Networking:** a Windows container cannot reach the host's loopback, and +`host.docker.internal` is not injected for hyperv-isolated Windows containers. +The script targets the Docker NAT gateway (the host's `vEthernet (nat)` IP, e.g. +`172.17.224.1`) and rewrites the connection string to it. Because the SQL Server +(a Linux container published to host loopback) does not listen on that IP, add a +one-time **elevated** host bridge: + +```powershell +netsh interface portproxy add v4tov4 listenaddress=172.17.224.1 listenport=1434 connectaddress=127.0.0.1 connectport=1434 +New-NetFirewallRule -DisplayName "SQL 1434 from Windows containers" -Direction Inbound -LocalAddress 172.17.224.1 -LocalPort 1434 -Protocol TCP -Action Allow +# teardown: +# netsh interface portproxy delete v4tov4 listenaddress=172.17.224.1 listenport=1434 +# Remove-NetFirewallRule -DisplayName "SQL 1434 from Windows containers" +``` + +--- + +## Findings + +Across every configuration tested, the deadlock **does not reproduce** (all +tests pass), including: + +- in-box `System.Data.dll` 4.8.1 on `net462` / `net47` / `net472` / `net481` + (host dev box) +- 4-CPU-pinned runs +- the completion-races-close and MARS concurrency stress variants +- the full `System.Data.SqlClient` NuGet lineage on `net10.0` + (4.5.3, 4.6.1, 4.7.0, 4.8.6, 4.9.1) +- **the reported (ICM) environment**, reproduced faithfully in a container: + Windows Server 2016 base (`ltsc2016`) · .NET Framework 4.7 · in-box + `System.Data.dll` **4.7.4081.0** · **4 processors** (`--cpu-count 4`) · live + MARS + concurrency stress against SQL Server 2025 → **12/12 pass**. +- container `ltsc2019` · .NET Framework 4.8 · `System.Data.dll` 4.8.4690.0 · + 4 processors · live MARS + stress → **12/12 pass**. + +So the SNIClose close-during-I/O deadlock does not reproduce even in a +bit-for-bit reconstruction of the reported ICM environment. Because that +*exact* `System.Data.dll` (4.7.4081.0) also passes, there is no +version boundary / "fixed in a later servicing build" explanation: the synthetic +tests simply do not trigger whatever the original incident hit. That points away +from a plain client-side ordering bug reachable from managed timing alone, and +toward something more environment-specific in the original incident (e.g. exact +network/timing conditions, or a native-layer interaction not exercised here). + +### Additional reproduction attempts (`ReproAttempts.cs`) + +Harness-only, exploratory variants targeting conditions the shared tests do not +create. On the exact 4.7.4081.0 bits at 4 CPUs, **none reproduces the issue**: + +- **Close under thread-pool starvation** → no deadlock. +- **Sync-over-async on a single-threaded `SynchronizationContext`** (classic + ASP.NET shape) → no captured-context deadlock. +- **Cancel `OpenAsync` during the TLS handshake** (internal abort/close path) + → settles promptly, no deadlock. +- **`Open()` under client thread-pool starvation** (real, out-of-process server; + plain and TLS) → **`Open()` succeeds**. Starvation is *not* the reproduction. + +> A note on a false lead: an *in-process* TDS test server shares the client's +> thread pool, so saturating the pool starves the **server** and makes `Open()` +> time out - a test artifact. Against a real out-of-process server the driver's +> `Open()` is robust under the same starvation, so the timeout does not indicate +> a driver defect. + +--- + +## Isolation notes + +- Uses Central Package Management via its own `Directory.Build.props` + (`ManagePackageVersionsCentrally=true`) and `Directory.Packages.props`, which + imports the shared SqlClient tests package versions. The legacy + `System.Data.SqlClient` package is pinned there; the net10.0 target overrides + it per-run via `VersionOverride` / `-p:SqlClientLegacyVersion=…`. +- Legacy `System.Data.SqlClient` versions are served from the repo's governed + feed (no extra `NuGet.config`). +- References the driver-agnostic in-repo TDS test-server projects + (`tests/tools/TDS/*`). diff --git a/tools/SniCloseLegacyRepro/ReproAttempts.cs b/tools/SniCloseLegacyRepro/ReproAttempts.cs new file mode 100644 index 0000000000..e2107616d5 --- /dev/null +++ b/tools/SniCloseLegacyRepro/ReproAttempts.cs @@ -0,0 +1,965 @@ +// 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 + +// ----------------------------------------------------------------------------- +// Tier-2 reproduction ATTEMPTS for ICM 775308542 / ADO.Net #43847 (harness-only, +// exploratory - not promoted to the driver test suite unless one reproduces). +// +// The shared SNIClose/MARS tests conclusively show that a single connection +// closed while one async I/O is in flight does NOT deadlock, even on the +// customer's exact in-box System.Data.dll (4.7.4081.0) at 4 CPUs. These variants +// target conditions those tests do not create, which better match the reported +// symptom ("initial TLS handshake failure" under a 4-proc production load): +// +// 1. ThreadPoolStarvation - the worker pool is saturated (as on a small, +// busy box) when the connection is closed, so an async completion callback +// cannot obtain a thread while the close path runs. +// 2. SyncOverAsyncContext - a single-threaded SynchronizationContext (as in +// classic ASP.NET) is blocked on an async SqlClient operation via .Result; +// if the driver captures the context for a continuation, it deadlocks. +// 3. CancelOpenDuringTls - OpenAsync is cancelled while the TLS handshake +// read is in flight, driving the driver's INTERNAL abort/close path +// (rather than an external Close on another thread). PROMOTED to the driver +// test suite as SNICloseHandshakeCancellationTest (linked in via the +// harness csproj), so it also runs against the legacy driver here. +// 4. OpenUnderStarvation - does client thread-pool starvation cause a +// connection/handshake failure? Against a REAL server it does NOT: the +// driver's Open() is robust. (An in-process test server shares the pool +// and would be co-starved - a test artifact, not driver behavior.) +// +// After the ICM dump was obtained, the real mechanism was identified: a +// RE-ENTRANT SNIClose (Close() called synchronously from within +// ReadAsyncCallback via the command-timeout -> failed-attention-write path, so +// SNIClose spins in WaitForActiveCallbacks waiting for the callback it runs in). +// Two further attempts target that mechanism: +// +// 5. CallbackReentrancy - async read + short command timeout + a proxy that +// breaks the client's write. A regression guard for the MDS asyncClose fix; +// does not deterministically force the legacy re-entrancy (see its notes). +// 6. ReentrantSNICloseSoak - many concurrent workers RST connections at ~the +// command-timeout instant, hunting the race probabilistically. OPT-IN +// (set SNICLOSE_SOAK_ENABLE) because it is heavy and, when it fires, poisons +// the process. It fired ONCE in a full-suite run on the legacy in-box driver +// (net462/47x/48x) but NOT on the Core NuGet lineage (net10.0) - a clean +// driver-lineage split consistent with the real bug - yet it did not recur +// in subsequent runs. So it confirms the mechanism is reachable but is not a +// reliable reproduction (matching the SQL EE's "very difficult to capture"). +// The definitive evidence remains the dump + the asyncClose code review. +// +// All waits are bounded, so a genuine deadlock is reported as a failed +// bounded-wait rather than hanging the run. +// ----------------------------------------------------------------------------- + +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlServer.TDS; +using Microsoft.SqlServer.TDS.EndPoint; +using Microsoft.SqlServer.TDS.Servers; +using Microsoft.SqlServer.TDS.SQLBatch; +using Microsoft.Data.SqlClient.ManualTesting.Tests; // DataTestUtility shim (live connection string) +using Xunit; + +namespace SniCloseLegacyRepro +{ + /// Shared helpers for the reproduction attempts. + internal static class ReproSupport + { + public static readonly TimeSpan CloseBudget = TimeSpan.FromSeconds(30); + public static readonly TimeSpan HandshakeBudget = TimeSpan.FromSeconds(30); + + /// + /// A query engine that signals when a SQL batch arrives and then withholds + /// the response until released. + /// + public sealed class StallingQueryEngine : QueryEngine + { + private readonly ManualResetEventSlim _batchReceived; + private readonly ManualResetEventSlim _release; + + public StallingQueryEngine( + TdsServerArguments arguments, + ManualResetEventSlim batchReceived, + ManualResetEventSlim release) + : base(arguments) + { + _batchReceived = batchReceived; + _release = release; + } + + protected override TDSMessageCollection CreateQueryResponse( + ITDSServerSession session, + TDSSQLBatchToken batchRequest) + { + _batchReceived.Set(); + _release.Wait(); + return base.CreateQueryResponse(session, batchRequest); + } + } + } + + /// + /// A single-threaded synchronization context that pumps posted callbacks on + /// one dedicated (background) thread - the shape that makes classic ASP.NET + /// sync-over-async deadlock. + /// + internal sealed class SingleThreadedSynchronizationContext : SynchronizationContext, IDisposable + { + private readonly BlockingCollection<(SendOrPostCallback callback, object? state)> _queue = new(); + private readonly Thread _thread; + + public SingleThreadedSynchronizationContext() + { + _thread = new Thread(Pump) { IsBackground = true, Name = "SingleThreadedSyncCtx" }; + _thread.Start(); + } + + private void Pump() + { + SetSynchronizationContext(this); + try + { + foreach ((SendOrPostCallback callback, object? state) in _queue.GetConsumingEnumerable()) + { + callback(state); + } + } + catch (InvalidOperationException) + { + // Queue completed while blocked; normal shutdown. + } + } + + public override void Post(SendOrPostCallback d, object? state) => _queue.Add((d, state)); + + public override void Send(SendOrPostCallback d, object? state) => + throw new NotSupportedException(); + + /// Queue an action to run on the context thread. + public void Run(Action action) => Post(_ => action(), null); + + public void Dispose() + { + try + { + _queue.CompleteAdding(); + } + catch + { + // Ignore. + } + } + } + + /// + /// Attempt 1: close a connection with an in-flight async read while the + /// worker thread pool is fully saturated, so an async completion callback + /// cannot obtain a thread while the close path runs. + /// + public class ThreadPoolStarvationReproTests + { + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CloseWithPendingReadUnderThreadPoolStarvation_DoesNotDeadlock(bool disposeInsteadOfClose) + { + ThreadPool.GetMinThreads(out int minW, out int minIo); + ThreadPool.GetMaxThreads(out int maxW, out int maxIo); + + using ManualResetEventSlim releaseWorkers = new(false); + using ManualResetEventSlim batchReceived = new(false); + using ManualResetEventSlim releaseResponse = new(false); + int busy = 0; + bool poolConstrained = false; + try + { + TdsServerArguments arguments = new(); + using TdsServer server = new( + new ReproSupport.StallingQueryEngine(arguments, batchReceived, releaseResponse), + arguments); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }; + + SqlConnection connection = new(builder.ConnectionString); + SqlCommand? command = null; + Task? readTask = null; + bool closeAttempted = false; + bool closedInTime = false; + try + { + // Open and start the read while the pool is healthy, so the + // starvation below isolates the CLOSE path (not connection + // establishment, which is starvation-sensitive on its own). + connection.Open(); + command = new("SELECT 1", connection); + readTask = command.ExecuteReaderAsync(); + + Assert.True( + batchReceived.Wait(ReproSupport.HandshakeBudget), + "The server never received the batch; the async read was not in flight."); + + // Now saturate a small, busy-box worker pool so any async + // completion the close path depends on cannot obtain a thread. + int poolSize = Math.Max(2, Math.Min(4, Environment.ProcessorCount)); + ThreadPool.SetMinThreads(poolSize, poolSize); + ThreadPool.SetMaxThreads(poolSize, poolSize); + poolConstrained = true; + for (int i = 0; i < poolSize; i++) + { + ThreadPool.QueueUserWorkItem(_ => + { + Interlocked.Increment(ref busy); + releaseWorkers.Wait(); + }); + } + SpinWait.SpinUntil(() => Volatile.Read(ref busy) >= poolSize, TimeSpan.FromSeconds(10)); + + closeAttempted = true; + Task closeTask = Task.Factory.StartNew( + () => + { + if (disposeInsteadOfClose) + { + connection.Dispose(); + } + else + { + connection.Close(); + } + }, + TaskCreationOptions.LongRunning); + + closedInTime = closeTask.Wait(ReproSupport.CloseBudget); + + Assert.True( + closedInTime, + $"{(disposeInsteadOfClose ? "Dispose()" : "Close()")} did not complete within " + + $"{ReproSupport.CloseBudget.TotalSeconds:N0}s while the thread pool was saturated " + + "(possible SNIClose deadlock / thread-starvation)."); + } + finally + { + // Free the pool first so the pending read and any close-path + // continuations can drain, then release the server. + releaseWorkers.Set(); + if (poolConstrained) + { + ThreadPool.SetMinThreads(minW, minIo); + ThreadPool.SetMaxThreads(maxW, maxIo); + } + releaseResponse.Set(); + + if (readTask != null) + { + try { readTask.Wait(ReproSupport.CloseBudget); } catch { /* expected */ } + } + command?.Dispose(); + if (!closeAttempted || closedInTime) + { + connection.Dispose(); + } + } + } + finally + { + releaseWorkers.Set(); + ThreadPool.SetMinThreads(minW, minIo); + ThreadPool.SetMaxThreads(maxW, maxIo); + } + } + } + + /// + /// Attempt 2: block a single-threaded synchronization context on an async + /// SqlClient read via .Result. If any driver continuation captures the + /// context, the post-back cannot run and the operation deadlocks. + /// + public class SyncOverAsyncContextReproTests + { + [Fact] + public void SyncOverAsyncOnSingleThreadedContext_DoesNotDeadlock() + { + using ManualResetEventSlim batchReceived = new(false); + using ManualResetEventSlim releaseResponse = new(false); + using ManualResetEventSlim scenarioDone = new(false); + Exception? scenarioError = null; + + TdsServerArguments arguments = new(); + using TdsServer server = new( + new ReproSupport.StallingQueryEngine(arguments, batchReceived, releaseResponse), + arguments); + server.Start(); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"localhost,{server.EndPoint.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }; + + // Once the read is in flight, release the server so its completion + // fires and the continuation must post back to the (blocked) context. + Task releaser = Task.Run(() => + { + if (batchReceived.Wait(ReproSupport.HandshakeBudget)) + { + releaseResponse.Set(); + } + }); + + using SingleThreadedSynchronizationContext context = new(); + SqlConnection? connection = null; + context.Run(() => + { + try + { + connection = new SqlConnection(builder.ConnectionString); + connection.Open(); + using SqlCommand command = new("SELECT 1", connection); + + // Sync-over-async on the single-threaded context: blocks the + // pump thread. Deadlocks iff a continuation captured the context. + using SqlDataReader reader = command.ExecuteReaderAsync().GetAwaiter().GetResult(); + while (reader.Read()) + { + // Drain. + } + } + catch (Exception ex) + { + scenarioError = ex; + } + finally + { + scenarioDone.Set(); + } + }); + + bool completed = scenarioDone.Wait(ReproSupport.CloseBudget); + + // Best-effort cleanup; if we deadlocked, the context thread is parked + // (background) and the connection is stuck - do not block on it. + try { releaser.Wait(TimeSpan.FromSeconds(5)); } catch { /* ignore */ } + if (completed) + { + try { connection?.Dispose(); } catch { /* ignore */ } + } + + Assert.True( + completed, + $"Sync-over-async on a single-threaded SynchronizationContext did not complete within " + + $"{ReproSupport.CloseBudget.TotalSeconds:N0}s. This indicates a captured-context deadlock in " + + "the driver's async read path (ADO.Net #43847 / ICM 775308542)."); + + // If it completed, it should have completed cleanly (or with an + // expected SqlException), not via some other failure mode. + Assert.True( + scenarioError is null or System.Data.SqlClient.SqlException, + $"Unexpected error on the context thread: {scenarioError}"); + } + } + + /// + /// Attempt 4: does client-side thread-pool starvation cause a CONNECTION + /// failure? This checks the hypothesis that the reported "connection/ + /// handshake failure under load" is thread starvation rather than an + /// SNIClose deadlock. + /// + /// + /// IMPORTANT: an in-process TDS test server shares the client's thread pool, + /// so saturating the pool starves the SERVER and makes Open() time out - a + /// test artifact, not driver behavior. These tests therefore require a real, + /// out-of-process SQL Server (SNICLOSE_CONNSTR). The observed result is that + /// Open() SUCCEEDS under client thread-pool starvation, i.e. starvation is + /// NOT the reproduction. + /// + /// + public class OpenUnderThreadPoolStarvationReproTests + { + private static readonly TimeSpan HardBudget = TimeSpan.FromSeconds(60); + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [InlineData(false)] // Encrypt = Optional + [InlineData(true)] // Encrypt = Mandatory (drives the TLS handshake) + public void SyncOpenAgainstRealServerUnderThreadPoolStarvation_Succeeds(bool encrypt) + { + string cs = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + { + Encrypt = encrypt ? SqlConnectionEncryptOption.Mandatory : SqlConnectionEncryptOption.Optional, + TrustServerCertificate = true, + Pooling = false, + ConnectTimeout = 30, + }.ConnectionString; + + RunStarvedOpen(cs, out bool settled, out bool timedOut, out Exception? error); + + Assert.True( + settled, + $"Open() did not settle within {HardBudget.TotalSeconds:N0}s under thread-pool starvation " + + "(it hung)."); + Assert.True( + error is null, + $"Open() against a real server failed under client thread-pool starvation: {error}"); + Assert.False( + timedOut, + "Open() timed out under client thread-pool starvation against a real server."); + } + + private static void RunStarvedOpen(string connectionString, out bool settled, out bool timedOut, out Exception? error) + { + ThreadPool.GetMinThreads(out int minW, out int minIo); + ThreadPool.GetMaxThreads(out int maxW, out int maxIo); + // Not disposed: saturating workers may still be parked on it when the + // method returns; disposing would race them into ObjectDisposedException. + ManualResetEventSlim releaseWorkers = new(false); + int busy = 0; + settled = false; + timedOut = false; + error = null; + try + { + // Constrain the pool to a small, busy-box size and occupy every + // worker thread so any continuation the driver needs is starved. + int poolSize = Math.Max(2, Math.Min(4, Environment.ProcessorCount)); + ThreadPool.SetMinThreads(poolSize, poolSize); + ThreadPool.SetMaxThreads(poolSize, poolSize); + for (int i = 0; i < poolSize; i++) + { + ThreadPool.QueueUserWorkItem(_ => + { + Interlocked.Increment(ref busy); + releaseWorkers.Wait(); + }); + } + SpinWait.SpinUntil(() => Volatile.Read(ref busy) >= poolSize, TimeSpan.FromSeconds(10)); + + Exception? captured = null; + Thread openThread = new(() => + { + try + { + using SqlConnection conn = new(connectionString); + conn.Open(); + conn.Close(); + } + catch (Exception ex) + { + captured = ex; + } + }) + { IsBackground = true, Name = "StarvedOpen" }; + + openThread.Start(); + settled = openThread.Join(HardBudget); + error = captured; + timedOut = captured is System.Data.SqlClient.SqlException sqlEx + && (sqlEx.Number == -2 + || sqlEx.Message.IndexOf("timeout", StringComparison.OrdinalIgnoreCase) >= 0 + || sqlEx.Message.IndexOf("timed out", StringComparison.OrdinalIgnoreCase) >= 0); + } + finally + { + releaseWorkers.Set(); + ThreadPool.SetMinThreads(minW, minIo); + ThreadPool.SetMaxThreads(maxW, maxIo); + } + } + } + + /// + /// A transparent TCP proxy that forwards bytes between a client and a backend + /// TDS server, and can - on demand - break the CLIENT's write path while + /// leaving its pending read outstanding. It does this via + /// Shutdown(SocketShutdown.Receive) on the client-facing socket: the + /// client's next write (the timeout attention) then arrives at a + /// receive-shutdown socket and is answered with a TCP RST, so the client's + /// SNIWritePacket fails - the exact condition from the ICM dump. + /// + internal sealed class AttentionBreakingProxy : IDisposable + { + private readonly TcpListener _listener; + private readonly IPEndPoint _backend; + private Socket? _clientSocket; + private Socket? _backendSocket; + private volatile bool _broken; + + public int Port { get; } + + public AttentionBreakingProxy(IPEndPoint backend) + { + _backend = backend; + _listener = new TcpListener(IPAddress.Loopback, 0); + _listener.Start(); + Port = ((IPEndPoint)_listener.LocalEndpoint).Port; + Task.Run(AcceptAndPump); + } + + /// + /// Break the client's write direction: subsequent client writes RST while + /// its pending read stays outstanding (the backend sends nothing). + /// + public void BreakClientWrites() + { + _broken = true; + try + { + // SD_RECEIVE: data the client sends afterwards (the attention) is + // answered with a TCP RST, failing the client's write, while the + // client's pending read remains outstanding. + _clientSocket?.Shutdown(SocketShutdown.Receive); + } + catch + { + // best effort + } + } + + private void AcceptAndPump() + { + try + { + _clientSocket = _listener.AcceptSocket(); + _backendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _backendSocket.Connect(_backend); + + // client -> backend on a worker; backend -> client on this thread. + Task.Run(() => Pump(_clientSocket, _backendSocket, clientToBackend: true)); + Pump(_backendSocket, _clientSocket, clientToBackend: false); + } + catch + { + // Teardown races are expected; ignore. + } + } + + private void Pump(Socket from, Socket to, bool clientToBackend) + { + byte[] buffer = new byte[8192]; + try + { + while (true) + { + int n = from.Receive(buffer); + if (n <= 0) + { + break; + } + + // Once broken, stop forwarding client bytes to the backend; the + // client's write will RST on its receive-shutdown socket. + if (clientToBackend && _broken) + { + break; + } + + to.Send(buffer, 0, n, SocketFlags.None); + } + } + catch + { + // Expected on RST / teardown. + } + } + + public void Dispose() + { + try { _listener.Stop(); } catch { /* ignore */ } + try { _clientSocket?.Dispose(); } catch { /* ignore */ } + try { _backendSocket?.Dispose(); } catch { /* ignore */ } + } + } + + /// + /// Attempt 5 (matches the ICM dump's mechanism): the reported deadlock is a + /// RE-ENTRANT SNIClose. An async read times out; the driver sends an + /// attention from within ReadAsyncCallback; the attention + /// SNIWritePacket fails; the legacy driver then calls + /// SqlConnection.Close() SYNCHRONOUSLY on the callback thread, so + /// SNIClose spins in SNI_Conn::WaitForActiveCallbacks() waiting + /// for the very callback it is running within - a self-deadlock. + /// + /// + /// Microsoft.Data.SqlClient defers the close off the callback thread via its + /// asyncClose path (ThrowExceptionAndWarning wraps the close in + /// Task.Factory.StartNew), so the read settles promptly and this test + /// passes - a regression guard for that fix. + /// + /// + /// + /// NOTE: this exercises the async-read-timeout + broken-write path and asserts + /// no deadlock, but it does NOT deterministically force the re-entrant close + /// on the legacy driver: that requires the exact TCP state from the dump + /// (the attention write fails while the read stays pending - a half-broken + /// connection), which is very hard to synthesize (a RST kills both + /// directions; the SQL EE analysis likewise noted it is "very difficult to + /// capture"). The definitive evidence is the dump plus the code review of the + /// asyncClose deferral in Microsoft.Data.SqlClient. + /// + /// + public class SNICloseCallbackReentrancyReproTests + { + private static readonly TimeSpan SettleBudget = TimeSpan.FromSeconds(45); + private static readonly TimeSpan BatchBudget = TimeSpan.FromSeconds(30); + + [Fact] + public void CloseFromReadCallbackOnAttentionFailure_DoesNotDeadlock() + { + using ManualResetEventSlim batchReceived = new(false); + using ManualResetEventSlim releaseResponse = new(false); + + TdsServerArguments arguments = new(); + using TdsServer server = new( + new ReproSupport.StallingQueryEngine(arguments, batchReceived, releaseResponse), + arguments); + server.Start(); + + using AttentionBreakingProxy proxy = new(new IPEndPoint(IPAddress.Loopback, server.EndPoint.Port)); + + SqlConnectionStringBuilder builder = new() + { + DataSource = $"127.0.0.1,{proxy.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + // MARS off and pooling off, matching the reported configuration and + // ensuring Close() tears down the physical connection (reaching SNIClose). + Pooling = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }; + + SqlConnection connection = new(builder.ConnectionString); + SqlCommand? command = null; + Task? readTask = null; + bool settledInTime = false; + try + { + connection.Open(); + + // Short command timeout so the pending async read times out, which + // drives OnTimeoutCore -> SendAttention from within ReadAsyncCallback. + command = new("SELECT 1", connection) { CommandTimeout = 2 }; + readTask = command.ExecuteReaderAsync(); + + Assert.True( + batchReceived.Wait(BatchBudget), + "The server never received the batch; the async read was not in flight."); + + // Break the client's write path so the timeout attention's + // SNIWritePacket fails, forcing Close() from within ReadAsyncCallback. + proxy.BreakClientWrites(); + + try + { + settledInTime = readTask.Wait(SettleBudget); + } + catch (AggregateException) + { + // Faulting/cancelling is the healthy outcome; what matters is it settled. + settledInTime = true; + } + + Assert.True( + settledInTime, + $"ExecuteReaderAsync did not settle within {SettleBudget.TotalSeconds:N0}s after the " + + "command-timeout attention write failed. This indicates the re-entrant SNIClose " + + "deadlock: Close() called from within ReadAsyncCallback -> SNIClose -> " + + "WaitForActiveCallbacks (ADO.Net #43847 / ICM 775308542)."); + } + finally + { + releaseResponse.Set(); + command?.Dispose(); + // Only dispose the connection if we did NOT deadlock; a deadlocked + // connection's Dispose() would also hang in SNIClose. + if (settledInTime) + { + try { connection.Dispose(); } catch { /* ignore */ } + } + } + } + } + + /// + /// A transparent TCP proxy that relays bytes between a client and a backend + /// TDS server and can hard-RST the client connection on demand + /// (LingerState 0 + Close), used by the soak to break the connection at a + /// timed offset relative to the async read's command timeout. + /// + internal sealed class RstProxy : IDisposable + { + private readonly TcpListener _listener; + private readonly IPEndPoint _backend; + private Socket? _clientSocket; + private Socket? _backendSocket; + + public int Port { get; } + + public RstProxy(IPEndPoint backend) + { + _backend = backend; + _listener = new TcpListener(IPAddress.Loopback, 0); + _listener.Start(); + Port = ((IPEndPoint)_listener.LocalEndpoint).Port; + Task.Run(AcceptAndPump); + } + + /// Hard-reset the client connection (sends a TCP RST). + public void Rst() + { + try + { + Socket? s = _clientSocket; + if (s != null) + { + s.LingerState = new LingerOption(true, 0); + s.Close(); + } + } + catch + { + // best effort + } + } + + private void AcceptAndPump() + { + try + { + _clientSocket = _listener.AcceptSocket(); + _backendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _backendSocket.Connect(_backend); + Task.Run(() => Pump(_clientSocket, _backendSocket)); + Pump(_backendSocket, _clientSocket); + } + catch + { + // Teardown races are expected; ignore. + } + } + + private static void Pump(Socket from, Socket to) + { + byte[] buffer = new byte[8192]; + try + { + while (true) + { + int n = from.Receive(buffer); + if (n <= 0) + { + break; + } + to.Send(buffer, 0, n, SocketFlags.None); + } + } + catch + { + // Expected on RST / teardown. + } + } + + public void Dispose() + { + try { _listener.Stop(); } catch { /* ignore */ } + try { _clientSocket?.Dispose(); } catch { /* ignore */ } + try { _backendSocket?.Dispose(); } catch { /* ignore */ } + } + } + + /// + /// Soak that hunts for the re-entrant SNIClose deadlock probabilistically. + /// Many concurrent workers repeatedly open a connection, start an async read + /// that the server stalls, and RST the connection at ~the command-timeout + /// instant (with jitter), trying to land in the tiny window where the SNI + /// read timeout has fired but the timeout attention has not yet been written - + /// so the attention SNIWritePacket fails and the legacy driver closes + /// synchronously from within ReadAsyncCallback. A hang (a read that + /// never settles) means the re-entrant SNIClose was hit. + /// + /// + /// Tunable via environment variables: + /// SNICLOSE_SOAK_WORKERS (default 16; the ICM saw a "~36-thread" condition), + /// SNICLOSE_SOAK_SECONDS (default 60). + /// The test fails if it reproduces (green = did not reproduce this run). + /// + /// + public class ReentrantSNICloseSoakTests + { + private sealed class BoundedStallQueryEngine : QueryEngine + { + private readonly int _stallMs; + + public BoundedStallQueryEngine(TdsServerArguments arguments, int stallMs) + : base(arguments) + { + _stallMs = stallMs; + } + + protected override TDSMessageCollection CreateQueryResponse( + ITDSServerSession session, + TDSSQLBatchToken batchRequest) + { + // Stall every query long enough that the client's short command + // timeout fires first; the (eventual) response goes to an already + // RST-torn-down connection and the handler thread unwinds. + Thread.Sleep(_stallMs); + return base.CreateQueryResponse(session, batchRequest); + } + } + + private static int EnvInt(string name, int fallback) => + int.TryParse(Environment.GetEnvironmentVariable(name), out int v) && v > 0 ? v : fallback; + + /// + /// Gate: the soak is opt-in (set SNICLOSE_SOAK_ENABLE) because it is + /// heavy, rarely fires, and - when it DOES reproduce - poisons the + /// process (hung SNIClose threads cascade into later tests). Skipped by + /// default so normal harness runs stay clean and deterministic. + /// + public static bool SoakEnabled() => + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SNICLOSE_SOAK_ENABLE")); + + [ConditionalFact(typeof(ReentrantSNICloseSoakTests), nameof(SoakEnabled))] + public void ConcurrentTimeoutWithRstNearTimeout_DoesNotReentrantlyDeadlock() + { + int workers = EnvInt("SNICLOSE_SOAK_WORKERS", 16); + TimeSpan soak = TimeSpan.FromSeconds(EnvInt("SNICLOSE_SOAK_SECONDS", 60)); + const int commandTimeoutSec = 1; + const int stallMs = 6000; + TimeSpan perOpBudget = TimeSpan.FromSeconds(20); + + TdsServerArguments arguments = new(); + using TdsServer server = new(new BoundedStallQueryEngine(arguments, stallMs), arguments); + server.Start(); + IPEndPoint backend = new(IPAddress.Loopback, server.EndPoint.Port); + + using CancellationTokenSource cts = new(soak); + long attempts = 0; + string? hangInfo = null; + object gate = new(); + + void Worker(int id) + { + Random rng = new(unchecked(id * 397 ^ Environment.TickCount)); + while (!cts.IsCancellationRequested && Volatile.Read(ref hangInfo) is null) + { + RstProxy? proxy = null; + SqlConnection? conn = null; + SqlCommand? cmd = null; + Task? readTask = null; + bool settled = false; + try + { + proxy = new RstProxy(backend); + string cs = new SqlConnectionStringBuilder + { + DataSource = $"127.0.0.1,{proxy.Port}", + Encrypt = SqlConnectionEncryptOption.Optional, + Pooling = false, + ConnectTimeout = 15, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif + }.ConnectionString; + + conn = new SqlConnection(cs); + conn.Open(); + cmd = new SqlCommand("SELECT 1", conn) { CommandTimeout = commandTimeoutSec }; + readTask = cmd.ExecuteReaderAsync(); + Interlocked.Increment(ref attempts); + + // Fire the RST at ~the command-timeout instant, with jitter, + // trying to land just after the SNI read timeout but before + // (or during) the attention write. + int rstDelayMs = commandTimeoutSec * 1000 + rng.Next(-40, 100); + RstProxy captured = proxy; + _ = Task.Delay(rstDelayMs).ContinueWith(_ => + { + try { captured.Rst(); } catch { /* ignore */ } + }); + + try + { + settled = readTask.Wait(perOpBudget); + } + catch (AggregateException) + { + settled = true; // faulted/cancelled = healthy + } + + if (!settled) + { + lock (gate) + { + hangInfo ??= $"worker {id}: ExecuteReaderAsync did not settle within " + + $"{perOpBudget.TotalSeconds:N0}s (attempt #{Interlocked.Read(ref attempts)})"; + } + } + } + catch + { + // Per-iteration connect/read failures under the RST storm are expected. + } + finally + { + cmd?.Dispose(); + // Only tear down if this iteration settled; a hung connection's + // Dispose() would also block in SNIClose. + if (settled) + { + try { conn?.Dispose(); } catch { /* ignore */ } + try { proxy?.Dispose(); } catch { /* ignore */ } + } + } + } + } + + Thread[] threads = new Thread[workers]; + for (int i = 0; i < workers; i++) + { + int id = i; + threads[i] = new Thread(() => Worker(id)) { IsBackground = true, Name = $"SoakWorker{id}" }; + threads[i].Start(); + } + + // Wait until the soak window elapses or a hang is detected, then give + // workers a moment to unwind. + DateTime deadline = DateTime.UtcNow + soak + perOpBudget + TimeSpan.FromSeconds(10); + while (DateTime.UtcNow < deadline + && Volatile.Read(ref hangInfo) is null + && threads.Any(t => t.IsAlive)) + { + Thread.Sleep(500); + } + foreach (Thread t in threads) + { + t.Join(TimeSpan.FromSeconds(2)); + } + + Assert.True( + Volatile.Read(ref hangInfo) is null, + $"Reproduced the re-entrant SNIClose deadlock: {hangInfo}. Total attempts: " + + $"{Interlocked.Read(ref attempts)}. (close from within ReadAsyncCallback -> SNIClose -> " + + "WaitForActiveCallbacks; ADO.Net #43847 / ICM 775308542)."); + } + } +} diff --git a/tools/SniCloseLegacyRepro/SniCloseLegacyRepro.csproj b/tools/SniCloseLegacyRepro/SniCloseLegacyRepro.csproj new file mode 100644 index 0000000000..7c7f5507de --- /dev/null +++ b/tools/SniCloseLegacyRepro/SniCloseLegacyRepro.csproj @@ -0,0 +1,115 @@ + + + + + + + net462;net47;net472;net481;net10.0 + + 4.9.1 + enable + latest + disable + true + false + + false + false + + $(NoWarn);CS0618 + + + + + + + + + + + + + + + + xunit.runner.json + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/SniCloseLegacyRepro/run-in-container.ps1 b/tools/SniCloseLegacyRepro/run-in-container.ps1 new file mode 100644 index 0000000000..02d1901b45 --- /dev/null +++ b/tools/SniCloseLegacyRepro/run-in-container.ps1 @@ -0,0 +1,114 @@ +# 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. + +# Runs the net47 build of this harness inside a .NET Framework Windows container, +# so the tests bind the container's OLD in-box System.Data.dll (e.g. 4.7.4081.0) +# instead of the host's. The framework container has no dotnet CLI, so we run the +# tests with xunit.console.exe. +# +# Examples: +# # in-process tests only (no SQL Server needed): +# .\run-in-container.ps1 +# +# # include the live MARS + stress tests against a host SQL Server: +# $env:SNICLOSE_CONNSTR = "Server=127.0.0.1,1434;Database=master;User ID=sa;Password=***;TrustServerCertificate=true" +# .\run-in-container.ps1 +# +# # a different framework image / version: +# .\run-in-container.ps1 -Image mcr.microsoft.com/dotnet/framework/runtime:4.8-windowsservercore-ltsc2019 + +[CmdletBinding()] +param( + [string]$Framework = 'net47', + [string]$Image = 'mcr.microsoft.com/dotnet/framework/runtime:4.7-windowsservercore-ltsc2016', + [string]$ConnectionString = $env:SNICLOSE_CONNSTR, + # Container-reachable host IP that the loopback SQL Server is bridged to. + # Defaults to the Docker NAT gateway (the host's 'vEthernet (nat)' adapter). + [string]$SqlHost, + [string]$RunnerVersion = '2.9.3', + # Constrain the hyperv container VM to this many processors (0 = unconstrained). + # The ICM environment had 4 procs; races can be CPU-count sensitive. + [int]$Cpus = 0, + [switch]$SkipBuild +) + +$ErrorActionPreference = 'Stop' +$projDir = $PSScriptRoot +$proj = Join-Path $projDir 'SniCloseLegacyRepro.csproj' + +# --- Build the net47 output (host-side; independent of the docker engine) ------ +if (-not $SkipBuild) { + Write-Host "Building $Framework ..." -ForegroundColor Cyan + dotnet build $proj -c Debug -f $Framework | Out-Host + if ($LASTEXITCODE -ne 0) { throw "Build failed." } +} + +$outDir = Join-Path $projDir "bin\Debug\$Framework" +if (-not (Test-Path (Join-Path $outDir 'SniCloseLegacyRepro.dll'))) { + throw "Build output not found: $outDir (build first, or omit -SkipBuild)." +} + +# --- Stage xunit.console.exe next to the test assembly ------------------------- +# The framework container has no dotnet CLI, so run tests with the console runner. +$gp = (dotnet nuget locals global-packages --list) -replace '^.*?:\s*', '' +$runnerTools = Join-Path $gp "xunit.runner.console\$RunnerVersion\tools\$Framework" +if (-not (Test-Path $runnerTools)) { + $runnerTools = Join-Path $gp "xunit.runner.console\$RunnerVersion\tools\net472" +} +if (-not (Test-Path (Join-Path $runnerTools 'xunit.console.exe'))) { + throw "xunit.console.exe not found under $runnerTools. Is xunit.runner.console $RunnerVersion restored?" +} +Copy-Item (Join-Path $runnerTools '*') $outDir -Force + +# --- Ensure Docker is on the Windows engine ----------------------------------- +$null = docker context use desktop-windows 2>$null +$serverOs = docker version --format '{{.Server.Os}}' 2>$null +if ($serverOs -ne 'windows') { + throw "Docker is not on the Windows engine (Server OS='$serverOs'). Switch with: & 'C:\Program Files\Docker\Docker\DockerCli.exe' -SwitchWindowsEngine" +} + +# --- Build the container args -------------------------------------------------- +# A Windows container cannot reach the host's loopback SQL Server directly, and +# host.docker.internal is not injected for hyperv-isolated Windows containers. +# Instead we target the Docker NAT gateway IP (the host's 'vEthernet (nat)' +# adapter); a one-time elevated host portproxy must bridge that IP to loopback: +# netsh interface portproxy add v4tov4 listenaddress= listenport=1434 ` +# connectaddress=127.0.0.1 connectport=1434 +$envArgs = @() +if ($ConnectionString) { + if (-not $SqlHost) { + $SqlHost = Get-NetIPAddress -InterfaceAlias 'vEthernet (nat)' -AddressFamily IPv4 -ErrorAction SilentlyContinue | + Select-Object -First 1 -ExpandProperty IPAddress + } + if (-not $SqlHost) { + throw "Could not determine a container-reachable host IP; pass -SqlHost explicitly." + } + $cs = $ConnectionString ` + -replace '127\.0\.0\.1', $SqlHost ` + -replace '(?i)(?<=[=;\s])localhost(?=[,;]|$)', $SqlHost + $envArgs = @('-e', "SNICLOSE_CONNSTR=$cs") + Write-Host "Live tests enabled (SQL Server via $SqlHost; requires a host portproxy to 127.0.0.1)." -ForegroundColor Cyan +} +else { + Write-Host "No connection string: live MARS + stress tests will be skipped." -ForegroundColor Yellow +} + +# Forward soak tuning knobs if set on the host. +foreach ($name in 'SNICLOSE_SOAK_WORKERS', 'SNICLOSE_SOAK_SECONDS') { + $val = [Environment]::GetEnvironmentVariable($name) + if ($val) { $envArgs += @('-e', "$name=$val") } +} + +Write-Host "Running tests in $Image (isolation=hyperv) ..." -ForegroundColor Cyan +$cpuArgs = @() +if ($Cpus -gt 0) { + $cpuArgs = @('--cpu-count', "$Cpus") + Write-Host "Container VM constrained to $Cpus processor(s)." -ForegroundColor Cyan +} +docker run --rm --isolation=hyperv @cpuArgs @envArgs -v "${outDir}:C:\app" -w 'C:\app' $Image ` + C:\app\xunit.console.exe C:\app\SniCloseLegacyRepro.dll -parallel none + +$code = $LASTEXITCODE +Write-Host "xunit.console.exe exit code: $code" -ForegroundColor $(if ($code -eq 0) { 'Green' } else { 'Red' }) +exit $code From 3d18ee0847882c6ccb31bcda06c711626ed522be Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Fri, 24 Jul 2026 10:38:03 -0700 Subject: [PATCH 205/298] Background pool warmup and replenishment for ChannelDbConnectionPool (#4452) * Add background pool warmup spec Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Implement background pool warmup and replenishment for ChannelDbConnectionPool Adds serial, rate-limited background warmup that pre-creates connections up to Min Pool Size on Startup and replenishes whenever the pool drops below the minimum (destroy-on-return, idle-timeout eviction, pruning). Warmup failures are absorbed without triggering the pool error state, warmup is coalesced to a single loop, cancels promptly on shutdown, and stops on generation change. Implements specs/003-pool-warmup/spec.md. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address review: cheap warmup fast-path and distinguish rate-limit from failure - RequestWarmup() now returns early when Count >= MinPoolSize, so hot-path callers (e.g. every RemoveConnection) don't schedule a thread-pool work item just to run a pass that immediately exits. - OpenNewInternalConnection(isWarmup: true) now rethrows genuine creation failures (still without entering the blocking-period error state) instead of absorbing them as null. A null return now unambiguously means the rate limiter is saturated. WarmupPassAsync catches real failures, traces, and stops the pass rather than retrying on a 50ms cadence, avoiding a noisy spin when opens persistently fail. - Strengthen the failure test to assert warmup does not spin on persistent creation failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address review: quiet expected disposal in Shutdown, assert async request contract - Shutdown now treats ObjectDisposedException from _warmupCts.Cancel()/Dispose() as an expected no-op instead of tracing it as a failure, so it never emits misleading trace noise. (Shutdown is already guarded idempotent via the _shutdownInitiated CAS, but this hardens the per-step cleanup.) - Warmup_RateLimiterSaturated_UserSharesSameLimiter now asserts the queued async TryGetConnection call returns false with no inline connection, making the async-request contract explicit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address review: null-safe owning connection in factory, fix warmup cancellation docs - SqlConnectionFactory.CreateConnection: guard the User Instance path's write to sqlOwningConnection._applyTransientFaultHandling with a null check. Warmup creates connections with a null owning connection, which would otherwise NullReferenceException for 'User Instance=true' connection strings. Matches the null-safe reads already present earlier in the method. - Correct the _warmupCts field and Shutdown comments: the token stops the warmup loop at its await points / pre-create checks, but does not abort an in-progress synchronous physical open (the factory does not yet accept a cancellation token), so the docs no longer claim behavior the implementation doesn't provide. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address review: align spec async wording with implementation Warmup runs on a background task (never blocking callers, no sync-over-async in the loop), but the physical connection open is still synchronous because the factory does not yet expose an async open. Reword the implementation note and acceptance criterion so the spec no longer claims 'async I/O throughout'. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Make warmup handshake tests deterministic via a scheduler seam Startup_UserRequestDuringWarmup_ServedImmediately flaked on CI, timing out while waiting for warmup's first background creation. Root cause is thread-pool scheduling latency: warmup runs on a queued work item whose first physical create blocks a worker on the test gate, and under xUnit parallel execution the shared pool can be momentarily starved, delaying the work item past the wait. Rather than mutating global ThreadPool.SetMinThreads (which would perturb the environment other parallel tests share), add a narrow, opt-in test seam: the pool constructor now accepts an optional warmupLoopScheduler that controls how the warmup loop is launched. Production is unchanged (defaults to Task.Run); the warmup tests inject a scheduler that runs the loop on a dedicated thread, so warmup's gated first create can never be delayed by thread-pool starvation. This mirrors the existing timeProvider test-injection precedent on the constructor. Also replace the 5s handshake waits with a generous 30s upper bound (only fires on a genuine hang) and run the user request on a dedicated thread so the CreateCount==2 seriality assertion is deterministic. Applied to the three gated tests that share the handshake primitive. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address review: warmup respects error state, condense loop, harden scheduling - Warmup now respects the pool's blocking-period error state (mirroring the WaitHandle pool): the loop stands down while ErrorOccurred is set, though it still never enters or clears that state. Added CanWarmup(token) capturing the running/cancellation/error-state guard, plus a unit test that drives the pool into the error state while warmup is parked mid-create and asserts warmup stops instead of replenishing. Updated the spec (Story 3 scenario 4). - Condensed the warmup region: merged RunWarmupLoopAsync and WarmupPassAsync into a single method and factored the repeated loop guard into CanWarmup, reducing fragmentation. - Removed the explicit stale-generation check in the warmup pass; a Clear that races an in-flight create is handled by the existing lazy liveness/generation check on retrieval. Updated the spec note accordingly. - Hardened TryStartWarmupLoop: if scheduling the loop throws (injected scheduler or Task.Run), release the single-loop guard and trace so warmup isn't pinned off for the life of the pool. - Converted test-method comments to XML docs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address review: full error-state parity, simpler coalescing, harden warmup loop - Warmup now fully mirrors the WaitHandle pool's replenishment: it goes through the same OpenNewInternalConnection path as user requests, so a genuine open failure enters the blocking-period error state and a successful open clears it. Removed the isWarmup parameter and all its error-state special-casing; warmup-specific behavior (absorbing the rethrow, standing down while blocking) now lives entirely in the warmup loop and CanWarmup. Updated Story 3 and the two affected tests (a user request during the blocking window now fast-fails). - Dropped the _warmupRequested counter: coalescing relies solely on the _warmupLoopRunning guard, and the single-pass loop re-reads Count each iteration to absorb below-minimum drops that happen while it runs. Requests arriving while the guard is set are simply dropped. - Defaulted _warmupLoopScheduler to Task.Run so TryStartWarmupLoop no longer needs a null-check conditional. - RunWarmupLoopAsync now releases the single-loop guard in a finally covering every exit path (including the ObjectDisposedException early return), so the flag can't be leaked. Documented why the loop is resilient to _warmupCts being disposed mid-run (token is read once under a guard; Cancel always precedes Dispose, so post-capture reads observe cancellation, not disposal). - Redesigned the stand-down test to gate the warmup loop's start (its own successful create would now clear the error state), and bounded a previously unbounded await in the rate-limiter test with a timeout. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Inline single-use warmup helpers; reconcile spec/comments Merge TryStartWarmupLoop into RequestWarmup and inline the CanWarmup guard into the warmup loop condition, since each was used from only one call site. Soften residual CanWarmup references in test comments and the spec now that the method no longer exists. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Expose warmup task and remove test scheduler seam Expose the warmup loop as an internal WarmupLoopTask property so tests can await a pass to deterministic completion instead of polling. Remove the test-only warmup loop scheduler seam (always use Task.Run) and make RequestWarmup internal so tests can invoke it directly. Remove the rate-limit retry loop in RunWarmupLoopAsync: on limiter saturation the pass now ends, relying on concurrent user creations plus re-triggering via RemoveConnection to converge to MinPoolSize. Rewrite the warmup unit tests to await WarmupLoopTask rather than spin-poll, and drive the error-state stand-down test via a failing user request plus a direct RequestWarmup invocation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address review comments on warmup task exposure - Make WarmupLoopTask a plain auto-property (test-only, internal) instead of a Volatile.Read backing field, and move it into the properties region. - Release the single-loop guard with Interlocked.Exchange at both reset sites, symmetric with the Interlocked.CompareExchange acquire. - Rewrite Warmup_RateLimiterSaturated to await the deferred user request and assert the shared-limiter denial after the pool settles, removing the spin-based WaitFor poll. - Remove the WaitFor and CheckOut test helpers (inlined at their call sites). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Reconcile warmup spec with implementation The implementation is the source of truth. Correct drifted claims: - Pruning respects the Min Pool Size floor and never itself reduces the pool below the minimum; reword the Description and Story 5 accordingly. - Idle-timeout eviction is lazy (on retrieval), not a background sweep. - Replenishment restores to minimum in a single pass only when not racing a concurrent drain (Clear) or a saturated rate limiter; otherwise convergence completes across re-triggered passes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix flaky rate-limiter warmup test and guard error state after shutdown - Fix CI failure in Warmup_RateLimiterSaturated_UserSharesSameLimiter: the post-settle TotalFailedLeases assertion raced the permit being released, so the deferred user request could acquire the freed permit and never record a denial. Prove shared-limiter saturation deterministically instead, by attempting to acquire the limiter's only permit from the test thread while warmup still holds it (a no-op denied lease), with no spin. - Bound AwaitWarmupToMinimum with HandshakeTimeout so a regression that never reaches the minimum surfaces as a deterministic timeout instead of hanging the test run. - Only enter the blocking-period error state while the pool is Running: an in-flight synchronous open can fail after Shutdown has disposed _errorState, and entering then would re-arm its exit timer past teardown. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address review: add idle-timeout eviction replenishment test; add CreateCount to DoomableConnectionFactory - Add Replenish_AfterIdleTimeoutEviction_RefillsToMinimum test (Story 5): covers the case where an idle connection is evicted by IsLiveConnection due to idle-timeout, dropping the pool below MinPoolSize and triggering replenishment via RemoveConnection -> RequestWarmup. The test backdates ReturnedTime to simulate expiry, verifies TryGetConnection evicts the stale connection and returns the fresh one, then awaits WarmupLoopTask and asserts CreateCount increased by 1 and pool.Count returns to MinPoolSize. - Add CreateCount tracking to DoomableConnectionFactory so replenishment tests can assert exactly how many physical connections were created. - Add using Microsoft.Data.SqlClient.Tests.Common for LocalAppContextSwitchesHelper. - Update PR description: remove stale 'test seam' mention; RequestWarmup now always uses Task.Run directly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Make rate-limiter pool tests deterministic without spin/timing Replace the flaky SpinWait polling in three ChannelDbConnectionPool rate-limiter tests with deterministic invariants, eliminating the CI timeouts (e.g. 'Timed out waiting for the second request to be denied by the rate limiter'). No production code changes. - RateLimiter_PermitDenied_ReusesReturnedConnection: rely on the held permit - while it is held every AttemptAcquire is denied, so the waiting request can never create and must reuse the returned connection. Drop the SpinWait on TotalFailedLeases. - RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysicalConnection: bump maxPoolSize 2->3 so the lease-release poke (gated on ReservationCount < MaxPoolSize) always fires even while the denied waiter still holds its transient slot reservation; the unbounded idle channel buffers the poke so it is never lost. Drop the SpinWait. - RateLimiter_LeaseDisposedOnFailure_DoesNotStarvePool: the lease is disposed in the createCallback finally before the failure completes the awaited request, so after all opens throw the permits are already back. Replace the SpinWait with a direct assert. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Align rate-limiter test names/descriptions with deterministic behavior The deterministic rewrites weakened some guarantees these tests' names and summaries claimed, so update them to match what is now verified. - Rename RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysicalConnection to RateLimiter_LeaseReleaseAllowsRateLimitedWaiterToCreatePhysicalConnection: the parked-and-woken path is no longer guaranteed every run, so drop the 'Wakes' claim and rewrite the summary to describe the interleaving-robust invariant (the single permit serializes the opens; B creates its own connection once A releases its lease). - RateLimiter_PermitDenied_ReusesReturnedConnection: reword the summary to drop 'falls back to waiting' (a park is no longer guaranteed) and state the held-permit invariant that makes reuse the only possible outcome. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address Copilot review comments on warmup path - ChannelDbConnectionPool: filter both warmup catch-all blocks with ADP.IsCatchableExceptionType so non-catchable exceptions (e.g. OutOfMemoryException) propagate instead of being absorbed into a pool that keeps running in a corrupted state; the single-loop guard is still released on every path. - WarmupTest.AwaitWarmupToMinimum: wait briefly instead of re-requesting in a tight loop when no warmup task is published yet, so the bounded wait never busy-spins a CPU core on loaded CI. - WarmupTest: wrap the three gated tests in try/finally so createGate is always released even if an assertion throws, so warmup's parked creation in the gated factory never strands a thread-pool thread. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Make idle-timeout eviction test deterministic via injected TimeProvider Route the ChannelDbConnectionPool idle-timeout stamp (ReturnInternalConnection) and expiry check (IsLiveConnection) through a single injected TimeProvider, defaulting to TimeProvider.System so production behavior is unchanged. Add a SetReturnedTime(DateTime) overload on DbConnectionInternal so the pool can source the return stamp from that provider. Rewrite Replenish_AfterIdleTimeoutEviction_RefillsToMinimum to inject a FakeTimeProvider and Advance() the clock past the timeout between returning the two connections, so exactly one connection expires. This removes the wall-clock race that could evict both connections under a slow agent/CI and produce an extra create, making the create-count assertion deterministic. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- specs/003-pool-warmup/spec.md | 95 +++ .../Data/ProviderBase/DbConnectionInternal.cs | 15 +- .../ConnectionPool/ChannelDbConnectionPool.cs | 332 +++++++- .../Data/SqlClient/SqlConnectionFactory.cs | 10 +- .../ChannelDbConnectionPoolTest.cs | 83 +- .../ChannelDbConnectionPoolWarmupTest.cs | 728 ++++++++++++++++++ 6 files changed, 1205 insertions(+), 58 deletions(-) create mode 100644 specs/003-pool-warmup/spec.md create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolWarmupTest.cs diff --git a/specs/003-pool-warmup/spec.md b/specs/003-pool-warmup/spec.md new file mode 100644 index 0000000000..8c41b88470 --- /dev/null +++ b/specs/003-pool-warmup/spec.md @@ -0,0 +1,95 @@ +# Feature Specification: Background Pool Warmup + +**Feature Branch**: `dev/mdaigle/pool-warmup` (branched from `dev/mdaigle/pool-channel-rate-limiting`) +**Created**: 2026-07-16 +**Status**: Draft + +## Description + +Implement background pool warmup for `ChannelDbConnectionPool`. When the pool starts, it asynchronously pre-creates connections up to Min Pool Size in the background, serially (one at a time), through the shared rate-limiting mechanism used by user-initiated requests. + +Whenever the pool count drops below Min Pool Size for any reason (a connection destroyed on return, idle-timeout eviction, or any other removal that crosses the floor), the pool automatically triggers replenishment using the same serial, rate-limited creation path. Pruning itself respects the Min Pool Size floor and never reduces the pool below the minimum; it is only mentioned here because it shares the same removal choke point that triggers replenishment. + +## User Scenarios & Testing + +### User Story 1 — Background Warmup on Pool Creation (P1) + +When a pool is created with Min Pool Size > 0, it pre-creates connections in the background so early application requests find ready-to-use connections. + +**Acceptance Scenarios**: + +1. **Given** a pool is created with minimum pool size of N, **When** startup completes, **Then** the pool begins creating connections in the background up to N. +2. **Given** a pool is warming up, **When** a user opens a connection before warmup finishes, **Then** the user request is served immediately without waiting for warmup to complete. +3. **Given** a pool is warming up, **When** warmup creates each connection, **Then** each connection is created one at a time (serially), not in parallel. + +--- + +### User Story 2 — Warmup Through Shared Rate Limiter (P1) + +Warmup creates connections through the same rate-limiting mechanism as user-initiated requests, preventing warmup from overwhelming the server. + +**Acceptance Scenarios**: + +1. **Given** warmup is creating connections, **When** a user request also needs a new connection, **Then** both warmup and user requests compete fairly through the shared rate limiter. +2. **Given** the rate limiter is at capacity, **When** warmup attempts to create the next connection, **Then** warmup does not bypass the limiter: it ends the current pass without creating (rather than spinning or waiting on a permit). Saturation only occurs while user requests are actively creating connections, which fill the pool themselves; any later drop below the minimum re-triggers warmup once capacity has freed up. + +--- + +### User Story 3 — Warmup Failure Resilience (P1) + +If a connection fails to open during warmup, the failure is silently absorbed on the warmup loop — it is never propagated to the caller as an unhandled exception. Warmup participates in the pool's blocking-period error state exactly like an on-demand creation (mirroring the legacy WaitHandle pool): a genuine open failure enters the error state, and a successful open clears it. The pool remains operational — once the blocking period expires, user requests create connections on demand as needed. + +**Acceptance Scenarios**: + +1. **Given** warmup is in progress, **When** a connection fails to open, **Then** the failure is traced/logged and absorbed by the warmup loop rather than surfacing as an unhandled exception. +2. **Given** warmup fails and has entered the blocking-period error state, **When** a user opens a connection during the blocking window, **Then** the user request fast-fails with the cached exception (matching the WaitHandle pool); once the blocking period expires it creates a connection on demand and succeeds normally. +3. **Given** warmup's open fails, **When** the pool's error state is checked, **Then** the pool IS in the blocking-period error state — warmup uses the same shared creation path as user requests and enters/clears that state identically. +4. **Given** the pool is already in the blocking-period error state (driven there by a failing request), **When** warmup would otherwise replenish, **Then** warmup stands down while the error state is active rather than piling more doomed opens onto a struggling server (the warmup loop skips replenishment while `ErrorOccurred`, mirroring the WaitHandle pool). + +--- + +### User Story 4 — Warmup Cancellation on Shutdown (P2) + +When a pool is shut down while warmup is still in progress, warmup stops promptly. No new connections are created after shutdown begins. + +**Acceptance Scenarios**: + +1. **Given** warmup is in progress, **When** the pool is shut down, **Then** warmup stops and no further connections are created. +2. **Given** warmup is in progress, **When** the pool is shut down, **Then** any connections already created by warmup are cleaned up as part of normal shutdown. + +--- + +### User Story 5 — Replenishment on Any Below-Minimum Event (P2) + +Whenever the pool count drops below Min Pool Size, the pool automatically triggers warmup-style replenishment to restore to the minimum. Every connection removal funnels through a single choke point (`RemoveConnection`), which triggers replenishment when the removal leaves the pool below the minimum. + +**Acceptance Scenarios**: + +1. **Given** a pruning cycle runs, **When** it removes idle connections, **Then** it stops at the Min Pool Size floor and does not reduce the pool below the minimum (pruning shares the removal choke point but does not itself cause a below-minimum replenishment). +2. **Given** a connection is destroyed on return to the pool (broken, lifetime expired), **When** the pool count drops below the minimum, **Then** the pool queues a replenishment request. +3. **Given** idle-timeout eviction removes an expired connection on retrieval, **When** the pool count drops below minimum, **Then** the pool queues a replenishment request. +4. **Given** replenishment is triggered, **When** connections are created, **Then** they are created serially through the shared rate limiter, identical to initial warmup. +5. **Given** the pool is at or above the minimum pool size, **When** a connection is destroyed, **Then** no replenishment is triggered. + +## Implementation Notes + +- Warmup starts automatically when the pool's `Startup()` is called. +- Warmup runs on a background task, so it never blocks the caller (`Startup`/connection return) and uses no sync-over-async in the loop. The physical connection open itself is currently synchronous (executed on the background task); the connection factory does not yet expose an async open. +- Creates connections serially (one at a time) through the shared rate limiter. If the limiter is saturated (no permit available), warmup ends the pass rather than bypassing or spinning on the limiter; convergence to the minimum is handled by the concurrent user creations that saturated it and by re-triggering on the next below-minimum event. +- Warmup failures are traced/logged and absorbed by the warmup loop (never surfaced as an unhandled exception). Warmup goes through the same shared creation path as user requests, so a genuine open failure enters the blocking-period error state and a successful open clears it (mirroring the WaitHandle pool). While the error state is active, the warmup loop stands down rather than replenishing. +- If a `Clear` races with an in-flight warmup creation, the freshly created (now stale-generation) connection is not special-cased by warmup; it is harmlessly discarded by the liveness/generation check on its next retrieval. After a `Clear`, replenishment refills the pool to the minimum with fresh-generation connections. +- Idle-timeout eviction is lazy: an idle connection past its timeout is removed when it is next retrieved (via the liveness check), not by a background sweep. Pruning is separate and always respects the Min Pool Size floor. +- Concurrent warmup/replenishment requests are coalesced — a single `_warmupLoopRunning` guard ensures only one warmup loop executes at a time, and requests that arrive while it is running are dropped (the running loop re-reads the pool count each iteration and drives to the minimum on its own). +- Warmup is a no-op when Min Pool Size = 0. + +## Acceptance Criteria + +- When a pool starts with Min Pool Size > 0, background warmup begins immediately. +- After warmup completes, the pool contains Min Pool Size idle connections. +- Warmup runs on a background task without blocking callers and without sync-over-async in the loop (the physical open is currently synchronous, executed on the background task, pending async-open support in the factory). +- Warmup connections are created serially through the shared rate limiter. +- Warmup errors are traced/logged and never crash the application. Because warmup shares the on-demand creation path, a genuine open failure enters the pool's blocking-period error state (as it would for a user request) and a successful open clears it. +- If the pool is shut down or cleared during warmup, warmup stops gracefully. +- After any event that reduces the pool below Min Pool Size, the pool restores to the minimum through replenishment: a single warmup pass suffices when it is not racing a concurrent drain (e.g. `Clear`) or a saturated rate limiter; otherwise convergence completes across subsequent re-triggered passes. +- Concurrent warmup/replenishment requests are coalesced so only one warmup loop executes at a time. +- Unit tests validate warmup behavior for various Min Pool Size values (0, 1, N) and all replenishment triggers. 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 7e0ef67b94..00df93f788 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -98,7 +98,7 @@ internal DbConnectionInternal(ConnectionState state, bool hidePassword, bool all /// /// UTC timestamp of when this connection was last returned to the pool. - /// Stamped by . Initialized to in the constructor + /// Stamped by . Initialized to in the constructor /// so a freshly built connection is treated as "just used" until its first return. /// Internal setter exists to support deterministic unit tests without reflection. /// The pool reads this value to decide whether the connection has sat idle longer than the configured idle timeout. @@ -745,7 +745,18 @@ internal void PostPop(DbConnection newOwner) /// internal void SetReturnedTime() { - ReturnedTime = DateTime.UtcNow; + SetReturnedTime(DateTime.UtcNow); + } + + /// + /// Stamps with the supplied UTC time. Lets the pool source the + /// timestamp from its configured so the return stamp and the + /// idle-timeout expiry check read the same clock, which tests use to drive idle expiry + /// deterministically. Callers must pass a UTC value. + /// + internal void SetReturnedTime(DateTime utcNow) + { + ReturnedTime = utcNow; } internal void PrePush(DbConnection expectedOwner) 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 index 298c38b9db..63910c331e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -121,12 +121,36 @@ internal sealed class ChannelDbConnectionPool : IDbConnectionPool, IDisposable /// private readonly ConcurrencyLimiter? _connectionCreationRateLimiter; + /// + /// Time source for idle-timeout expiry and the blocking-period exit timer. Defaults to + /// in production; tests inject a fake provider so idle + /// expiry and the blocking period can be driven deterministically without real waits. + /// + private readonly TimeProvider _timeProvider; + /// /// Encapsulates the blocking-period error state for this pool: cached exception, exponential /// backoff timer, and synchronization. Created only when blocking period is enabled for /// this pool group. See . /// private readonly BlockingPeriodErrorState? _errorState; + + /// + /// Cancels in-flight background warmup/replenishment when the pool shuts down so no new + /// connections are created after shutdown begins (Story 4). The token is observed at the + /// warmup loop's await points and before each creation attempt, so it stops the loop and + /// prevents further attempts promptly. It cannot abort an already in-progress physical open: + /// that path is synchronous and the connection factory does not yet accept a cancellation + /// token (see the TODO in ). + /// + private readonly CancellationTokenSource _warmupCts = new(); + + /// + /// Coalescing guard ensuring only one warmup loop executes at a time. Transitioned + /// 0 -> 1 via by the first + /// requester to start the loop, and reset to 0 by the loop when it drains. + /// + private int _warmupLoopRunning; #endregion /// @@ -149,15 +173,16 @@ internal ChannelDbConnectionPool( MaxPoolSize = Convert.ToUInt32(PoolGroupOptions.MaxPoolSize); TransactedConnectionPool = new(this); _connectionCreationRateLimiter = connectionCreationRateLimiter; + // timeProvider is injected only by tests so idle-timeout expiry and the blocking-period + // exit timer can be driven deterministically; in production it is null and falls back to + // TimeProvider.System, whose UTC time equals DateTime.UtcNow. + _timeProvider = timeProvider ?? TimeProvider.System; _connectionSlots = new(MaxPoolSize); _idleChannel = new(); if (PoolGroup.IsBlockingPeriodEnabled()) { - // timeProvider is injected only by tests so the blocking-period exit timer can be - // driven deterministically; in production it is null and the error state falls back - // to TimeProvider.System. - _errorState = new BlockingPeriodErrorState(_instanceId, timeProvider: timeProvider); + _errorState = new BlockingPeriodErrorState(_instanceId, timeProvider: _timeProvider); } // Pruning is only useful when the pool can grow beyond MinPoolSize. @@ -221,6 +246,16 @@ public ConcurrentDictionary< private uint MaxPoolSize { get; } private int MinPoolSize => PoolGroupOptions.MinPoolSize; + + /// + /// The most recently launched warmup/replenishment loop task, exposed so tests can await a + /// warmup pass to a deterministic completion instead of polling pool counters. May be null + /// (warmup never requested) or reference an already-completed pass (requests are coalesced); + /// a caller that only needs "some warmup pass has finished" can await it regardless. Only + /// consumed by unit tests, which read it after synchronously triggering warmup on their own + /// thread, so a plain auto-property is sufficient. + /// + internal Task? WarmupLoopTask { get; private set; } #endregion #region Methods @@ -296,12 +331,12 @@ public void ReturnInternalConnection(DbConnectionInternal connection, DbConnecti // measures time-in-pool, not time-since-last-return. Without this, a connection whose // checkout exceeded IdleTimeout (e.g. a long-running query) would be wrongly evicted on // return even though it was actively in use on the wire. The same gating conditions are - // applied here as in IsLiveConnection so we avoid the per-return DateTime.UtcNow when + // applied here as in IsLiveConnection so we avoid the per-return timestamp read when // idle expiry is disabled or the legacy idle-timeout behavior is in effect. if (!LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior && PoolGroupOptions.IdleTimeout != TimeSpan.Zero) { - connection.SetReturnedTime(); + connection.SetReturnedTime(_timeProvider.GetUtcNow().UtcDateTime); } if (!IsLiveConnection(connection)) @@ -349,6 +384,26 @@ public void Shutdown() // routes returning connections to RemoveConnection. State = ShuttingDown; + // Cancel any in-flight background warmup/replenishment so no new connections are + // created after shutdown begins (Story 4). The warmup loop also observes the + // State transition above; cancelling here stops the loop promptly by tripping its + // await points and pre-create checks. It does not abort an already in-progress + // synchronous physical open (see _warmupCts field docs). Cancel is idempotent. + try + { + _warmupCts.Cancel(); + } + catch (ObjectDisposedException) + { + // Expected no-op: the cancellation source is already disposed, so there is nothing + // left to cancel. This is not a failure, so it is not traced. + } + catch (Exception ex) + { + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, _warmupCts.Cancel threw, continuing shutdown: {1}", Id, ex); + } + // Each cleanup step is independent and best-effort. A failure in one step must not // prevent later steps from running, otherwise the pool can be left half-shut-down // (e.g. timer disposed but channel never completed -> waiters stuck forever). @@ -422,6 +477,23 @@ public void Shutdown() " {0}, RemoveConnection threw during drain, continuing: {1}", Id, ex); } } + + // Release the warmup cancellation source now that the loop has been signalled to stop + // and no further connections will be created. Dispose is best-effort and idempotent. + try + { + _warmupCts.Dispose(); + } + catch (ObjectDisposedException) + { + // Expected no-op: the cancellation source is already disposed. Dispose is documented + // as safe to call repeatedly, so this is not a failure and is not traced. + } + catch (Exception ex) + { + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, _warmupCts.Dispose threw, continuing shutdown: {1}", Id, ex); + } } /// @@ -432,14 +504,16 @@ public void Shutdown() /// public void Startup() { - // Startup is currently a no-op for this pool: State is set to Running in the - // constructor, and PoolPruner (when present, i.e. MinPoolSize < MaxPoolSize) is - // also constructed eagerly there; its timer arms/disarms via UpdateTimer() calls - // from OpenNewInternalConnection and RemoveConnection as the pool grows/shrinks. - // This method exists as the symmetrical counterpart of Shutdown and as a hook - // for future warmup behavior. + // State is set to Running in the constructor, and PoolPruner (when present, i.e. + // MinPoolSize < MaxPoolSize) is also constructed eagerly there; its timer arms/disarms + // via UpdateTimer() calls from OpenNewInternalConnection and RemoveConnection as the + // pool grows/shrinks. SqlClientEventSource.Log.TryPoolerTraceEvent( " {0}", Id); + + // Kick off background warmup so the pool pre-creates connections up to MinPoolSize + // without blocking the caller (Story 1). No-op when MinPoolSize == 0. + RequestWarmup(); } /// @@ -571,7 +645,11 @@ public bool TryGetConnection( { cancellationToken.ThrowIfCancellationRequested(); - // Fast-fail in the error state. FR-006. + // Fast-fail if the pool is in the blocking-period error state. FR-006. Warmup goes + // through this same path (it has no isWarmup exemption): it mirrors the legacy WaitHandle + // pool, whose replenishment enters/clears the same error state as user requests. In + // practice the warmup loop already stands down before reaching here (its loop condition + // checks ErrorOccurred); this covers the narrow race where the state flips in between. _errorState?.ThrowIfActive(); try @@ -688,8 +766,9 @@ _connectionCreationRateLimiter is not null && // start the pruning timer so idle connections can be reclaimed. Pruner?.UpdateTimer(); - // A successful creation clears error/backoff state - // FR-009. + // A successful creation clears error/backoff state (FR-009). Warmup goes through + // this same path and clears the state on success too, mirroring the legacy + // WaitHandle pool: a connection that opens proves the server is reachable. _errorState?.Clear(); } @@ -697,13 +776,27 @@ _connectionCreationRateLimiter is not null && } catch (Exception ex) when (ADP.IsCatchableExceptionType(ex) && ex is not OperationCanceledException) { - // Enter the blocking period error state on creation failure if configured. + // Enter the blocking period error state on creation failure if configured. Warmup + // goes through this same path (the warmup loop absorbs the rethrow in its own catch), + // mirroring the legacy WaitHandle pool, whose replenishment failures also enter the + // error state. + // // We deliberately exclude OperationCanceledException: that is thrown when the // caller's own timeout/cancellation budget expires while waiting, which is // client-side contention rather than a physical connection creation failure and // must not poison the pool into fast-fail/backoff for other callers. // FR-006, FR-007. - _errorState?.Enter(ex); + // + // Only enter the error state while the pool is still Running. A synchronous physical + // open cannot be cancelled once it is in progress (see _warmupCts field docs), so a + // warmup/user open that began before Shutdown can complete with a failure after + // Shutdown has already disposed _errorState. Entering here would re-arm the exit + // timer and keep callbacks/logging alive past teardown, so we skip it once shutdown + // has begun; the throw below still propagates the failure to the caller/warmup loop. + if (State == Running) + { + _errorState?.Enter(ex); + } throw; } @@ -721,13 +814,15 @@ private bool IsLiveConnection(DbConnectionInternal connection) // connection is discarded without an SNI round-trip. // ReturnedTime is initialized to CreateTime so a freshly minted connection never trips this // check on first retrieval, and is then stamped by ReturnInternalConnection on every return. + // Uses the pool's TimeProvider (TimeProvider.System in production) so the return stamp and + // this expiry check read the same clock, letting tests drive idle expiry deterministically. // Use subtraction rather than addition so the comparison cannot throw if ReturnedTime is // ever close to DateTime.MaxValue. A clock skew that leaves ReturnedTime in the future // produces a negative TimeSpan, which falls through as not-expired (fail safe). TimeSpan idleTimeout = PoolGroupOptions.IdleTimeout; if (!LocalAppContextSwitches.UseLegacyIdleTimeoutBehavior && idleTimeout != TimeSpan.Zero && - DateTime.UtcNow - connection.ReturnedTime > idleTimeout) + _timeProvider.GetUtcNow().UtcDateTime - connection.ReturnedTime > idleTimeout) { return false; } @@ -770,6 +865,14 @@ private void RemoveConnection(DbConnectionInternal connection) // If this removal brought us back to MinPoolSize, disable the pruning timer. Pruner?.UpdateTimer(); + + // Any removal that drops the pool below MinPoolSize triggers replenishment through the + // shared serial, rate-limited warmup path (Story 5). This is the single choke point for + // every below-minimum event: connections destroyed on return (broken/lifetime), idle + // timeout eviction, and pruning all funnel through RemoveConnection. RequestWarmup is a + // no-op when MinPoolSize == 0, the pool is not Running, or a warmup loop is already + // running/queued, so calling it unconditionally here is cheap. + RequestWarmup(); } /// @@ -962,6 +1065,199 @@ private void ValidateOwnershipAndSetPoolingState(DbConnectionInternal connection } #endregion + #region Warmup + /// + /// Requests background warmup/replenishment: the pool asynchronously pre-creates connections + /// up to , serially and through the shared rate limiter. Safe to + /// call from any thread and from hot paths; it is cheap and non-blocking. + /// + /// This is the single entry point for every warmup trigger: pool startup and any event that + /// drops the pool below (connection destruction on return, idle + /// timeout eviction, pruning). It is also invoked directly by tests to exercise the loop + /// deterministically. Concurrent requests are coalesced by the + /// guard so that only one warmup loop ever runs at a time: a + /// request that arrives while a loop is already running is simply dropped, because that loop + /// re-reads on every iteration and will drive the pool to + /// regardless. + /// + internal void RequestWarmup() + { + // No-op when there is nothing to pre-create (MinPoolSize == 0), the pool is not running, + // or shutdown has cancelled background activity. + if (MinPoolSize == 0 || State != Running || _warmupCts.IsCancellationRequested) + { + return; + } + + // Fast path: the pool is already at or above the minimum, so there is nothing to warm + // up. Return before scheduling a thread-pool work item. This keeps hot-path callers + // (e.g. RemoveConnection on every return) cheap. The check is best-effort under + // concurrency; a below-minimum condition missed here is still observed by the running + // loop, which re-reads Count on every iteration. + if (Count >= MinPoolSize) + { + return; + } + + // Coalesce: only start a loop if one is not already running. + if (Interlocked.CompareExchange(ref _warmupLoopRunning, 1, 0) != 0) + { + return; + } + + try + { + // Fire-and-forget on the thread pool so warmup never blocks the caller. The loop + // absorbs its own exceptions and always releases the single-loop guard on exit. The + // task is published so tests can await a warmup pass to a deterministic completion. + WarmupLoopTask = Task.Run(RunWarmupLoopAsync); + } + catch (Exception ex) + { + // Scheduling the loop failed (e.g. the thread pool refused the work item). Release the + // guard so warmup isn't permanently pinned off for the life of the pool; the next + // below-minimum trigger will try again. Release the guard for every exception, but + // only absorb catchable ones - a non-catchable exception (e.g. OutOfMemoryException) + // must not be swallowed into a pool that keeps running in a potentially corrupted state. + Interlocked.Exchange(ref _warmupLoopRunning, 0); + if (!ADP.IsCatchableExceptionType(ex)) + { + throw; + } + + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Failed to schedule warmup loop, absorbing: {1}", Id, ex); + } + } + + /// + /// The coalesced warmup loop: creates connections one at a time (serially) up to + /// , then releases the single-loop guard. Each creation goes through + /// the same slot-reservation and rate-limited path as user requests + /// (), and freshly created connections are published + /// to the idle channel for waiting or future user requests. All failures are absorbed so + /// warmup can never surface an unhandled exception (Story 3); a genuine open failure still + /// enters the pool's blocking-period error state via the shared creation path, mirroring the + /// legacy WaitHandle pool. + /// + private async Task RunWarmupLoopAsync() + { + try + { + CancellationToken token; + try + { + token = _warmupCts.Token; + } + catch (ObjectDisposedException) + { + // The pool shut down and disposed the cancellation source before this loop + // started. There is nothing to warm up; the finally releases the guard. + return; + } + + // Single pass: re-reads Count each iteration, so any below-minimum drop that happens + // while the loop is running is picked up here without a separate request flag. + // + // The loop keeps creating while the pool is running, shutdown has not cancelled + // background work, and the pool is not in the blocking-period error state. While user + // requests are failing and the pool is blocking (ErrorOccurred), warmup stands down + // rather than piling more doomed opens onto a struggling server - mirroring the legacy + // WaitHandle pool, which skips replenishment while blocking. (Warmup still + // participates in the error state on its own creations - entering it on failure and + // clearing it on success - via OpenNewInternalConnection.) + while (State == Running + && !token.IsCancellationRequested + && !ErrorOccurred + && Count < MinPoolSize) + { + // Fresh per-attempt timeout budget based on the pool's CreationTimeout, since + // warmup has no owning Open() call to inherit a budget from. Matches the + // replenishment behavior of the legacy WaitHandle pool. + TimeoutTimer timeout = TimeoutTimer.StartNew( + TimeSpan.FromMilliseconds(PoolGroupOptions.CreationTimeout)); + + DbConnectionInternal? connection; + try + { + // owningConnection is null: warmup connections are created unattached and + // enter the pool as idle. A null return means the shared rate limiter was + // saturated; a thrown exception means the physical open genuinely failed. + connection = OpenNewInternalConnection( + owningConnection: null, + cancellationToken: token, + timeout: timeout); + } + catch (OperationCanceledException) + { + // Shutdown/cancellation unwound the in-flight create. Stop warming up. + break; + } + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) + { + // A genuine connection-open failure (not rate limiting). It has already + // entered the pool's blocking-period error state (see OpenNewInternalConnection); + // trace and absorb the rethrow here, then stop this pass rather than retrying + // on a tight cadence. The pool stays operational: user requests fast-fail + // during the blocking window and resume creating on demand once it expires, + // and the next below-minimum trigger re-requests warmup (Story 3). + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Warmup connection creation failed, stopping pass: {1}", Id, ex); + break; + } + + if (connection is null) + { + // A slot is guaranteed available here (Count < MinPoolSize <= MaxPoolSize), + // and creation failures throw rather than return null, so a null return means + // the shared rate limiter is currently saturated. Rather than bypassing the + // limiter or spinning on it (Story 2), end this warmup pass. Saturation only + // happens while user requests are actively creating connections - those very + // creations fill the pool toward MinPoolSize, and any later drop below the + // minimum re-triggers warmup through RemoveConnection - so warmup does not need + // to compete for a permit here. + break; + } + + // Publish the freshly created connection as idle. It was PrePush'd at creation + // (CreatePooledConnection) and never activated, so it is in the correct state + // to enter the idle channel directly. If a Clear raced and bumped the + // generation, the stale connection is harmlessly removed by IsLiveConnection + // on its next retrieval, so we don't check the generation here. + if (!_idleChannel.TryWrite(connection)) + { + // Channel completed (pool shutting down). Destroy instead of pooling. + RemoveConnection(connection); + break; + } + + // OpenNewInternalConnection is synchronous and blocks the loop's thread for + // the duration of the physical open. Yield between creations so a multi- + // connection warmup returns its thread-pool worker to the scheduler between + // opens (rather than monopolizing one worker for the whole sequence) and stays + // responsive to cancellation. There is no sync-over-async anywhere in this path. + await Task.Yield(); + } + } + catch (Exception ex) when (ADP.IsCatchableExceptionType(ex)) + { + // Defense in depth: the loop must never throw a catchable exception onto the thread + // pool. A non-catchable exception (e.g. OutOfMemoryException) is left to propagate + // rather than absorbed into a pool that keeps running in a potentially corrupted + // state; the finally below still releases the single-loop guard on that path. + SqlClientEventSource.Log.TryPoolerTraceEvent( + " {0}, Warmup loop failed, absorbing: {1}", Id, ex); + } + finally + { + // Always release the single-loop guard, whatever exit path we took, so a future + // below-minimum trigger can start a new loop. Interlocked.Exchange mirrors the + // Interlocked.CompareExchange acquire in RequestWarmup. + Interlocked.Exchange(ref _warmupLoopRunning, 0); + } + } + #endregion + #region Pruning /// /// Manages idle connection pruning. Null when the pool is fixed-size (MinPoolSize >= MaxPoolSize) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 3aa89f5332..8840864958 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -650,8 +650,14 @@ protected virtual DbConnectionInternal CreateConnection( // 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; + // Set future transient fault handling based on connection options. + // sqlOwningConnection is null for background warmup/replenishment creations, + // which have no owning connection to carry this state forward; only propagate + // it when an owning connection is present (matches the null-safe reads above). + if (sqlOwningConnection != null) + { + sqlOwningConnection._applyTransientFaultHandling = opt != null && opt.ConnectRetryCount > 0; + } if (!instanceName.StartsWith(@"\\.\", StringComparison.Ordinal)) { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index ec596ae16d..9d24200b17 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -1859,15 +1859,17 @@ public void RateLimiter_SuccessfulCreate_ReleasesLeaseForNextCreate() } /// - /// Verifies the FR-004 wake path: a caller blocked purely because the rate limiter denied - /// its permit is woken when a different caller releases its lease, and then creates its own - /// physical connection (rather than reusing one, since the permit holder never returns its - /// connection). Exercises both the sync and async idle-channel wait mechanisms. + /// Verifies the FR-004 behavior that releasing a rate-limiter lease lets a caller which + /// could not obtain a permit go on to create its own physical connection. Caller A holds the + /// single permit while it creates, so caller B cannot create until A releases its lease; + /// because A never returns its connection, B cannot reuse one and must open its own. When B + /// has already parked on the idle channel this covers the lease-release wake poke, but the + /// assertions hold in every interleaving. Runs both the sync and async open paths. /// [Theory] [InlineData(false)] [InlineData(true)] - public async Task RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysicalConnection(bool async) + public async Task RateLimiter_LeaseReleaseAllowsRateLimitedWaiterToCreatePhysicalConnection(bool async) { // Both open paths below dispatch physical creation onto the thread pool (via Task.Run), // and caller A blocks a worker thread inside gated creation while holding the only @@ -1891,8 +1893,14 @@ public async Task RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysical var poolGroupOptions = new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, - // Room to grow so the release actually pokes a waiter (ReservationCount < MaxPoolSize). - maxPoolSize: 2, + // Headroom so the lease-release poke fires deterministically. That poke is gated on + // ReservationCount < MaxPoolSize, and a denied waiter (B) still holds a transient + // slot reservation while it unwinds (ConnectionPoolSlots.Add only releases it when + // the createCallback returns null). When A releases, the count can momentarily be + // A's own in-flight reservation plus B's (2), so MaxPoolSize must exceed 2 for the + // poke to fire regardless of how far B has unwound. The idle channel is unbounded, so + // once fired the poke is buffered and never lost even if B parks just after it. + maxPoolSize: 3, creationTimeout: 15, loadBalanceTimeout: 0, hasTransactionAffinity: true, @@ -1932,20 +1940,15 @@ public async Task RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysical factory.FirstCreateStarted.Wait(TimeSpan.FromSeconds(5)), "Timed out waiting for the first open to begin physical creation."); - // Caller B is denied a permit (A holds it) and must fall back to the idle-channel wait. - // A denied AttemptAcquire increments the limiter's TotalFailedLeases, so watching that - // counter rise is how we confirm B was refused a permit and is now parked on the idle - // channel waiting for a wakeup (rather than still racing to acquire). - long failedLeasesBefore = rateLimiter.GetStatistics()!.TotalFailedLeases; + // Caller B is denied a permit (A holds it) and falls back to the idle-channel wait. Task requestB = Open(new SqlConnection()); - Assert.True( - SpinWait.SpinUntil( - () => rateLimiter.GetStatistics()!.TotalFailedLeases > failedLeasesBefore, - TimeSpan.FromSeconds(5)), - "Timed out waiting for the second request to be denied by the rate limiter."); - // Releasing A's create lets it finish and dispose its lease, which pokes the idle - // channel to wake B. B then finds the permit available and creates its own connection. + // Release A's create. A finishes, disposes its lease (returning the permit) and then + // pokes the idle channel. Because the permit is released before the poke and the channel + // is unbounded, B is guaranteed to observe the wake - whether it already parked or reads + // the buffered poke afterward - and then acquires the now-free permit. B has no idle + // connection to reuse (A never returns its connection), so this deterministically forces + // B to create its own physical connection with no reliance on spin/sleep timing. createGate.Set(); DbConnectionInternal? connectionA = await requestA; @@ -1962,8 +1965,10 @@ public async Task RateLimiter_LeaseReleaseWakesRateLimitedWaiter_CreatesPhysical } /// - /// Verifies that when the rate limiter denies a new physical open, the caller falls back - /// to waiting for an existing connection to be returned instead of forcing a second create. + /// Verifies that when the rate limiter denies a new physical open, the caller reuses a + /// connection returned to the pool instead of forcing a second create. The single permit is + /// held for the whole test, so the pool can never open a second connection; the waiting + /// request can only complete by reusing the returned connection. /// [Fact] public async Task RateLimiter_PermitDenied_ReusesReturnedConnection() @@ -1975,6 +1980,9 @@ public async Task RateLimiter_PermitDenied_ReusesReturnedConnection() var poolGroupOptions = new DbConnectionPoolGroupOptions( poolByIdentity: false, minPoolSize: 0, + // Room to grow: a free slot remains after the first connection is checked out, so + // the only thing that can stop the second request from opening a new physical + // connection is the rate limiter - not pool exhaustion. maxPoolSize: 2, creationTimeout: 15, loadBalanceTimeout: 0, @@ -1996,13 +2004,18 @@ public async Task RateLimiter_PermitDenied_ReusesReturnedConnection() Assert.NotNull(firstConnection); Assert.Equal(1, factory.CreateCount); - // Externally hold the only permit so the pool's next AttemptAcquire is denied and the - // waiting request must fall back to waiting for a returned connection. + // Hold the single permit for the remainder of the test. This is the invariant the + // assertions rely on: while the permit is held, every AttemptAcquire the pool makes is + // denied, so the second request can never create a physical connection. Its only path + // to a connection is to reuse one returned to the pool. That makes the outcome + // independent of thread scheduling - no polling for the denial, no spin, no sleep. using RateLimitLease heldLease = rateLimiter.AttemptAcquire(1); Assert.True(heldLease.IsAcquired); - long failedLeasesBefore = rateLimiter.GetStatistics()!.TotalFailedLeases; // Act + // The second request finds no idle connection and is denied a permit, so it parks on + // the idle channel waiting for a returned connection. It runs on a background thread + // because the sync pool path blocks the caller until a connection becomes available. Task waitingRequest = Task.Run(() => { pool.TryGetConnection( @@ -2013,16 +2026,15 @@ public async Task RateLimiter_PermitDenied_ReusesReturnedConnection() return queuedConnection; }); - Assert.True( - SpinWait.SpinUntil( - () => rateLimiter.GetStatistics()!.TotalFailedLeases > failedLeasesBefore, - TimeSpan.FromSeconds(5)), - "Timed out waiting for the second request to be denied by the rate limiter."); - + // Return the first connection. With the permit held, this is the only connection the + // waiting request can obtain, so it deterministically completes by reusing it - + // whether it grabs it from the idle channel directly or is woken from its parked wait. pool.ReturnInternalConnection(firstConnection!, firstOwner); DbConnectionInternal? reusedConnection = await waitingRequest; // Assert + // Same instance reused, and the factory was never asked to create a second connection: + // proof the held permit blocked a new open despite the free pool slot, forcing reuse. Assert.Same(firstConnection, reusedConnection); Assert.Equal(1, factory.CreateCount); } @@ -2067,12 +2079,11 @@ await Assert.ThrowsAsync(async () => }); } - // Every failed open must have released its permit; otherwise the pool would starve. - Assert.True( - SpinWait.SpinUntil( - () => rateLimiter.GetStatistics()!.CurrentAvailablePermits == 4, - TimeSpan.FromSeconds(5)), - "Rate limiter did not release all permits after failed opens."); + // Every failed open disposes its rate-limiter lease in the createCallback's finally as + // the failure unwinds - which happens before that failure propagates to complete the + // awaited request. So once all opens above have thrown, all four permits are already + // back in the limiter; there is nothing to wait for and no need to poll. + Assert.Equal(4, rateLimiter.GetStatistics()!.CurrentAvailablePermits); } /// diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolWarmupTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolWarmupTest.cs new file mode 100644 index 0000000000..d4c29d0459 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolWarmupTest.cs @@ -0,0 +1,728 @@ +// 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; +using System.Threading.RateLimiting; +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.Data.SqlClient.Tests.Common; +using Microsoft.Extensions.Time.Testing; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ConnectionPool +{ + /// + /// Unit tests for background pool warmup and replenishment in + /// . See specs/003-pool-warmup/spec.md. + /// + public class ChannelDbConnectionPoolWarmupTest + { + #region Helpers + + private static ChannelDbConnectionPool ConstructPool( + SqlConnectionFactory connectionFactory, + int minPoolSize = 0, + int maxPoolSize = 50, + int idleTimeout = 0, + int loadBalanceTimeout = 0, + ConcurrencyLimiter? connectionCreationRateLimiter = null, + TimeProvider? timeProvider = null) + { + var poolGroupOptions = new DbConnectionPoolGroupOptions( + poolByIdentity: false, + minPoolSize: minPoolSize, + maxPoolSize: maxPoolSize, + creationTimeout: 15, + loadBalanceTimeout: loadBalanceTimeout, + hasTransactionAffinity: true, + idleTimeout: idleTimeout); + + var dbConnectionPoolGroup = new DbConnectionPoolGroup( + new SqlConnectionOptions("Data Source=localhost;"), + new ConnectionPoolKey("TestDataSource", credential: null, accessToken: null, accessTokenCallback: null, sspiContextProvider: null), + poolGroupOptions); + + return new ChannelDbConnectionPool( + connectionFactory, + dbConnectionPoolGroup, + DbConnectionPoolIdentity.NoIdentity, + new DbConnectionPoolProviderInfo(), + connectionCreationRateLimiter, + timeProvider); + } + + /// + /// Generous upper bound for waits that guard a background handshake which, in correct code, + /// completes in milliseconds. It is only ever hit on a genuine failure/hang, so it does not + /// make passing runs slow or introduce timing races. + /// + private static readonly TimeSpan HandshakeTimeout = TimeSpan.FromSeconds(30); + + /// + /// Awaits background warmup passes until the pool settles at . + /// A single coalesced pass can race with concurrent removals - for example a + /// draining the pool while replenishment refills + /// it - so this re-requests warmup until the pool is full. Only safe with a factory whose + /// warmup creations succeed and where the pool is not in the error state, so every pass makes + /// forward progress and the loop terminates. Bounded by so a + /// regression that prevents the pool from ever reaching the minimum surfaces as a + /// deterministic timeout instead of hanging the whole test run. + /// + private static async Task AwaitWarmupToMinimum(ChannelDbConnectionPool pool, int minPoolSize) + { + DateTime deadline = DateTime.UtcNow + HandshakeTimeout; + while (pool.Count < minPoolSize) + { + Assert.True( + DateTime.UtcNow < deadline, + $"Pool did not reach MinPoolSize {minPoolSize} within {HandshakeTimeout}; Count={pool.Count}."); + + pool.RequestWarmup(); + Task? warmup = pool.WarmupLoopTask; + if (warmup is not null) + { + await warmup; + } + else + { + // No loop is published yet (e.g. one is mid-teardown between passes). Wait briefly + // instead of re-requesting in a tight loop, so this bounded wait never busy-spins a + // CPU core and starves other test work on a loaded CI machine. + await Task.Delay(TimeSpan.FromMilliseconds(10)); + } + } + } + + #endregion + + #region Story 1 - Background warmup on pool creation + + /// + /// Warmup is a no-op when MinPoolSize == 0: no connections are ever created in the background. + /// + [Fact] + public void Startup_MinPoolSizeZero_DoesNotWarmUp() + { + var factory = new ChannelDbConnectionPoolTest.CountingSuccessfulConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: 0, maxPoolSize: 10); + + pool.Startup(); + + // MinPoolSize == 0, so warmup is a no-op: no loop is ever launched (WarmupLoopTask stays + // null) and nothing is created in the background. + Assert.Null(pool.WarmupLoopTask); + Assert.Equal(0, factory.CreateCount); + Assert.Equal(0, pool.Count); + } + + /// + /// Warmup pre-creates connections up to MinPoolSize for various sizes (1 and N). + /// + [Theory] + [InlineData(1)] + [InlineData(3)] + [InlineData(10)] + public async Task Startup_WithMinPoolSize_WarmsUpToMinimum(int minPoolSize) + { + var factory = new ChannelDbConnectionPoolTest.CountingSuccessfulConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: minPoolSize, maxPoolSize: minPoolSize + 10); + + pool.Startup(); + + // Awaiting the warmup loop to completion is deterministic: the single serial pass runs + // with no competing removals, so when it finishes the pool is exactly at MinPoolSize. + await pool.WarmupLoopTask!; + + // Warmup reaches exactly MinPoolSize (no overshoot) and pools each created connection. + Assert.Equal(minPoolSize, pool.Count); + Assert.Equal(minPoolSize, pool.IdleCount); + Assert.Equal(minPoolSize, factory.CreateCount); + } + + /// + /// A user request during warmup is served immediately and does not wait for warmup to finish. + /// The gated factory blocks warmup's first (serial) creation, yet a user open still completes + /// by creating its own connection, proving warmup does not block user requests and does not + /// create connections in parallel. + /// + [Fact] + public async Task Startup_UserRequestDuringWarmup_ServedImmediately() + { + using var createGate = new ManualResetEventSlim(initialState: false); + var factory = new ChannelDbConnectionPoolTest.GatedSuccessfulConnectionFactory(createGate); + using var pool = ConstructPool(factory, minPoolSize: 3, maxPoolSize: 10); + + try + { + // Warmup begins and its single, serial creation blocks on the gate. Waiting on this + // explicit signal (not a sleep) establishes a deterministic happens-before: warmup is now + // parked inside its first create. + pool.Startup(); + Assert.True( + factory.FirstCreateStarted.Wait(HandshakeTimeout), + "Timed out waiting for warmup to begin its first creation."); + + // Exactly one creation - warmup's - is in flight and parked on the closed gate. The + // warmup loop is serial, so it cannot advance to a second create until the gate opens. + Assert.Equal(1, factory.CreateCount); + + // Issue a user request while warmup is parked. Run it on a dedicated (LongRunning) thread + // so it never competes with warmup for the same pool worker. Because warmup does not block + // user requests, it must complete promptly by creating its own connection. A generous + // upper bound converts a hypothetical "user blocked behind warmup" regression into a clean + // failure instead of an indefinite hang; correct code completes in milliseconds. + Task userOpen = Task.Factory.StartNew( + () => + { + bool completed = pool.TryGetConnection( + new SqlConnection(), + taskCompletionSource: null, + TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), + out DbConnectionInternal? connection); + Assert.True(completed); + Assert.NotNull(connection); + return connection!; + }, + CancellationToken.None, + TaskCreationOptions.LongRunning, + TaskScheduler.Default); + + Task completed = await Task.WhenAny(userOpen, Task.Delay(HandshakeTimeout)); + Assert.True(completed == userOpen, "User request was blocked behind warmup."); + Assert.NotNull(await userOpen); + + // Seriality: warmup is still parked on its single in-flight create (the gate is closed), + // so the only additional creation is the user's. CreateCount == 2 proves warmup did not + // create connections in parallel. This is deterministic: nothing can advance warmup's + // loop until the gate is set below. + Assert.Equal(2, factory.CreateCount); + } + finally + { + // Guarantee the gate is released even if an assertion above throws, so warmup's parked + // creation in the gated factory never strands a thread-pool thread for the rest of the run. + createGate.Set(); + } + } + + #endregion + + #region Story 2 - Warmup through the shared rate limiter + + /// + /// Warmup creates connections through the shared rate limiter and still reaches MinPoolSize. + /// + [Fact] + public async Task Warmup_ThroughSharedRateLimiter_ReachesMinimum() + { + using var rateLimiter = new ConcurrencyLimiter( + new ConcurrencyLimiterOptions { PermitLimit = 1, QueueLimit = 0 }); + var factory = new ChannelDbConnectionPoolTest.CountingSuccessfulConnectionFactory(); + using var pool = ConstructPool( + factory, + minPoolSize: 3, + maxPoolSize: 10, + connectionCreationRateLimiter: rateLimiter); + + pool.Startup(); + + // Warmup is the only actor here, so each serial create acquires and releases the single + // permit in turn; the pass never sees a saturated limiter and reaches MinPoolSize. + await pool.WarmupLoopTask!; + + Assert.Equal(3, pool.Count); + // Every warmup creation acquired (and released) a permit from the shared limiter. + Assert.True(rateLimiter.GetStatistics()!.TotalSuccessfulLeases >= 3); + } + + /// + /// When the shared rate limiter is saturated by a user request, warmup waits its turn rather + /// than bypassing the limiter: the user request that arrives while warmup holds the only + /// permit is denied by the same limiter. + /// + [Fact] + public async Task Warmup_RateLimiterSaturated_UserSharesSameLimiter() + { + using var createGate = new ManualResetEventSlim(initialState: false); + var factory = new ChannelDbConnectionPoolTest.GatedSuccessfulConnectionFactory(createGate); + using var rateLimiter = new ConcurrencyLimiter( + new ConcurrencyLimiterOptions { PermitLimit = 1, QueueLimit = 0 }); + using var pool = ConstructPool( + factory, + minPoolSize: 2, + maxPoolSize: 5, + connectionCreationRateLimiter: rateLimiter); + + try + { + // Warmup begins, acquires the only permit, and blocks in creation while holding it. + pool.Startup(); + Assert.True( + factory.FirstCreateStarted.Wait(HandshakeTimeout), + "Timed out waiting for warmup to begin its first creation."); + + // A concurrent user request competes for the same limiter. There is no idle connection + // and warmup holds the only permit, so the request cannot be satisfied inline: it is + // deferred to the TaskCompletionSource, proving warmup did not bypass the limiter. + var tcs = new TaskCompletionSource(); + bool completedSynchronously = pool.TryGetConnection( + new SqlConnection(), tcs, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? immediateConnection); + Assert.False(completedSynchronously, "Async request unexpectedly completed synchronously."); + Assert.Null(immediateConnection); + + // Deterministically prove the shared limiter is saturated by warmup: while warmup holds + // its only permit (guaranteed by FirstCreateStarted above and the still-closed gate), + // attempting to acquire that same limiter from the test thread is denied. This is the + // shared-limiter proof without any spin - the denial is observed synchronously here + // rather than by polling for the deferred user request's transient failed-lease, which + // races the permit being released below. The denied attempt is a no-op lease. + long failedBefore = rateLimiter.GetStatistics()!.TotalFailedLeases; + using (RateLimitLease probe = rateLimiter.AttemptAcquire(1)) + { + Assert.False( + probe.IsAcquired, + "Shared rate limiter was not saturated while warmup held its permit."); + } + Assert.True( + rateLimiter.GetStatistics()!.TotalFailedLeases > failedBefore, + "Denied acquire was not recorded by the shared rate limiter."); + + // Release warmup. With rate-limit retry removed, warmup's pass may end early if the + // user request grabs the freed permit first - but the pool must still converge to + // MinPoolSize: whichever creations win the permit (warmup's or the user's) fill it. + createGate.Set(); + + // Await the deferred user request directly rather than spin-polling limiter statistics. + // Bound the wait so a regression that never completes the async request fails fast + // instead of hanging the whole test run. + Task userRequest = tcs.Task; + Assert.True( + userRequest == await Task.WhenAny(userRequest, Task.Delay(HandshakeTimeout)), + "Timed out waiting for the deferred user request to complete."); + DbConnectionInternal? userConnection = await userRequest; + Assert.NotNull(userConnection); + + // Once both the warmup pass and the deferred user request have settled, the pool holds + // exactly MinPoolSize connections (a warmup idle connection plus the user's, or two + // warmup connections one of which the user took). + await AwaitWarmupToMinimum(pool, 2); + Assert.Equal(2, pool.Count); + } + finally + { + // Guarantee the gate is released even if an assertion above throws before the explicit + // release, so warmup's parked creation never strands a thread-pool thread. Setting an + // already-set gate is a harmless no-op on the success path. + createGate.Set(); + } + } + + #endregion + + #region Story 3 - Warmup failure resilience + + /// + /// Warmup creation failures are absorbed - no exception surfaces onto the thread pool and no + /// connections are pooled - but, mirroring the legacy WaitHandle pool, a genuine open failure + /// does enter the pool's blocking-period error state. Warmup then stops the pass rather than + /// spinning on the persistent failure. + /// + [Fact] + public async Task Warmup_AllCreationsFail_AbsorbedAndEntersErrorState() + { + var factory = new WarmupFailingConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: 3, maxPoolSize: 10); + + pool.Startup(); + + // Await the warmup pass. The first open fails: OpenNewInternalConnection enters the + // blocking-period error state and rethrows, the warmup loop absorbs the rethrow and ends + // the pass. Awaiting is deterministic - no exception surfaces onto the thread pool. + await pool.WarmupLoopTask!; + + // Exactly one attempt was made: a genuine failure ends the pass rather than spinning on + // the persistent failure, and nothing is pooled. + Assert.Equal(1, factory.WarmupAttemptCount); + Assert.Equal(0, pool.IdleCount); + + // Mirroring the legacy WaitHandle pool, the failed open entered the blocking-period error + // state, exactly as an on-demand creation failure would. + Assert.True( + pool.ErrorOccurred, + "Warmup failure did not enter the pool error state as the WaitHandle pool does."); + } + + /// + /// After warmup fails, the pool enters its blocking-period error state (mirroring the + /// WaitHandle pool), so a user request during the blocking window fast-fails with the cached + /// exception rather than attempting a fresh on-demand open. The pool remains operational and + /// resumes creating on demand once the blocking period expires. + /// + [Fact] + public async Task Warmup_Fails_UserRequestFastFailsDuringBlockingPeriod() + { + var factory = new WarmupFailingConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: 2, maxPoolSize: 10); + + pool.Startup(); + + // Warmup's failure drives the pool into the blocking-period error state. + await pool.WarmupLoopTask!; + Assert.True(pool.ErrorOccurred, "Warmup failure did not enter the pool error state."); + + // A user request while the pool is blocking fast-fails with the cached exception. + Assert.ThrowsAny(() => pool.TryGetConnection( + new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _)); + } + + /// + /// Warmup respects the pool's blocking-period error state: once a failing user request has + /// driven the pool into the error state, invoking warmup must make it stand down (its loop + /// condition sees ErrorOccurred) and create nothing, mirroring the legacy WaitHandle pool, + /// which skips replenishment while the pool is blocking. Driving the error state with a user + /// request (rather than warmup itself) keeps the two behaviors independent, and awaiting the + /// warmup task makes the stand-down deterministic. + /// + [Fact] + public async Task Warmup_RespectsErrorState_StandsDownWhileBlocking() + { + var factory = new UserFailingWarmupCountingConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: 3, maxPoolSize: 10); + + // Drive the pool into the blocking-period error state with a failing user request. We do + // NOT call Startup here: that would kick off a *successful* warmup and fill the pool, + // which is not the behavior under test. + Assert.ThrowsAny(() => pool.TryGetConnection( + new SqlConnection(), taskCompletionSource: null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out _)); + Assert.True(pool.ErrorOccurred, "Failing user request did not enter the pool error state."); + + // Now request warmup directly. Because the pool is blocking, the loop's error-state guard + // makes it stand down immediately: it creates nothing rather than piling doomed opens + // onto a struggling server. + pool.RequestWarmup(); + await pool.WarmupLoopTask!; + + Assert.Equal(0, factory.WarmupCreateCount); + Assert.Equal(0, pool.IdleCount); + Assert.True(pool.ErrorOccurred, "Warmup must not clear the error state it stood down for."); + } + + #endregion + + #region Story 4 - Warmup cancellation on shutdown + + /// + /// When the pool is shut down mid-warmup, warmup stops promptly: no connections created after + /// shutdown begins, and any in-flight connection is cleaned up. + /// + [Fact] + public async Task Shutdown_DuringWarmup_StopsAndCleansUp() + { + using var createGate = new ManualResetEventSlim(initialState: false); + var factory = new ChannelDbConnectionPoolTest.GatedSuccessfulConnectionFactory(createGate); + using var pool = ConstructPool(factory, minPoolSize: 3, maxPoolSize: 10); + + try + { + // Warmup begins and blocks on its first serial creation. + pool.Startup(); + Assert.True( + factory.FirstCreateStarted.Wait(HandshakeTimeout), + "Timed out waiting for warmup to begin its first creation."); + + // Shut down while warmup is parked. This cancels warmup and completes the idle channel. + pool.Shutdown(); + + // Release the in-flight creation and await the loop to completion. The freshly created + // connection cannot be published to the completed idle channel, so it is destroyed, and + // the loop stops without creating anything further. + createGate.Set(); + await pool.WarmupLoopTask!; + + Assert.Equal(0, pool.Count); + Assert.Equal(1, factory.CreateCount); + } + finally + { + // Guarantee the gate is released even if an assertion above throws before the explicit + // release, so warmup's parked creation never strands a thread-pool thread. Setting an + // already-set gate is a harmless no-op on the success path. + createGate.Set(); + } + } + + #endregion + + #region Story 5 - Replenishment on any below-minimum event + + /// + /// Destroying a connection on return (non-poolable/broken) drops the pool below MinPoolSize and + /// triggers replenishment back to the minimum. + /// + [Fact] + public async Task Replenish_AfterDoomedReturn_RefillsToMinimum() + { + var factory = new DoomableConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: 2, maxPoolSize: 10); + + pool.Startup(); + await pool.WarmupLoopTask!; + Assert.Equal(2, pool.Count); + + // Check out a connection, mark it non-poolable, and return it: it is destroyed on return, + // dropping the pool to 1 and triggering replenishment through RemoveConnection. + var owner = new SqlConnection(); + pool.TryGetConnection(owner, null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? connection); + Assert.NotNull(connection); + ((DoomableStubConnection)connection!).MarkDoNotPool(); + pool.ReturnInternalConnection(connection, owner); + + // The doomed return re-triggered warmup synchronously; awaiting the replenishment pass + // (a single serial create with no competing removal) restores the pool to MinPoolSize. + await pool.WarmupLoopTask!; + Assert.Equal(2, pool.Count); + } + + /// + /// Clearing the pool drops it to zero (below MinPoolSize) and triggers replenishment back to + /// the minimum with fresh-generation connections. + /// + [Fact] + public async Task Replenish_AfterClear_RefillsToMinimum() + { + var factory = new ChannelDbConnectionPoolTest.CountingSuccessfulConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: 3, maxPoolSize: 10); + + pool.Startup(); + await pool.WarmupLoopTask!; + Assert.Equal(3, pool.Count); + + pool.Clear(); + + // Clear drains the pool concurrently with replenishment, so a single warmup pass can race + // with the drain; await passes until the pool settles at MinPoolSize. + await AwaitWarmupToMinimum(pool, 3); + Assert.Equal(3, pool.Count); + } + + /// + /// Destroying a connection while still at/above the minimum does NOT trigger replenishment + /// beyond the minimum: the pool stays at MinPoolSize. + /// + [Fact] + public async Task Replenish_DestroyWhileAtMinimum_NoOvershoot() + { + var factory = new DoomableConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: 2, maxPoolSize: 10); + + pool.Startup(); + await pool.WarmupLoopTask!; + Assert.Equal(2, pool.Count); + + // Grow above the minimum. Two checkouts reuse the two idle warmup connections; a third + // finds no idle connection and creates a new one synchronously, bringing the total to 3. + var owner1 = new SqlConnection(); + var owner2 = new SqlConnection(); + var owner3 = new SqlConnection(); + pool.TryGetConnection(owner1, null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? c1); + pool.TryGetConnection(owner2, null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? c2); + pool.TryGetConnection(owner3, null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? c3); + Assert.NotNull(c1); + Assert.NotNull(c2); + Assert.NotNull(c3); + Assert.Equal(3, pool.Count); + + // Destroy the third on return. Dropping from 3 to 2 stays at the minimum, so RequestWarmup + // hits its fast path and launches no new pass. + Task? warmupBeforeReturn = pool.WarmupLoopTask; + ((DoomableStubConnection)c3!).MarkDoNotPool(); + pool.ReturnInternalConnection(c3, owner3); + + // No replenishment was triggered: the exposed warmup task is unchanged (no new pass) and + // the pool settled at MinPoolSize without overshooting. + Assert.Same(warmupBeforeReturn, pool.WarmupLoopTask); + Assert.Equal(2, pool.Count); + } + + /// + /// An idle connection evicted by the idle-timeout check (IsLiveConnection) drops the pool + /// below MinPoolSize and triggers replenishment via RemoveConnection → RequestWarmup, + /// restoring the pool to the minimum. A drives the idle-timeout + /// clock so exactly one connection crosses the timeout, making the create-count assertion + /// deterministic regardless of host scheduling or GC pauses. + /// + [Fact] + public async Task Replenish_AfterIdleTimeoutEviction_RefillsToMinimum() + { + using LocalAppContextSwitchesHelper switchesHelper = new(); + switchesHelper.UseLegacyIdleTimeoutBehavior = false; + + // Pool with a 1-second idle timeout and MinPoolSize=2 so an evicted connection + // triggers replenishment. The fake clock lets the test decide exactly which connection + // ages past the idle timeout, rather than racing the wall clock. + var fakeTime = new FakeTimeProvider(); + var factory = new DoomableConnectionFactory(); + using var pool = ConstructPool(factory, minPoolSize: 2, maxPoolSize: 10, idleTimeout: 1, timeProvider: fakeTime); + + pool.Startup(); + await pool.WarmupLoopTask!; + Assert.Equal(2, pool.Count); + int createCountAfterWarmup = factory.CreateCount; + + // Check out both warm-up connections. + var owner1 = new SqlConnection(); + var owner2 = new SqlConnection(); + pool.TryGetConnection(owner1, null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? conn1); + pool.TryGetConnection(owner2, null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? conn2); + Assert.NotNull(conn1); + Assert.NotNull(conn2); + + // Return conn1, then advance the fake clock past the 1s idle timeout. conn1's return stamp + // is now 2s old while conn2, returned after the advance, is fresh (0s), so only conn1 expires. + pool.ReturnInternalConnection(conn1!, owner1); + fakeTime.Advance(TimeSpan.FromSeconds(2)); + pool.ReturnInternalConnection(conn2!, owner2); + + // TryGetConnection pops conn1 (head of channel), finds it idle-expired, calls + // RemoveConnection (which triggers RequestWarmup), then pops conn2 and returns it. + var owner3 = new SqlConnection(); + pool.TryGetConnection(owner3, null, TimeoutTimer.StartNew(TimeSpan.FromSeconds(15)), out DbConnectionInternal? conn3); + Assert.NotNull(conn3); + Assert.Same(conn2, conn3); + + // The eviction triggered replenishment; await the pass and verify the pool refilled to MinPoolSize. + await pool.WarmupLoopTask!; + Assert.Equal(2, pool.Count); + Assert.Equal(createCountAfterWarmup + 1, factory.CreateCount); + } + + #endregion + + #region Test doubles + + /// + /// A stub internal connection that can be marked non-poolable so a return destroys it. + /// + private sealed class DoomableStubConnection : DbConnectionInternal + { + internal void MarkDoNotPool() => DoNotPoolThisConnection(); + + public override string ServerVersion => throw new NotImplementedException(); + + public override ConnectionCapabilities Capabilities => throw new NotImplementedException(); + + public override DbTransaction BeginTransaction(System.Data.IsolationLevel il) + => throw new NotImplementedException(); + + public override void EnlistTransaction(Transaction transaction) + { + } + + protected override void Activate(Transaction transaction) + { + } + + protected override void Deactivate() + { + } + + internal override void ResetConnection() + { + } + } + + /// + /// Produces instances so replenishment tests can force a + /// connection to be destroyed on return. + /// + private sealed class DoomableConnectionFactory : SqlConnectionFactory + { + private int _createCount; + + internal int CreateCount => Volatile.Read(ref _createCount); + + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + Interlocked.Increment(ref _createCount); + return new DoomableStubConnection(); + } + } + + /// + /// Fails only warmup creations (identified by a null owning connection) and succeeds for real + /// user requests. Lets tests assert that warmup failures are absorbed while on-demand user + /// creation still works. + /// + private sealed class WarmupFailingConnectionFactory : SqlConnectionFactory + { + private int _warmupAttemptCount; + + internal int WarmupAttemptCount => Volatile.Read(ref _warmupAttemptCount); + + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + if (owningConnection is null) + { + Interlocked.Increment(ref _warmupAttemptCount); + throw ADP.PooledOpenTimeout(); + } + + return new DoomableStubConnection(); + } + } + + /// + /// Fails user requests (non-null owning connection) so the pool enters its blocking-period + /// error state, while succeeding for and counting warmup creations (null owning connection). + /// Lets a test drive the pool into the error state and then verify warmup stands down + /// (creates nothing) rather than continuing to replenish. + /// + private sealed class UserFailingWarmupCountingConnectionFactory : SqlConnectionFactory + { + private int _warmupCreateCount; + + internal int WarmupCreateCount => Volatile.Read(ref _warmupCreateCount); + + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + { + if (owningConnection is not null) + { + // User request: fail so the pool enters the blocking-period error state. + throw ADP.PooledOpenTimeout(); + } + + // Warmup creation: count it. In the stand-down test this must never be reached while + // the pool is blocking. + Interlocked.Increment(ref _warmupCreateCount); + return new DoomableStubConnection(); + } + } + + #endregion + } +} From a684c99b261d44649946f9b232b5e043d46c0e74 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:14:52 -0300 Subject: [PATCH 206/298] Reword SqlLegacyRepro placeholder connection string; add secrets instructions (#4475) --- .github/instructions/secrets.instructions.md | 112 ++++++++++++++++++ .../SniCloseLegacyRepro.csproj | 2 +- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 .github/instructions/secrets.instructions.md diff --git a/.github/instructions/secrets.instructions.md b/.github/instructions/secrets.instructions.md new file mode 100644 index 0000000000..77af612bee --- /dev/null +++ b/.github/instructions/secrets.instructions.md @@ -0,0 +1,112 @@ +--- +applyTo: "**" +--- +# Secrets and Credential Handling + +This guide describes how to avoid committing secrets, how to write credential +placeholders that do **not** trip secret scanners (e.g. GitHub Advanced Security +/ 1ES push protection), and what to do when a push is blocked. + +## Golden Rules + +1. **Never commit a real secret** — passwords, connection strings with live + credentials, access keys, SAS tokens, client secrets, certificates/PFX + files, or bearer tokens. This applies to source, tests, docs, samples, + scripts, pipeline YAML, and config files. +2. **Never route a secret through tooling or the model.** When a value is truly + secret, have the user type it directly into their terminal or set it as an + environment variable. Do not paste it into files, prompts, or chat. +3. **Prefer indirection over literals.** Read credentials from environment + variables, a secret store (Azure Key Vault), user secrets, or CI secret + variables — not from committed text. +4. **Assume public.** This repository mirrors to public GitHub via autosync. + Anything committed is effectively public and permanent in history. + +## Approved Placeholder Formats + +When you need to show the *shape* of a connection string or credential in code, +docs, comments, or samples, use one of these placeholder styles. These are +recognized as non-secrets by the scanner: + +| Style | Example | Use for | +|-------|---------|---------| +| Angle brackets | `User ID=;Password=` | Docs, READMEs, comments, samples | +| Descriptive angle brackets | `Password=`, `User Id=` | Samples that name the value | +| Masked | `Password=********` or `Password=***` | Illustrative output / redaction | +| Env var expansion | `Password=${SA_PASSWORD}` (bash), `Password=$(SqlPwd)` (ADO) | Scripts and pipelines | +| Named token (docs prose) | `Password=` | Narrative documentation | + +### Do NOT use these placeholder styles + +- **Ellipsis values** — a `Password` (or `Pwd`) key whose value is a literal + ellipsis, especially when paired with a `User ID` key in the same connection + string. The ellipsis is treated as a credential value and **will** trip + `SEC101/037 SqlLegacyCredentials`. Use `` instead. +- **Realistic-looking fake secrets** — a `Password` key set to a value that + looks like a real password (mixed letters, digits, and symbols). Even fake + values that resemble real passwords can be flagged and set a bad example. +- **Bare word secrets** — a `Password` key set to a plain dictionary word + inside a connection-string literal. Prefer ``. + +> **NOTE**: Ironically, this document cannot show verbatim examples of the +> disallowed styles above — spelling out a `Password` key followed by an +> ellipsis or realistic-looking value would itself trip `SEC101/037` and block +> commits to this very file. That is exactly why the "don't" cases are described +> in prose rather than shown literally. + +### Full connection-string placeholder examples + +```text +Server=;Database=;User ID=;Password=;TrustServerCertificate=true +Server=tcp:.database.windows.net;Database=;Authentication=Active Directory Service Principal;User Id=;Password= +``` + +```bash +# Have the user set the value directly; never write the real value into a file: +export SNICLOSE_CONNSTR="Server=;User ID=;Password=" +``` + +## Reading Secrets at Runtime (preferred patterns) + +- **Environment variables**: read connection strings from an env var + (e.g. `SNICLOSE_CONNSTR`) so the password never lands in a committed file. +- **SecureString / SqlCredential**: use `SqlCredential` and `SecureString` + rather than embedding a password in the connection string. +- **Integrated auth**: prefer `Integrated Security=true` or an + `Authentication=ActiveDirectory*` mode where no password is stored. +- **CI/CD**: reference pipeline secret variables (`$(mySecret)`), never inline + literals in YAML. + +## When a Push Is Blocked by Secret Scanning + +Error shape: `VS403654:BypassableBlock ... push was rejected because it contains +one or more secrets` with a `SEC101/...` rule id and `commit`/`paths` details. + +1. **Locate it.** Inspect the exact committed blob: + `git show :` and go to the reported line/columns. +2. **Determine real vs. false positive.** + - *Real secret*: rotate/revoke it immediately, then remove it from the file. + If it is in the tip commit only, amend; if it is deeper in **unshared** + history, rewrite that history. Never rewrite commits already public. + - *False positive* (an ellipsis or other non-credential placeholder value): + reword to an approved placeholder (`Password=`) so future commits + don't recur. +3. **Already-public / mirrored commits.** If the flagged content lives in a + commit that is already on public GitHub (e.g. an autosync mirror replaying + `github/main`), you cannot scrub that specific commit without rewriting + shared/public history. For a confirmed false positive, **bypass** the block + via the 1ES/Advanced Security push-protection flow + (https://aka.ms/1esSecretScanning/PushProtectionBypassableBlock) with a clear + reason, or dismiss the alert as *False positive*. Then land the placeholder + reword going forward so new files don't trip the rule again. +4. **Never** disable secret scanning or use `--no-verify`-style bypasses to work + around a *real* secret. + +## Common Scanner Rules to Watch + +| Rule | Triggers on | +|------|-------------| +| `SEC101/037 SqlLegacyCredentials` | Connection strings pairing a `User ID` key with a `Password`/`Pwd` value | +| `SEC101/*` (general) | Cloud keys, SAS tokens, client secrets, PATs, bearer tokens | + +If in doubt, use an approved placeholder from the table above. diff --git a/tools/SniCloseLegacyRepro/SniCloseLegacyRepro.csproj b/tools/SniCloseLegacyRepro/SniCloseLegacyRepro.csproj index 7c7f5507de..19351e09cc 100644 --- a/tools/SniCloseLegacyRepro/SniCloseLegacyRepro.csproj +++ b/tools/SniCloseLegacyRepro/SniCloseLegacyRepro.csproj @@ -15,7 +15,7 @@ Framework Windows containers to bind older in-box System.Data.dll versions. Run: dotnet test tools/SniCloseLegacyRepro (in-process tests only) - $env:SNICLOSE_CONNSTR="Server=...;User ID=...;Password=..." (enables live MARS + stress) + $env:SNICLOSE_CONNSTR="Server=;User ID=;Password=" (enables live MARS + stress) dotnet test tools/SniCloseLegacyRepro This project uses Central Package Management via the sibling From e0b5c4af07cd9dae205919d55a4af38b6334ba42 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 27 Jul 2026 07:32:02 -0300 Subject: [PATCH 207/298] Standardize APIScan registration and SDL break policy in OneBranch pipelines (#4462) * Standardize APIScan registration and SDL break policy in OneBranch pipelines - Configure APIScan registration (softwareName, softwareVersion, modeType, verbosityLevel) inline in both official and non-official globalSdl blocks instead of per-job ob_sdl_apiscan_* variables; keep only DLL/PDB folders per-job in build-buildproj-job. - Register the actual SqlClient APIScan software version (6.10). - Enable break: true for apiscan, armory, binskim, policheck, and roslyn in the official pipeline; make the non-official pipeline non-breaking and disable its TSA publishing. - Drop redundant ob_sdl_binskim_break in onebranch-variables and publish-symbols-job (BinSkim disabled there). * Address PR #4462 review feedback: APIScan comment fixes - Fix typo in the APIScan enable comment: ob_sdl_apiscan_enable -> ob_sdl_apiscan_enabled (the actual per-job variable name), in both the official and non-official pipelines. - Drop "and version" from the non-official softwareName comment so it matches the official wording (the version has its own comment below). --- .config/guardian/.gdnbaselines | 124 ++++++++++++++++ .../onebranch-pipeline-design.instructions.md | 2 +- .../onebranch/jobs/build-buildproj-job.yml | 5 +- .../onebranch/jobs/publish-symbols-job.yml | 1 - .../onebranch/sqlclient-non-official.yml | 107 ++++++++++++-- .../onebranch/sqlclient-official.yml | 132 ++++++++++++++---- .../variables/onebranch-variables.yml | 4 - 7 files changed, 328 insertions(+), 47 deletions(-) create mode 100644 .config/guardian/.gdnbaselines diff --git a/.config/guardian/.gdnbaselines b/.config/guardian/.gdnbaselines new file mode 100644 index 0000000000..10698219a1 --- /dev/null +++ b/.config/guardian/.gdnbaselines @@ -0,0 +1,124 @@ +{ + "hydrated": false, + "properties": { + "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines" + }, + "version": "1.0.0", + "baselines": { + "default": { + "name": "default", + "createdDate": "2026-07-23 11:29:23Z", + "lastUpdatedDate": "2026-07-23 11:29:23Z" + } + }, + "results": { + "40b23e076c5c65b58c7da0889f0bf58e271b1637f662060bbc7aadd5c62a7126": { + "signature": "40b23e076c5c65b58c7da0889f0bf58e271b1637f662060bbc7aadd5c62a7126", + "alternativeSignatures": [ + "4113c2e383bc4d7206cc94ba3e16dcdaef105762c7e22060d8e7964407e048ab", + "41bc93496e4ace0362f89323cc166b46e7448108052b75f7dd3ae5f4d48b5ad8", + "357246d2ddf96dbab309e392e49d088dc5c15ec9343553876173296f02eef94c" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + }, + "6e0dddb2d631181c20dbbf2892aceea3fbb157b3c293a147b70f49fc4a17765b": { + "signature": "6e0dddb2d631181c20dbbf2892aceea3fbb157b3c293a147b70f49fc4a17765b", + "alternativeSignatures": [ + "2a9d1465faa5347c1f77eb856eb21074dc1dfcca4963991fb918fa61c1c29b02", + "9f71983dfef73a0b5fa1ecd3d1f14b3af9067aed39c5eb8619ecd0a0f4ee33f6", + "bd1475a089ab85c698803c80d71e73148edfc9db706bafe3789509ea32f917f2" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + }, + "28f157377f5600a0ce77a1a2193653ca71c8de4cca5d24166e1363cfc1f53782": { + "signature": "28f157377f5600a0ce77a1a2193653ca71c8de4cca5d24166e1363cfc1f53782", + "alternativeSignatures": [ + "ff91ec920d3ed908c7fdc8419f4e767216c312a3907419b65e8e11e611be0db6", + "cc5e7e0d3670c5b2738f2b9194d50de2b123bdae5e906729f3a9c7baa980477d", + "8641e063e4aff04c260660c01c71ee18d32bdb7788357eaecc420a64cb279b2d" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + }, + "253df4b6cde68fb64c0376d3fa5351a49ccd9136e987129a6686f003303902bb": { + "signature": "253df4b6cde68fb64c0376d3fa5351a49ccd9136e987129a6686f003303902bb", + "alternativeSignatures": [ + "e98757960f13dfe3c2c4ef2acbbdbd782cf26a7635caae25b7fd9e9230f863f6", + "df606344d52fe66ab4fa386e5cc50b6ced2c8d711aa7bc5a14db61f1fad7b70f", + "61239d49bfbe4702066454a51c302633291f61542011ee755420f4f556ede2d6" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + }, + "40b7b29173191dd629e8921c99d8718cad7ebf4d6acd83d8719e2e15db3d89a3": { + "signature": "40b7b29173191dd629e8921c99d8718cad7ebf4d6acd83d8719e2e15db3d89a3", + "alternativeSignatures": [ + "870b4398ab01cdfaa4be8605e03eaa7377a5079b79573d3290248f5d5fd6fd6c", + "6b690695e0e41aa7a7c79394fa5d9f6a08e6e06cffc326b7fae4a8883ff42ac6", + "514ed34ad14af5b3739b2804e791b1b63d88be42e658238794f467c4bf2ff140" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + }, + "fb0afe300269068323c4e3fab292104aa30201c545156ffee1ccf6109df3bd59": { + "signature": "fb0afe300269068323c4e3fab292104aa30201c545156ffee1ccf6109df3bd59", + "alternativeSignatures": [ + "7bdac882cf7914eb42130101ca921f1e4c5654cc55916daf1c2b9bc51c439dfa", + "6e99b53e08dcbad4533fbd8a101b881fba112f31483c7e9b7b14aaa709f3cee8", + "fc771d2d8a5bd2dfa72695e42516ff6c504e18fd714a1462d911539ea69fe710" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + }, + "e3ec17e281386f24ee7650c502b034993dcfd9a5ad19089c9af3edde8321fbb9": { + "signature": "e3ec17e281386f24ee7650c502b034993dcfd9a5ad19089c9af3edde8321fbb9", + "alternativeSignatures": [ + "0910ddd1f1c9f37b764457a39d0dd543f25fce6e5e8694f066d0092e99784b4d", + "ee166a838d6e6b4aa08218eb89b001689bc3ec8fb6343fdfee89834847a294d5", + "7beed57053ec779f46498ff84c41a9400761001ea494237fe32cfab1c829454a" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + }, + "0ef75f3e883bfae828f2b15b4a41963e6ea3a61306fc19767586044309bc739f": { + "signature": "0ef75f3e883bfae828f2b15b4a41963e6ea3a61306fc19767586044309bc739f", + "alternativeSignatures": [ + "7e71c00bf08e29508821cc200dcc41556ecc570c205f8833a5a704466e80baa5", + "0d5b851e97bdb0eb8931e6f326718a657f9cd46092eb8a2a2d414be2147a797c", + "e6c0cd6ef2433a42c95a2939cce740019ecda3fcfde64a3a0f21661e6ff27f71" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + }, + "27cf35f7df3f630fab489573ec19318f563e042424ca30625e9fe08407d74bdf": { + "signature": "27cf35f7df3f630fab489573ec19318f563e042424ca30625e9fe08407d74bdf", + "alternativeSignatures": [ + "55e9029f79f883ee7e23a98ee6aad0c3713e02dd67cd7a7158875cdee4c86806", + "a443d3867a75710110ddf675eb639080559e694d4331fd89bc1589b1a60b8777", + "ff040e311fa90fc602c647b539f52148605321c178fc731f30395a8a9ef3f7a9" + ], + "memberOf": [ + "default" + ], + "createdDate": "2026-07-23 11:29:23Z" + } + } +} \ No newline at end of file diff --git a/.github/instructions/onebranch-pipeline-design.instructions.md b/.github/instructions/onebranch-pipeline-design.instructions.md index 2421674eef..35f522dadc 100644 --- a/.github/instructions/onebranch-pipeline-design.instructions.md +++ b/.github/instructions/onebranch-pipeline-design.instructions.md @@ -9,7 +9,7 @@ Rules and conventions for editing the OneBranch Azure DevOps YAML pipelines that ## Pipeline Variants -- `sqlclient-official.yml` — Official pipeline; uses `OneBranch.Official.CrossPlat.yml`; CI trigger on `internal/main` + daily schedule at 04:30 UTC +- `sqlclient-official.yml` — Official pipeline; uses `OneBranch.Official.CrossPlat.yml`; runs on a daily schedule at 04:30 UTC on `internal/main`, with no activity-based trigger (`pr: none`, `trigger: none`) - `sqlclient-non-official.yml` — Non-Official pipeline; uses `OneBranch.NonOfficial.CrossPlat.yml`; manual only (`pr: none`, `trigger: none`) - Both live under `eng/pipelines/onebranch/` and extend OneBranch governed templates - Never parameterize the OneBranch template name — hardcode it per pipeline for PRC compliance diff --git a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml index 51c70e8c64..2f5238dd38 100644 --- a/eng/pipelines/onebranch/jobs/build-buildproj-job.yml +++ b/eng/pipelines/onebranch/jobs/build-buildproj-job.yml @@ -102,12 +102,9 @@ jobs: ob_outputDirectory: '$(JOB_OUTPUT)' - # APIScan configuration for the package being built by this job. - ob_sdl_apiscan_enabled: true + # APIScan per-job configuration for the DLL and PDB folders. ob_sdl_apiscan_softwareFolder: ${{ parameters.apiScanDllPath }} ob_sdl_apiscan_symbolsFolder: ${{ parameters.apiScanPdbPath }} - ob_sdl_apiscan_softwarename: ${{ parameters.packageFullName }} - ob_sdl_apiscan_versionNumber: ${{ parameters.fileVersion }} steps: - template: /eng/pipelines/onebranch/steps/script-output-environment-variables-step.yml@self diff --git a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml index 8ee37ad7d7..b3ada2a6f0 100644 --- a/eng/pipelines/onebranch/jobs/publish-symbols-job.yml +++ b/eng/pipelines/onebranch/jobs/publish-symbols-job.yml @@ -61,7 +61,6 @@ jobs: # Disable SDL scanning — this job only uploads/publishes PDBs and produces no # assemblies to scan. APIScan and BinSkim are handled by the build jobs. ob_sdl_apiscan_enabled: false - ob_sdl_binskim_break: false ob_sdl_binskim_enabled: false # Path where the downloaded artifact will be placed. diff --git a/eng/pipelines/onebranch/sqlclient-non-official.yml b/eng/pipelines/onebranch/sqlclient-non-official.yml index abf9b6dcbe..51fd924ac7 100644 --- a/eng/pipelines/onebranch/sqlclient-non-official.yml +++ b/eng/pipelines/onebranch/sqlclient-non-official.yml @@ -6,12 +6,21 @@ name: $(Year:YY)$(DayOfYear)$(Rev:.r) -# No automated triggers; this pipeline must be run manually. +# No activity- or schedule-based triggers. This pipeline is run manually only. pr: none trigger: none # These parameters are visible in the Azure DevOps pipeline UI when a new run is queued. parameters: + + # When true, any SDL errors will break the build. When false, SDL errors will be logged but + # will not break the build. TSA bug filing is always disabled in the non-official pipeline + # (see the tsa block in globalSdl). + - name: breakOnSdlError + displayName: Break on SDL error + type: boolean + default: true + # True to enable debug information and steps. - name: debug displayName: Enable debug output @@ -126,15 +135,64 @@ extends: category: NonAzure # See: https://aka.ms/obpipelines/sdl + # + # The following SDL tasks are auto-injected by the OneBranch / 1ES Pipeline Templates and run + # WITHOUT any explicit globalSdl configuration. They were verified as running in recent + # pipeline runs, so we intentionally do not redeclare them here: + # + # - Component Governance / Component Detection / Secure Supply Chain + # Analysis (dependency vulnerability scanning) + # - AntiMalware Scanner (Binary + Source) + # - 1ES Secret Scanning (SPMI) + # - Generate SBoM Manifest + # - CodeQL 3000 (only run when it is explicitly opted in via codeql.compiled.enabled, and + # even then only on its ~72h cadence and on supported (non-PR/non-tag) + # branches.) + # globalSdl: + + # Snapshot of the SDL analyzer findings that pre-existed the breakOnSdlError rollout, so + # builds only break on NEW findings. Generated from the SDL analysis artifacts of a full + # non-official run and kept under .config/ alongside the other SDL tool configs + # (CredScan/PoliCheck/TSA). + baseline: + baselineFile: $(Build.SourcesDirectory)/.config/guardian/.gdnbaselines + + # Configure APIScan behaviour: + # + # https://eng.ms/docs/products/onebranch/securitycompliancegovernanceandpolicies/sdlforcontainerizedworkflows/customizesdlforcontainerbuilds#apiscan + # + # https://eng.ms/docs/coreai/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/apiscan-build-task#v2 + # apiscan: - # APIScan "software name" and version parameters are set on a per-job basis with via - # ob_sdl_apiscan_* variables. + # We want APIScan enabled by default for our jobs. However, some jobs don't produce any + # artifacts, and they will disable APIScan via the OneBranch per-job ob_sdl_apiscan_enabled + # variable. enabled: true - break: false + + # APIScan errors break the build when breakOnSdlError is set. + break: ${{ parameters.breakOnSdlError }} + + # Use pre-release mode for non-official pipelines. + modeType: prerelease + + # We have a single "name" registered with APIScan for all of our packages. + # + # https://eng.ms/docs/products/apiscan/onboard/requirements/registersoftware + # + softwareName: Microsoft.Data.SqlClient + + # Similar to the software name, we have a single version registered as well. This has + # nothing to do with the NuGet package version. It is purely an APIScan registration + # value that points to our backend configuration. + softwareVersion: '6.10' + + # We want the standard level of logging. + verbosityLevel: standard armory: enabled: true + break: ${{ parameters.breakOnSdlError }} asyncSdl: # Disabling this as it complicates the build process with minimal gain @@ -142,10 +200,14 @@ extends: binskim: enabled: true + break: ${{ parameters.breakOnSdlError }} codeql: - enabled: true + # CodeQL 3000 is configured under the `compiled` key; `enabled` is a sub-property of + # `compiled` (not of `codeql`). CodeQL runs asynchronously on its own platform and cannot + # break the build by design, so it has no `break` value. compiled: + enabled: true credscan: enabled: true @@ -154,14 +216,23 @@ extends: eslint: # Only useful for repos with ECMAscript - which we do not have. enabled: false + # Break value is wired preemptively so it takes effect if eslint is ever enabled. + break: ${{ parameters.breakOnSdlError }} policheck: enabled: true + break: ${{ parameters.breakOnSdlError }} exclusionsFile: '$(REPO_ROOT)\.config\PolicheckExclusions.xml' + psscriptanalyzer: + # Static analysis of the repository's PowerShell scripts (e.g. under eng/). + enabled: true + break: ${{ parameters.breakOnSdlError }} + roslyn: # Note, requires RoslynAnalyzers task to be added as a separate step enabled: true + break: ${{ parameters.breakOnSdlError }} publishLogs: enabled: true @@ -172,9 +243,29 @@ extends: packageVersion: '$(Build.BuildNumber)' tsa: - # We disable TSA for non-official pipelines. This inhibits spurious TSA alerts and ADO task - # creation, since non-official builds are often done against development branches. This also - # forces all SDL tasks into "break" build mode. + # TSA (Trust Services Automation) files SDL analysis findings as Azure DevOps bug work + # items. It is hardcoded DISABLED in the non-official pipeline. + # + # Why disabled here: + # The non-official pipeline is a manual-only, developer-helper build used to validate + # SDL scans and iterate on fixes before they land. We do not want it filing (or + # updating) bug work items -- that would create churn and duplicate the bugs owned by + # the official pipeline, where TSA is hardcoded enabled. Here, findings surface + # directly in the build instead, gated by breakOnSdlError. + # + # Interaction with other SDL tool defaults: + # OneBranch derives each SDL tool's DEFAULT `break` value from whether TSA is enabled -- + # per the OneBranch "Customize SDL" docs, most tools default to `break: false` when TSA + # is enabled and `break: true` when it is disabled. We do NOT rely on that implicit + # coupling: every tool above sets `break: ${{ parameters.breakOnSdlError }}` explicitly, + # which overrides the TSA-derived default. We have hit bugs in OneBranch's implicit + # break-altering logic in the past, so both `tsa.enabled` and each tool's `break` are + # set explicitly to keep the behaviour deterministic. + # + # Otherwise independent of breakOnSdlError: + # Aside from that default-flipping, TSA and breakOnSdlError are orthogonal. Disabling + # TSA here does not by itself force breaking -- breakOnSdlError is what controls whether + # findings fail the build. enabled: false configFile: '$(REPO_ROOT)/.config/tsaoptions.json' diff --git a/eng/pipelines/onebranch/sqlclient-official.yml b/eng/pipelines/onebranch/sqlclient-official.yml index 8ea429bb00..69aeea72fb 100644 --- a/eng/pipelines/onebranch/sqlclient-official.yml +++ b/eng/pipelines/onebranch/sqlclient-official.yml @@ -6,15 +6,9 @@ name: $(Year:YY)$(DayOfYear)$(Rev:.r) -# No PR-based triggers. +# No activity-based triggers. pr: none - -# We trigger runs on activity on the internal/main branch, but not on any other branches. This -# pipeline is intended to only run against the ADO.Net dotnet-sqlclient repo. -trigger: - branches: - include: - - internal/main +trigger: none # We run this pipeline on a daily schedule. schedules: @@ -27,19 +21,21 @@ schedules: # These parameters are visible in the Azure DevOps pipeline UI when a new run is queued. parameters: + + # When true, any SDL errors will break the build. When false, SDL errors will be logged but + # will not break the build. TSA bug filing is always enabled in the official pipeline and is + # independent of this parameter (see the tsa block in globalSdl). + - name: breakOnSdlError + displayName: Break on SDL error + type: boolean + default: true + # True to enable debug information and steps. - name: debug displayName: Enable debug output type: boolean default: false - # When true, publish symbols and push NuGet packages to Production environments. When false, - # symbols use PPE and NuGet packages use QA/Test. - - name: releaseToProduction - displayName: Publish Symbols and NuGet Packages to Production - type: boolean - default: false - # True if this is a preview build. - name: isPreview displayName: Is this a preview build? @@ -52,6 +48,13 @@ parameters: type: boolean default: false + # When true, publish symbols and push NuGet packages to Production environments. When false, + # symbols use PPE and NuGet packages use QA/Test. + - name: releaseToProduction + displayName: Publish Symbols and NuGet Packages to Production + type: boolean + default: false + # Build parameters — select which packages to build. # Build the Microsoft.SqlServer.Server package. @@ -147,20 +150,64 @@ extends: category: NonAzure # See: https://aka.ms/obpipelines/sdl + # + # The following SDL tasks are auto-injected by the OneBranch / 1ES Pipeline Templates and run + # WITHOUT any explicit globalSdl configuration. They were verified as running in recent + # pipeline runs, so we intentionally do not redeclare them here: + # + # - Component Governance / Component Detection / Secure Supply Chain + # Analysis (dependency vulnerability scanning) + # - AntiMalware Scanner (Binary + Source) + # - 1ES Secret Scanning (SPMI) + # - Generate SBoM Manifest + # - CodeSign Validation (runs on the signing path; nothing is signed in non-official runs) + # - CodeQL 3000 (ALWAYS enabled via the 1ESPT (Official.CrossPlat) entry point, regardless of + # the codeql config below.) + # globalSdl: + + # Snapshot of the SDL analyzer findings that pre-existed the breakOnSdlError rollout, so + # builds only break on NEW findings. Generated from the SDL analysis artifacts of a full + # non-official run and kept under .config/ alongside the other SDL tool configs + # (CredScan/PoliCheck/TSA). + baseline: + baselineFile: $(Build.SourcesDirectory)/.config/guardian/.gdnbaselines + + # Configure APIScan behaviour: + # + # https://eng.ms/docs/products/onebranch/securitycompliancegovernanceandpolicies/sdlforcontainerizedworkflows/customizesdlforcontainerbuilds#apiscan + # + # https://eng.ms/docs/coreai/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/apiscan-build-task#v2 + # apiscan: - # APIScan "software name" and version parameters are set on a per-job basis with via - # ob_sdl_apiscan_* variables. + # We want APIScan enabled by default for our jobs. However, some jobs don't produce any + # artifacts, and they will disable APIScan via the OneBranch per-job ob_sdl_apiscan_enabled + # variable. enabled: true - # TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/42858): - # We have temporarily disabled breaking the build on ApiScan results until we can: - # - Register our new packages with API Scan, and/or - # - Publish MSDN/Learn/etc documentation for the new packages. - break: false + + # APIScan errors break the build when breakOnSdlError is set. + break: ${{ parameters.breakOnSdlError }} + + # Use release mode for official pipelines. + modeType: release + + # We have a single "name" registered with APIScan for all of our packages. + # + # https://eng.ms/docs/products/apiscan/onboard/requirements/registersoftware + # + softwareName: Microsoft.Data.SqlClient + + # Similar to the software name, we have a single version registered as well. This has + # nothing to do with the NuGet package version. It is purely an APIScan registration + # value that points to our backend configuration. + softwareVersion: '6.10' + + # We want the standard level of logging. + verbosityLevel: standard armory: enabled: true - break: true + break: ${{ parameters.breakOnSdlError }} asyncSdl: # Disabling this as it complicates the build process with minimal gain @@ -168,11 +215,14 @@ extends: binskim: enabled: true - break: true + break: ${{ parameters.breakOnSdlError }} codeql: - enabled: true + # CodeQL 3000 is configured under the `compiled` key; `enabled` is a sub-property of + # `compiled` (not of `codeql`). CodeQL runs asynchronously on its own platform and cannot + # break the build by design, so it has no `break` value. compiled: + enabled: true credscan: enabled: true @@ -181,16 +231,23 @@ extends: eslint: # Only useful for repos with ECMAscript - which we do not have. enabled: false + # Break value is wired preemptively so it takes effect if eslint is ever enabled. + break: ${{ parameters.breakOnSdlError }} policheck: enabled: true - break: true + break: ${{ parameters.breakOnSdlError }} exclusionsFile: '$(REPO_ROOT)\.config\PolicheckExclusions.xml' + psscriptanalyzer: + # Static analysis of the repository's PowerShell scripts (e.g. under eng/). + enabled: true + break: ${{ parameters.breakOnSdlError }} + roslyn: # Note, requires RoslynAnalyzers task to be added as a separate step enabled: true - break: true + break: ${{ parameters.breakOnSdlError }} publishLogs: enabled: true @@ -201,8 +258,25 @@ extends: packageVersion: '$(Build.BuildNumber)' tsa: - # OneBranch publishes all sdl results to TSA. If TSA is disabled, all SDL tools will be - # forced into 'break' build mode. + # TSA (Trust Services Automation) files SDL analysis findings as Azure DevOps bug work + # items. It is hardcoded enabled in the official pipeline so that every SDL finding is + # always tracked as a bug, regardless of how any individual run terminates. + # + # Interaction with other SDL tool defaults: + # OneBranch derives each SDL tool's DEFAULT `break` value from whether TSA is enabled -- + # per the OneBranch "Customize SDL" docs, most tools default to `break: false` when TSA + # is enabled and `break: true` when it is disabled. We do NOT rely on that implicit + # coupling: every tool above sets `break: ${{ parameters.breakOnSdlError }}` explicitly, + # which overrides the TSA-derived default. We have hit bugs in OneBranch's implicit + # break-altering logic in the past, so both `tsa.enabled` and each tool's `break` are + # set explicitly to keep the behaviour deterministic. + # + # Otherwise independent of breakOnSdlError: + # Aside from that default-flipping, TSA and breakOnSdlError are orthogonal. TSA files + # bugs from the complete finding set (the TSAUpload step runs after all analyzers have + # produced results), while breakOnSdlError separately controls whether those same + # findings also fail the build. Enabling TSA here does not suppress breaking, and + # breaking does not prevent bug filing within a job. enabled: true configFile: '$(REPO_ROOT)/.config/tsaoptions.json' diff --git a/eng/pipelines/onebranch/variables/onebranch-variables.yml b/eng/pipelines/onebranch/variables/onebranch-variables.yml index 7d940c1cb7..d56d95044e 100644 --- a/eng/pipelines/onebranch/variables/onebranch-variables.yml +++ b/eng/pipelines/onebranch/variables/onebranch-variables.yml @@ -51,10 +51,6 @@ variables: - name: CommitHead value: '' # the value will be extracted from the repo's head - # https://aka.ms/obpipelines/sdl - - name: ob_sdl_binskim_break - value: true - - name: Packaging.EnableSBOMSigning value: true From 80d9ee2a96165a91a6da7503b76cedea34b34e9b Mon Sep 17 00:00:00 2001 From: Malcolm Daigle Date: Mon, 27 Jul 2026 11:22:26 -0700 Subject: [PATCH 208/298] Expand connection pool benchmark coverage for pool implementation A/B testing (#4459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Expand connection pool benchmark coverage for pool implementation A/B testing Adds tooling and benchmarks to evaluate the new ChannelDbConnectionPool (UseConnectionPoolV2) against the legacy WaitHandleDbConnectionPool, aligned with the design goals in issue #3356 (parallel connection opening, async best practices / low threadpool pressure, minimal lock contention). - Add a process-level `UseConnectionPoolV2` config flag (runnerconfig.jsonc -> Config -> Program) that sets the AppContext switch once at startup. The pool implementation switch is read and cached when the first pool is created, so it cannot be toggled per iteration; comparing the two pools requires two runs (flag false, then true). This applies to every pool benchmark in the suite. - Add ConnectionPoolChurnRunner: single-threaded, no-contention open/close on a warm pool. Isolates raw per-checkout CPU and allocation cost (the low-noise counterpart to the parallel/contention runners). - Add ConnectionPoolContentionRunner: steady-state open/query/close with pure sync vs pure async workers and a large (no-wait) vs small (back-pressure) pool axis, focused on the waiter wake path and threadpool pressure. Leaves the existing ConnectionPoolStressRunner and ParallelAsyncConnectionRunner untouched; the new flag lets them all be run against either pool implementation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address Copilot review comments on ConnectionPoolContentionRunner - Split SteadyStateOpenQueryClose into separate async/sync Task.Run paths (Task.Run(Func) vs Task.Run(Action)) so the sync branch does not create an async state machine, removing overhead unrelated to pool behavior. - Wrap WarmPool connections in try/finally and dispose each SqlConnection after closing to prevent retaining objects until GC each iteration, which would add allocation noise to the benchmark measurements. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Split sync/async pool benchmarks into separate methods Address review feedback: remove the AsyncBehavior param from the churn and contention runners and expose dedicated sync and async benchmarks. The previous single async-signed method forced the sync path to pay async state-machine overhead, distorting per-operation allocation measurements. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Clarify UseConnectionPoolV2 switch comment in perf test setup Address review feedback: the switch is always set from config (true or false), not only when the flag is enabled. Reword the comment accordingly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add over-provisioned pool case to contention benchmark Address review feedback: add a MaxPoolSize (100) greater than Parallelism (50) so the contention runner covers all three regimes — pool larger than, equal to, and smaller than the worker count. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Return Task.WhenAll directly from contention benchmarks Address review feedback: the contention benchmark methods only build a task array and await Task.WhenAll, so they don't need an async state machine. Return the Task directly to avoid an extra allocation per invocation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../ConnectionPoolChurnRunner.cs | 95 ++++++++++ .../ConnectionPoolContentionRunner.cs | 163 ++++++++++++++++++ .../tests/PerformanceTests/Config/Config.cs | 16 ++ .../tests/PerformanceTests/Program.cs | 26 +++ .../tests/PerformanceTests/runnerconfig.jsonc | 22 +++ 5 files changed, 322 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolChurnRunner.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolContentionRunner.cs diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolChurnRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolChurnRunner.cs new file mode 100644 index 0000000000..ca1930e451 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolChurnRunner.cs @@ -0,0 +1,95 @@ +// 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.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Measures the raw per-checkout overhead of the connection pool: a single thread + /// repeatedly opens and closes a connection against a warm pool, with no contention. + /// + /// With only one caller and a pre-warmed pool, every open is a pure pool checkout + /// (no physical connect, no waiting), so this benchmark isolates the CPU and + /// allocation cost of the pool's acquire/return path itself. It is the low-noise + /// counterpart to the parallel and contention runners and is the most sensitive + /// measure of per-operation allocations (watch the Allocated / Gen0 columns) — a key + /// concern for the new ChannelDbConnectionPool, which aims to avoid extra allocations + /// on the hot path (issue #3356). + /// + /// The pool implementation (legacy vs V2) is a process-level choice — see the remarks + /// on . Run twice (UseConnectionPoolV2 false + /// then true) to compare. + /// + /// Related issue: #3356 + /// + public class ConnectionPoolChurnRunner : BaseRunner + { + /// + /// Number of sequential open/close operations performed per invocation. Kept high + /// so each measured invocation captures many pool checkouts, yielding a stable + /// per-operation cost. + /// + [Params(1000)] + public int OpsPerInvocation { get; set; } + + private string _connectionString; + + [GlobalSetup] + public void Setup() + { + Console.WriteLine( + "[ConnectionPoolChurnRunner] Pool implementation: " + + (s_config.UseConnectionPoolV2 + ? "ChannelDbConnectionPool (V2)" + : "WaitHandleDbConnectionPool (legacy)")); + + var builder = new SqlConnectionStringBuilder(s_config.ConnectionString) + { + Pooling = true, + MaxPoolSize = 100, + // Pre-warm a single connection so the very first checkout is served from + // the pool rather than establishing a physical connection. + MinPoolSize = 1 + }; + _connectionString = builder.ConnectionString; + + // Force the pool to exist and hold at least one idle connection. + using var warm = new SqlConnection(_connectionString); + warm.Open(); + warm.Close(); + } + + [GlobalCleanup] + public void Cleanup() + { + using var conn = new SqlConnection(_connectionString); + SqlConnection.ClearPool(conn); + } + + [Benchmark] + public void RapidOpenCloseSingleThread() + { + for (int i = 0; i < OpsPerInvocation; i++) + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + // Dispose returns the connection to the pool. + } + } + + [Benchmark] + public async Task RapidOpenCloseSingleThreadAsync() + { + for (int i = 0; i < OpsPerInvocation; i++) + { + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + // Dispose returns the connection to the pool. + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolContentionRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolContentionRunner.cs new file mode 100644 index 0000000000..899d25f64c --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolContentionRunner.cs @@ -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. + +using System; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + /// + /// Measures steady-state pool throughput under concurrent load — the hot path for + /// real applications such as web servers, where many workers repeatedly check a + /// connection out of a warm pool, do a small unit of work, and return it. + /// + /// This is where the legacy WaitHandleDbConnectionPool and the new + /// ChannelDbConnectionPool differ most (issue #3356): + /// + /// - When the pool is large enough to serve every worker, the benchmark isolates + /// checkout/return overhead and lock contention on the hot path. + /// - When the pool is smaller than the worker count ( less + /// than ), workers must wait for a connection to be + /// returned. This exercises the waiter wake path: the legacy pool parks on + /// WaitHandle.WaitAny (a blocking wait that consumes a threadpool thread for async + /// callers), while the new pool awaits the idle channel asynchronously. The + /// ThreadingDiagnoser's "Completed Work Items" and "Lock Contentions" columns make + /// the difference visible. + /// + /// The pool implementation (legacy vs V2) and SNI are process-level choices — see the + /// remarks on . Run twice (UseConnectionPoolV2 + /// false then true) to compare. + /// + /// Related issues: #601, #979, #3356 + /// + public class ConnectionPoolContentionRunner : BaseRunner + { + /// + /// Number of concurrent workers competing for pooled connections. + /// + [Params(50)] + public int Parallelism { get; set; } + + /// + /// Maximum pool size, exercised across three regimes relative to + /// : larger than the worker count (idle spare + /// connections, no contention), equal to it (fully subscribed, no contention), and + /// smaller than it (pool exhaustion forces workers to wait for a connection to be + /// returned — back-pressure). + /// + [Params(100, 50, 10)] + public int MaxPoolSize { get; set; } + + /// + /// Number of open/query/close operations each worker performs per invocation. + /// + [Params(20)] + public int OpsPerWorker { get; set; } + + private string _connectionString; + + [GlobalSetup] + public void Setup() + { + Console.WriteLine( + "[ConnectionPoolContentionRunner] Pool implementation: " + + (s_config.UseConnectionPoolV2 + ? "ChannelDbConnectionPool (V2)" + : "WaitHandleDbConnectionPool (legacy)")); + + var builder = new SqlConnectionStringBuilder(s_config.ConnectionString) + { + Pooling = true, + MaxPoolSize = MaxPoolSize, + MinPoolSize = 0 + }; + _connectionString = builder.ConnectionString; + } + + [IterationSetup] + public void IterationSetup() + { + // Warm the pool up to MaxPoolSize so the measured run reflects steady-state + // checkout/return rather than first-time physical connection establishment. + WarmPool(MaxPoolSize); + } + + [IterationCleanup] + public void IterationCleanup() + { + using var conn = new SqlConnection(_connectionString); + SqlConnection.ClearPool(conn); + } + + [Benchmark] + public Task SteadyStateOpenQueryClose() + { + var tasks = new Task[Parallelism]; + for (int i = 0; i < Parallelism; i++) + { + tasks[i] = Task.Run(() => + { + for (int op = 0; op < OpsPerWorker; op++) + { + using var conn = new SqlConnection(_connectionString); + conn.Open(); + using var cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT 1"; + _ = cmd.ExecuteScalar(); + // Dispose returns the connection to the pool. + } + }); + } + + return Task.WhenAll(tasks); + } + + [Benchmark] + public Task SteadyStateOpenQueryCloseAsync() + { + var tasks = new Task[Parallelism]; + for (int i = 0; i < Parallelism; i++) + { + tasks[i] = Task.Run(async () => + { + for (int op = 0; op < OpsPerWorker; op++) + { + using var conn = new SqlConnection(_connectionString); + await conn.OpenAsync(); + using var cmd = conn.CreateCommand(); + cmd.CommandText = "SELECT 1"; + _ = await cmd.ExecuteScalarAsync(); + // Dispose returns the connection to the pool. + } + }); + } + + return Task.WhenAll(tasks); + } + + private void WarmPool(int count) + { + var conns = new SqlConnection[count]; + try + { + for (int i = 0; i < count; i++) + { + conns[i] = new SqlConnection(_connectionString); + conns[i].Open(); + } + } + finally + { + // Close and dispose every connection so they return to the pool and + // are not retained until GC (which would add allocation/GC noise). + for (int i = 0; i < count; i++) + { + conns[i]?.Close(); + conns[i]?.Dispose(); + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs index 7a93f65090..2bbd1f1c23 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs @@ -12,6 +12,20 @@ public class Config public string ConnectionString; public bool UseManagedSniOnWindows; public bool UseOptimizedAsyncBehaviour; + + /// + /// When true, selects the new channel-based connection pool + /// (ChannelDbConnectionPool) by enabling the + /// Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2 AppContext switch. + /// When false (the default), the legacy WaitHandleDbConnectionPool is used. + /// + /// This is a process-level setting: the switch is read and cached the first time + /// a pool is created, so it cannot be toggled per benchmark iteration. To compare + /// the two implementations, run the benchmark once with this flag false and once + /// with it true. + /// + public bool UseConnectionPoolV2; + public bool WaitForProfiler; public bool UseNativeMemoryAndETWProfiler; public Benchmarks Benchmarks; @@ -54,6 +68,8 @@ public class Benchmarks public RunnerJob JsonVsVarcharReadRunnerConfig; public RunnerJob BeginTransactionRunnerConfig; public RunnerJob ConnectionPoolStressRunnerConfig; + public RunnerJob ConnectionPoolContentionRunnerConfig; + public RunnerJob ConnectionPoolChurnRunnerConfig; } public class RunnerJob diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs index 8f64c7448a..c23ffc8412 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs @@ -33,6 +33,8 @@ public Program() Run_JsonVsVarcharReadBenchmark(); Run_BeginTransactionBenchmark(); Run_ConnectionPoolStressBenchmark(); + Run_ConnectionPoolContentionBenchmark(); + Run_ConnectionPoolChurnBenchmark(); // TODOs: // Prepared/Regular Parameterized queries @@ -48,6 +50,14 @@ private void SetupConfigurations() AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); } + // Set the UseConnectionPoolV2 AppContext switch from config (true or false). + // This must be set before any connection is opened because the switch is read + // and cached when a pool is first created; it cannot be changed later in the + // process lifetime. + AppContext.SetSwitch( + "Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2", + _config.UseConnectionPoolV2); + // If the config file specifies to use optimized async behavior, // enable packet multiplexing feature and other optimizations in SqlClient // by setting the appropriate AppContext switches. @@ -186,6 +196,22 @@ private void Run_ConnectionPoolStressBenchmark() } } + private void Run_ConnectionPoolContentionBenchmark() + { + if (_config.Benchmarks.ConnectionPoolContentionRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.ConnectionPoolContentionRunnerConfig)); + } + } + + private void Run_ConnectionPoolChurnBenchmark() + { + if (_config.Benchmarks.ConnectionPoolChurnRunnerConfig?.Enabled == true) + { + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.ConnectionPoolChurnRunnerConfig)); + } + } + /// /// The main entry point for the performance tests program. /// diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc index 34d7a511aa..5777db9652 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc @@ -2,6 +2,12 @@ "ConnectionString": "Server=tcp:localhost; Integrated Security=true; Initial Catalog=sqlclient-perf-db;", // Enable this flag to enable managed SNI on Windows. "UseManagedSniOnWindows": false, + // Enable this flag to select the new channel-based connection pool + // (ChannelDbConnectionPool) instead of the legacy WaitHandleDbConnectionPool. + // This is a process-level setting (the pool implementation switch is cached the + // first time a pool is created), so comparing the two pools requires two runs: + // once with this flag false (legacy) and once true (new). See issue #3356. + "UseConnectionPoolV2": false, // Enable this flag to enable optimized async behavior in SqlClient. // This is useful for benchmarking packet multiplexing and async performance improvements. "UseOptimizedAsyncBehaviour": true, @@ -120,6 +126,22 @@ "InvocationCount": 1, "WarmupCount": 1, "RowCount": 0 + }, + "ConnectionPoolContentionRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 15, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 0 + }, + "ConnectionPoolChurnRunnerConfig": { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 15, + "InvocationCount": 1, + "WarmupCount": 1, + "RowCount": 0 } } } From b298eb316d546f91c79a56c671c3384bd2bca271 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Tue, 28 Jul 2026 00:44:53 +0530 Subject: [PATCH 209/298] Complete connection pool pruning (Story 2/3/4) (#4463) --- .../ConnectionPool/ChannelDbConnectionPool.cs | 11 +- .../SqlClient/ConnectionPool/PoolPruner.cs | 86 +++++--- .../ChannelDbConnectionPoolPruningTest.cs | 184 +++++++++++++++--- 3 files changed, 227 insertions(+), 54 deletions(-) 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 index 63910c331e..0b5becc0fa 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -185,11 +185,14 @@ internal ChannelDbConnectionPool( _errorState = new BlockingPeriodErrorState(_instanceId, timeProvider: _timeProvider); } - // Pruning is only useful when the pool can grow beyond MinPoolSize. - // If min >= max, the pool is fixed-size and pruning would never activate. - if (MinPoolSize < MaxPoolSize) + // Pruning is only useful when the pool can grow beyond MinPoolSize and idle + // connections are subject to reclamation. If min >= max the pool is fixed-size so + // pruning would never activate; if Connection Idle Timeout is zero, idle connections + // are never reclaimed and there is nothing to prune. The pruning window (and thus how + // many samples are collected) is derived from IdleTimeout. + if (MinPoolSize < MaxPoolSize && PoolGroupOptions.IdleTimeout != TimeSpan.Zero) { - Pruner = new PoolPruner(this, PoolGroupOptions.LoadBalanceTimeout); + Pruner = new PoolPruner(this, PoolGroupOptions.IdleTimeout); } State = Running; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/PoolPruner.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/PoolPruner.cs index 07899d26e3..9c6621f5f0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/PoolPruner.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/PoolPruner.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using Microsoft.Data.Common; +using Microsoft.Data.SqlClient.Internal; #nullable enable @@ -14,29 +15,41 @@ namespace Microsoft.Data.SqlClient.ConnectionPool /// Periodically samples the idle connection count and, once enough samples are collected, /// computes the median and prunes that many idle connections from the pool. /// - /// This type is only instantiated when the pool can shrink (MinPoolSize < MaxPoolSize). - /// When the pool is fixed-size, the pool holds no instance. + /// This type is only instantiated when the pool can shrink (MinPoolSize < MaxPoolSize) and + /// idle-timeout based reclamation is enabled (Connection Idle Timeout > 0). When the pool is + /// fixed-size or idle reclamation is disabled, the pool holds no instance. /// /// internal sealed class PoolPruner : IDisposable { /// - /// Maximum allowed sample buffer size to prevent excessive memory allocation - /// from very large lifetime values. With a 10-second interval this supports - /// idle lifetimes up to 3000 seconds (50 minutes). + /// Maximum number of idle-count samples collected per pruning window. Bounds the sample + /// buffer size and the per-window sort cost regardless of how large the configured idle + /// timeout is. Once a window would need more than this many samples at + /// , the sampling interval is stretched instead of growing + /// the buffer, keeping the sample count bounded (the interval itself is separately capped by + /// , so for very large idle timeouts the effective window can + /// be shorter than the configured timeout). + /// At the floor this supports idle timeouts up to + /// MaxSampleSize * MinIntervalSeconds = 3000 seconds (50 minutes) before stretching. /// internal const int MaxSampleSize = 300; /// - /// Default sampling interval. + /// Minimum (default) sampling interval, in seconds. Acts as a floor so the pruning timer + /// never fires more often than this and preserves the Story 1 cadence for short/typical + /// idle timeouts (e.g. the default 300s idle timeout yields a 10s interval and 30 samples). /// - private static readonly TimeSpan DefaultSamplingInterval = TimeSpan.FromSeconds(10); + internal const int MinIntervalSeconds = 10; /// - /// Default lifetime window used for sample size calculation when - /// LoadBalanceTimeout (Connection Lifetime) is zero. + /// Maximum sampling interval, in seconds (1 day). Purely a defensive guard so the derived + /// interval handed to stays well under its + /// ~49.7-day limit for pathological idle timeouts (the Connection Idle Timeout keyword + /// accepts up to seconds). It only engages above ~300-day idle + /// timeouts, so it never affects any realistic configuration. /// - private static readonly TimeSpan DefaultLifetimeWindow = TimeSpan.FromSeconds(300); + internal const int MaxIntervalSeconds = 24 * 60 * 60; /// /// The owning connection pool whose idle connections are pruned. @@ -56,7 +69,8 @@ internal sealed class PoolPruner : IDisposable /// /// Number of idle count samples to collect before computing the median and pruning. - /// Equals lifetime / interval (rounded up), clamped to . + /// Equals ceil(idleTimeout / samplingInterval), bounded by + /// (the interval is stretched rather than exceeding the sample cap for large idle timeouts). /// private readonly int _sampleSize; @@ -88,25 +102,46 @@ internal sealed class PoolPruner : IDisposable /// Creates a new pruner for the given pool. /// /// The owning connection pool. - /// - /// The connection lifetime window used to compute sample count. - /// When zero or negative, is used. + /// + /// The configured Connection Idle Timeout, used as the pruning window. The sampling interval + /// and sample count are derived from it so the window covers the idle timeout for typical + /// values, while the interval stays bounded by and the sample + /// count by (see the constructor body for the exact formula). For + /// very large idle timeouts both are capped, so the effective window can be shorter than the + /// configured timeout. The pool only constructs a pruner when idle-timeout based reclamation is + /// enabled (idleTimeout > 0); a defensive floor keeps the sizing math valid. /// - internal PoolPruner(ChannelDbConnectionPool pool, TimeSpan lifetimeWindow) + internal PoolPruner(ChannelDbConnectionPool pool, TimeSpan idleTimeout) { _pool = pool; - _samplingInterval = DefaultSamplingInterval; - int lifetimeSeconds = (int)lifetimeWindow.TotalSeconds; - if (lifetimeSeconds <= 0) + // The pool only constructs a pruner when IdleTimeout > 0, but floor at one second so + // the sizing math below always sees a positive window even under unexpected inputs. + int idleTimeoutSeconds = Math.Max(1, (int)idleTimeout.TotalSeconds); + + // Derive the interval so the window covers the idle timeout with at most MaxSampleSize + // samples. Short/typical timeouts keep the MinIntervalSeconds cadence (e.g. 300s -> 10s, + // 30 samples); large timeouts stretch the interval and pin the count at MaxSampleSize. The + // MaxIntervalSeconds cap is a defensive Timer.Change overflow guard (engages >~300 days). + // Math.Clamp is unavailable on net462, so clamp explicitly with Min/Max. + int intervalForMaxSamples = DivideRoundingUp(idleTimeoutSeconds, MaxSampleSize); + int flooredInterval = Math.Max(intervalForMaxSamples, MinIntervalSeconds); + int intervalSeconds = Math.Min(flooredInterval, MaxIntervalSeconds); + + int sampleSize = Math.Min(MaxSampleSize, DivideRoundingUp(idleTimeoutSeconds, intervalSeconds)); + + if (intervalSeconds != MinIntervalSeconds) { - lifetimeSeconds = (int)DefaultLifetimeWindow.TotalSeconds; + // The interval was stretched (or clamped) away from the default. Surface the derived + // cadence so operators can tell why a very large Connection Idle Timeout samples less + // frequently than the default 10-second interval. + SqlClientEventSource.Log.TryPoolerTraceEvent( + " Idle timeout {0}s derived a pruning interval of {1}s with {2} samples (max {3}).", + idleTimeoutSeconds, intervalSeconds, sampleSize, MaxSampleSize); } - int intervalSeconds = (int)_samplingInterval.TotalSeconds; - _sampleSize = Math.Min( - DivideRoundingUp(lifetimeSeconds, intervalSeconds), - MaxSampleSize); + _samplingInterval = TimeSpan.FromSeconds(intervalSeconds); + _sampleSize = sampleSize; // Subtract 1 to convert from length to 0-based index. Safe because // DivideRoundingUp always returns >= 1, so _sampleSize >= 1. @@ -127,6 +162,9 @@ internal PoolPruner(ChannelDbConnectionPool pool, TimeSpan lifetimeWindow) /// Total number of samples collected per pruning window. Exposed for unit tests. internal int SampleSize => _sampleSize; + /// The derived interval between pruning samples/evaluations. Exposed for unit tests. + internal TimeSpan SamplingInterval => _samplingInterval; + /// Read-only view of the sample buffer contents. Exposed for unit tests. internal ReadOnlySpan Samples => _samples; @@ -162,7 +200,7 @@ internal void UpdateTimer() else if (numConnections <= _pool.PoolGroupOptions.MinPoolSize && _timerEnabled) { // Pool shrunk back to min — stop pruning, reset sample buffer. - _timer.Change(Timeout.Infinite, Timeout.Infinite); + _timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); _sampleIndex = 0; _timerEnabled = false; } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs index 3f7f3b0bc5..fad16cf230 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolPruningTest.cs @@ -27,7 +27,8 @@ public class ChannelDbConnectionPoolPruningTest private static ChannelDbConnectionPool ConstructPool( int minPoolSize = 0, int maxPoolSize = 50, - int loadBalanceTimeout = 0) + int loadBalanceTimeout = 0, + int idleTimeout = 300) { var poolGroupOptions = new DbConnectionPoolGroupOptions( poolByIdentity: false, @@ -36,7 +37,7 @@ private static ChannelDbConnectionPool ConstructPool( creationTimeout: 15, loadBalanceTimeout: loadBalanceTimeout, hasTransactionAffinity: true, - idleTimeout: 0 + idleTimeout: idleTimeout ); var dbConnectionPoolGroup = new DbConnectionPoolGroup( new SqlConnectionOptions("Data Source=localhost;"), @@ -160,6 +161,15 @@ public void Constructor_MinPoolSizeEqualsMax_DoesNotCreatePruningTimer() Assert.Null(pool.Pruner); } + [Fact] + public void Constructor_IdleTimeoutZero_DoesNotCreatePruningTimer() + { + // Connection Idle Timeout = 0 disables idle reclamation, so there is nothing to prune. + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, idleTimeout: 0); + + Assert.Null(pool.Pruner); + } + [Fact] public void Constructor_PruningTimerStartsDisabled() { @@ -171,19 +181,28 @@ public void Constructor_PruningTimerStartsDisabled() } [Theory] - [InlineData(100, 10)] // 100 / 10 = 10 samples - [InlineData(300, 30)] // 300 / 10 = 30 samples - [InlineData(60, 6)] // 60 / 10 = 6 samples - [InlineData(15, 2)] // 15 / 10 = 2 samples (rounds up) - [InlineData(0, 30)] // 0 falls back to DefaultLifetimeWindow (300) / 10 = 30 samples - [InlineData(10000, 300)] // 10000 / 10 = 1000, clamped to MaxPruningSampleSize (300) - public void Constructor_CalculatesSampleSizeFromLoadBalanceTimeout( - int loadBalanceTimeout, int expectedSampleSize) + // Short/typical idle timeouts keep the 10s floor and grow the sample count (Story 1 parity). + [InlineData(100, 10, 10)] // interval 10s, ceil(100/10) = 10 samples + [InlineData(300, 10, 30)] // default idle timeout: 10s, 30 samples + [InlineData(60, 10, 6)] // 10s, 6 samples + [InlineData(15, 10, 2)] // 10s, ceil(15/10) = 2 samples + [InlineData(5, 10, 1)] // sub-interval idle timeout: floored to 10s, 1 sample + [InlineData(3000, 10, 300)] // boundary: MaxSampleSize * 10s, still 10s cadence + // Large idle timeouts pin the sample count at MaxSampleSize and stretch the interval so the + // window still spans the full idle timeout. + [InlineData(6000, 20, 300)] // ceil(6000/300) = 20s interval, 300 samples + [InlineData(10000, 34, 295)] // ceil(10000/300) = 34s interval, ceil(10000/34) = 295 samples + [InlineData(86400, 288, 300)] // 1-day idle timeout: 288s interval, 300 samples + // Pathological idle timeout hits the 1-day interval overflow clamp without throwing. + [InlineData(int.MaxValue, 86400, 300)] + public void Constructor_CalculatesIntervalAndSampleSizeFromIdleTimeout( + int idleTimeout, int expectedIntervalSeconds, int expectedSampleSize) { - using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, loadBalanceTimeout: loadBalanceTimeout); + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, idleTimeout: idleTimeout); var pruner = GetPruner(pool); Assert.Equal(expectedSampleSize, pruner.SampleSize); + Assert.Equal(TimeSpan.FromSeconds(expectedIntervalSeconds), pruner.SamplingInterval); } #endregion @@ -262,9 +281,9 @@ public void UpdatePruningTimer_PoolShrinksBackToMin_DisablesTimerAndResetsSample [Fact] public void PruneIdleConnections_BufferNotFull_CollectsSampleWithoutPruning() { - using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, loadBalanceTimeout: 30); + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, idleTimeout: 30); var pruner = GetPruner(pool); - // loadBalanceTimeout=30 → sample size = 30/10 = 3 + // idleTimeout=30 → sample size = 30/10 = 3 // Fill the pool so pruning timer is active FillPoolWithIdleConnections(pool, 5); @@ -281,8 +300,8 @@ public void PruneIdleConnections_BufferNotFull_CollectsSampleWithoutPruning() [Fact] public void PruneIdleConnections_RespectsMinPoolSizeFloor() { - // loadBalanceTimeout=20 → 2 samples - using var pool = ConstructPool(minPoolSize: 5, maxPoolSize: 20, loadBalanceTimeout: 20); + // idleTimeout=20 → 2 samples + using var pool = ConstructPool(minPoolSize: 5, maxPoolSize: 20, idleTimeout: 20); var pruner = GetPruner(pool); // Fill pool with 10 idle connections @@ -305,7 +324,7 @@ public void PruneIdleConnections_RespectsMinPoolSizeFloor() [Fact] public void PruneIdleConnections_TimerDisabled_ReturnsEarlyWithoutPruning() { - using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, loadBalanceTimeout: 20); + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 10, idleTimeout: 20); var pruner = GetPruner(pool); // Pool starts empty, timer is disabled. Calling prune should be a no-op. @@ -318,8 +337,8 @@ public void PruneIdleConnections_TimerDisabled_ReturnsEarlyWithoutPruning() [Fact] public void PruneIdleConnections_DoesNotRemoveInUseConnections() { - // loadBalanceTimeout=20 → 2 samples - using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, loadBalanceTimeout: 20); + // idleTimeout=20 → 2 samples + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, idleTimeout: 20); var pruner = GetPruner(pool); // Get 5 connections and KEEP them checked out (in use) @@ -428,8 +447,8 @@ public void PruneIdleConnections_AllIdleWithZeroMinPoolSize_PrunesEntirePool() // When: The pruning interval elapses (sample buffer fills). // Then: The pool closes excess idle connections. - // Use loadBalanceTimeout=20 for 2 samples (fast buffer fill) - using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, loadBalanceTimeout: 20); + // Use idleTimeout=20 for 2 samples (fast buffer fill) + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, idleTimeout: 20); var pruner = GetPruner(pool); // Simulate high load: open 20 connections @@ -456,8 +475,8 @@ public void PruneIdleConnections_VariedSamples_UsesMedianNotCurrentIdleCount() // When: The pruning interval elapses. // Then: Pruning uses sampled usage data (median) to avoid being too aggressive. - // Use 3 samples (loadBalanceTimeout=30) - using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, loadBalanceTimeout: 30); + // Use 3 samples (idleTimeout=30) + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, idleTimeout: 30); var pruner = GetPruner(pool); // Simulate varied usage: @@ -498,8 +517,8 @@ public void PruneIdleConnections_EvenSampleCountWithDistinctValues_UsesLowerMidd // Using distinct sample values proves the lower-middle is chosen rather than the // upper-middle or an average. - // loadBalanceTimeout=20 → 2 samples - using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, loadBalanceTimeout: 20); + // idleTimeout=20 → 2 samples + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, idleTimeout: 20); var pruner = GetPruner(pool); // Start with 8 idle connections. @@ -535,8 +554,8 @@ public void PruneIdleConnections_SecondWindowAfterPrune_RecomputesMedianFromFres // the first: it computes its own median from newly recorded samples and prunes the // correct amount, confirming the collect → prune → reset → collect cycle is repeatable. - // loadBalanceTimeout=20 → 2 samples per window - using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, loadBalanceTimeout: 20); + // idleTimeout=20 → 2 samples per window + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 20, idleTimeout: 20); var pruner = GetPruner(pool); // --- Window 1: produces a median of 4 --- @@ -570,6 +589,119 @@ public void PruneIdleConnections_SecondWindowAfterPrune_RecomputesMedianFromFres Assert.Equal(0, pool.Count); } + [Fact] + public void PruneIdleConnections_TransientDemandSpikeWithinWindow_DoesNotUnderPrune() + { + // Complements VariedSamples (which proves a brief lull doesn't over-prune): + // here a brief demand spike (a single low idle sample) must not drag the median down + // and cause the pruner to under-prune. The sustained idle level should still be reclaimed. + + // idleTimeout=30 → 3 samples + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, idleTimeout: 30); + var pruner = GetPruner(pool); + + FillPoolWithIdleConnections(pool, 10); + + // Sample 1: 10 idle (steady state). + pruner.OnPruningCallback(null); + AssertPrunerState(pruner, true, 1, 10); + Assert.Equal(10, pool.Count); + + // Demand spike: check out 8, leaving only 2 idle for the middle sample. + var busyConnections = CheckOutConnections(pool, 8); + + // Sample 2: 2 idle (transient spike). + pruner.OnPruningCallback(null); + AssertPrunerState(pruner, true, 2, 10, 2); + Assert.Equal(10, pool.Count); + + // Spike subsides — all connections idle again. + ReturnConnections(pool, busyConnections); + + // Sample 3: 10 idle. Buffer full → sorted=[2,10,10], median at index 1 = 10. + int countBefore = pool.Count; + pruner.OnPruningCallback(null); + // Median prunes all 10 idle down to MinPoolSize (0), so the timer disables. + AssertPrunerState(pruner, false, 0); + + // The transient spike (sample of 2) is ignored; the sustained level (10) is pruned. + int pruned = countBefore - pool.Count; + Assert.Equal(10, pruned); + Assert.Equal(0, pool.Count); + } + + [Fact] + public void Pruning_PruneToMinThenRegrow_ReArmsAndPrunesAgain() + { + // Exercises the full prune → disable → regrow → re-arm → prune cycle. After a window + // prunes the pool down to MinPoolSize the timer disables; opening new connections must + // re-arm it, and the next window must sample and prune independently. + + // idleTimeout=20 → 2 samples + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, idleTimeout: 20); + var pruner = GetPruner(pool); + + // --- Window 1: prune 6 idle down to min (0), disabling the timer --- + FillPoolWithIdleConnections(pool, 6); + + pruner.OnPruningCallback(null); // sample[0] = 6 + AssertPrunerState(pruner, true, 1, 6); + + pruner.OnPruningCallback(null); // buffer full [6,6], median 6, prune 6 → Count 0 + AssertPrunerState(pruner, false, 0); + Assert.Equal(0, pool.Count); + + // --- Regrow: opening connections must re-arm the disabled timer --- + FillPoolWithIdleConnections(pool, 12); + AssertPrunerState(pruner, true, 0); + Assert.Equal(12, pool.Count); + + // --- Window 2: samples freshly and prunes 12 --- + pruner.OnPruningCallback(null); // sample[0] = 12 + AssertPrunerState(pruner, true, 1, 12); + + int countBefore = pool.Count; + pruner.OnPruningCallback(null); // buffer full [12,12], median 12, prune 12 → Count 0 + AssertPrunerState(pruner, false, 0); + + Assert.Equal(12, countBefore - pool.Count); + Assert.Equal(0, pool.Count); + } + + [Fact] + public void OnPruningCallback_AfterShutdown_IsNoOp() + { + // A pruning timer callback can race with pool shutdown. Once Shutdown() has disposed the + // pruner, a stray callback must be an inert no-op: it must not throw, re-enable the timer, + // mutate the sample index, or resurrect connections. + + // idleTimeout=30 → 3 samples + using var pool = ConstructPool(minPoolSize: 0, maxPoolSize: 50, idleTimeout: 30); + var pruner = GetPruner(pool); + + FillPoolWithIdleConnections(pool, 6); + + // Advance one sample so SampleIndex is non-zero before shutdown. + pruner.OnPruningCallback(null); + AssertPrunerState(pruner, true, 1, 6); + + pool.Shutdown(); + + // Shutdown disposes the pruner (timer disabled) and drains all idle connections. + // Dispose intentionally does NOT reset the sample index. + Assert.False(pruner.IsTimerEnabled); + Assert.Equal(0, pool.Count); + int sampleIndexAfterShutdown = pruner.SampleIndex; + + // A stray timer callback after shutdown must be a no-op. + Exception recorded = Record.Exception(() => pruner.OnPruningCallback(null)); + + Assert.Null(recorded); + Assert.False(pruner.IsTimerEnabled); + Assert.Equal(sampleIndexAfterShutdown, pruner.SampleIndex); + Assert.Equal(0, pool.Count); + } + #endregion #region Test classes From c9d6efe07465367125663fe5edf3b8881b8272fe Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Mon, 27 Jul 2026 20:19:23 +0100 Subject: [PATCH 210/298] Tests | Refactor NativeVectorFloat32Tests for future Half support (#4347) --- .../VectorTest/NativeVectorFloat32Tests.cs | 626 +--------------- .../SQL/VectorTest/NativeVectorTestsBase.cs | 678 ++++++++++++++++++ 2 files changed, 700 insertions(+), 604 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs index da55ceb1b3..5dd71c0f3f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -1,627 +1,45 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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, 1.01f, float.MinValue, -0.0f }; - 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 }; +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest; - // Pattern 1-4 with SqlVector.Null - yield return new object[] { 1, SqlVector.Null, Array.Empty(), vectorColumnLength }; +#nullable enable - // 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 VectorFloat32TestData : NativeVectorTestDataBase +{ + public override float[] SampleScalarData => [1.1f, 2.2f, 3.3f, 1.01f, float.MinValue, -0.0f]; - [Trait("Set", "3")] - public sealed class NativeVectorFloat32Tests : IDisposable + public override float[,] SampleDataSet { - private readonly ITestOutputHelper _output; - private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; - private static readonly string s_tableName = DataTestUtility.GetShortName("VectorTestTable"); - private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetShortName("VectorBulkCopyTestTable"); - private static readonly int s_vectorDimensions = VectorFloat32TestData.vectorColumnLength; - private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector({s_vectorDimensions}) NULL)"; - private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector({s_vectorDimensions}) 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.GetShortName("VectorsAsVarcharSp"); - private static readonly string s_storedProcBody = $@" - {s_vectorParamName} vector({s_vectorDimensions}), -- Input: Serialized float[] as JSON string - {s_outputVectorParamName} vector({s_vectorDimensions}) 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.IsSqlVectorSupported))] - [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.IsSqlVectorSupported))] - [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] - public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( - int pattern, - object value, - float[] expectedValues, - int expectedLength) + get { - 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); - } + float[,] sampleData = new float[10, ValidSampleScalarDataLength]; - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [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) + for (int i = 0; i < sampleData.GetLength(0); i++) { - CommandType = CommandType.StoredProcedure - }; + float baseValue = i * 10; - // Set input and output parameters - SqlParameter inputParam = pattern switch - { - 1 => new SqlParameter + for (int j = 0; j < sampleData.GetLength(1); j++) { - 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.IsSqlVectorSupported))] - [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.IsSqlVectorSupported))] - [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}"); + sampleData[i, j] = baseValue + (j * 0.1f); } } - 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); + return sampleData; } + } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - [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.IsSqlVectorSupported))] - public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() - { - using var connection = new SqlConnection(s_connectionString); - connection.Open(); - - // Insert a row so we can query it - using (var insertCmd = new SqlCommand(s_insertCmdString, connection)) - { - var param = insertCmd.Parameters.Add(s_vectorParamName, SqlDbTypeExtensions.Vector); - param.Value = new SqlVector(VectorFloat32TestData.testData); - insertCmd.ExecuteNonQuery(); - } - - using var selectCmd = new SqlCommand(s_selectCmdString, connection); - using var reader = selectCmd.ExecuteReader(); - - // Verify GetFieldType returns SqlVector for the vector column - Assert.Equal(typeof(SqlVector), reader.GetFieldType(0)); - - // Verify GetProviderSpecificFieldType also returns SqlVector - Assert.Equal(typeof(SqlVector), reader.GetProviderSpecificFieldType(0)); + public override int IncorrectScalarDataParameterSize => 3234; - // Verify that GetValue returns an instance consistent with GetFieldType - Assert.True(reader.Read(), "No data found in the table."); - object value = reader.GetValue(0); - Assert.IsType>(value); - Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)value).Memory.ToArray()); + public override bool IsSupported => DataTestUtility.IsSqlVectorSupported; - // Verify GetFieldValue> returns the correct typed value - SqlVector typedValue = reader.GetFieldValue>(0); - Assert.IsType>(typedValue); - Assert.Equal(VectorFloat32TestData.testData, typedValue.Memory.ToArray()); - } + public override string SqlServerTypeName => "float32"; +} - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsSqlVectorSupported))] - 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, i + 0.4f, i + 0.5f, i + 0.6f }); - 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, rowcnt + 0.4f, rowcnt + 0.5f, rowcnt + 0.6f }; - float[] dbData = reader.GetSqlVector(0).Memory.ToArray(); - Assert.Equal(expectedData, dbData); - rowcnt++; - } - Assert.Equal(10, rowcnt); - } - } +[Trait("Set", "3")] +public sealed class NativeVectorFloat32Tests : NativeVectorTestsBase +{ } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs new file mode 100644 index 0000000000..5c9970f6ed --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -0,0 +1,678 @@ +// 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.SqlClient.Tests.Common.Fixtures.DatabaseObjects; +using Microsoft.Data.SqlTypes; +using Xunit; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest +{ + /// + /// Base class for all data passed to a type derived from . + /// + /// The element type of the . + public abstract class NativeVectorTestDataBase + where TElement : unmanaged + { + public abstract TElement[] SampleScalarData { get; } + + public abstract TElement[,] SampleDataSet { get; } + + // Incorrect size for SqlParameter.Size + public abstract int IncorrectScalarDataParameterSize { get; } + + public abstract bool IsSupported { get; } + + public abstract string SqlServerTypeName { get; } + + public int ValidSampleScalarDataLength => SampleScalarData.Length; + + public IEnumerable TestData => + [ + // Pattern 1-4 with SqlVector(values: SampleScalarData) + [ 1, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], + [ 2, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], + [ 3, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], + [ 4, new SqlVector(SampleScalarData), SampleScalarData, ValidSampleScalarDataLength ], + + // Pattern 1-4 with SqlVector(n) + [ 1, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength ], + [ 2, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength ], + [ 3, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength ], + [ 4, SqlVector.CreateNull(ValidSampleScalarDataLength), Array.Empty(), ValidSampleScalarDataLength ], + + // Pattern 1-4 with DBNull + [ 1, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength ], + [ 2, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength ], + [ 3, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength ], + [ 4, DBNull.Value, Array.Empty(), ValidSampleScalarDataLength ], + + // Pattern 1-4 with SqlVector.Null + [ 1, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength ], + + // 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. + // [ 2, SqlVector.Null, Array.Empty(), vectorColumnLength ], + + [ 3, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength ], + [ 4, SqlVector.Null, Array.Empty(), ValidSampleScalarDataLength ] + ]; + } + + /// + /// Base class for all strongly-typed manual tests for . + /// + /// The element type of the . + /// The type containing the sample data. + public abstract class NativeVectorTestsBase : IDisposable + where TElement : unmanaged + where TTestData : NativeVectorTestDataBase, new() + { + private const string VectorColumnName = "VectorData"; + private const string VectorParameterName = "@VectorData"; + private const string VectorOutputParameterName = "@OutputVectorData"; + + private readonly string _connectionString; + private readonly SqlConnection _managementConnection; + private readonly Table _vectorTable; + private readonly Table _bulkCopySourceTable; + private readonly StoredProcedure _vectorProcedure; + + private readonly string _selectCommand; + private readonly string _insertCommand; + + private bool _disposed; + + // xUnit only allows MemberData for a test to point to static methods, properties and variables. + // This presents a problem when the sample data needs to change based upon the element type of + // the SqlVector, so this compromises: it instantiates a class derived from NativeVectorTestDataBase, + // then projects the relevant fields from it as static properties in this base class. + private static TTestData TestDataInstance => + field ??= new(); + + public static bool IsSupported => TestDataInstance.IsSupported; + + public static IEnumerable TestData => TestDataInstance.TestData; + + public NativeVectorTestsBase() + { + int vectorDimensions = TestDataInstance.ValidSampleScalarDataLength; + string tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, {VectorColumnName} vector({vectorDimensions}, {TestDataInstance.SqlServerTypeName}) NULL)"; + + _connectionString = DataTestUtility.TCPConnectionString; + _managementConnection = new SqlConnection(_connectionString); + _vectorTable = new Table(_managementConnection, "VectorTestTable", tableDefinition); + _bulkCopySourceTable = new Table(_managementConnection, "VectorBulkCopyTestTable", tableDefinition); + _vectorProcedure = new StoredProcedure(_managementConnection, + prefix: "VectorsAsVarcharSp", + definition: $@" + {VectorParameterName} vector({vectorDimensions}, {TestDataInstance.SqlServerTypeName}), -- Input: Serialized TElement[] as JSON string + {VectorOutputParameterName} vector({vectorDimensions}, {TestDataInstance.SqlServerTypeName}) OUTPUT -- Output: Echoed back from latest inserted row + AS + BEGIN + SET NOCOUNT ON; + + -- Insert into vector table + INSERT INTO {_vectorTable.Name} ({VectorColumnName}) + VALUES ({VectorParameterName}); + + -- Retrieve latest entry (assumes auto-incrementing ID) + SELECT TOP 1 {VectorOutputParameterName} = {VectorColumnName} + FROM {_vectorTable.Name} + ORDER BY Id DESC; + END;"); + + _selectCommand = $"SELECT {VectorColumnName} FROM {_vectorTable.Name} ORDER BY Id DESC"; + _insertCommand = $"INSERT INTO {_vectorTable.Name} ({VectorColumnName}) VALUES ({VectorParameterName})"; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _vectorProcedure?.Dispose(); + _bulkCopySourceTable?.Dispose(); + _vectorTable?.Dispose(); + _managementConnection?.Dispose(); + } + + _disposed = true; + } + + ~NativeVectorTestsBase() => + Dispose(false); + + /// + /// Wraps an inbound in a according to + /// the specified pattern. + /// + /// Pattern number. + /// to wrap. + /// A instance wrapping the . + /// is not a valid pattern. + /// + /// can be a number from 1 to 4, inclusive, with the following meaning: + /// + /// Parameterless constructor, manually setting ParameterName, SqlDbType and Value. + /// Specify the parameter name and value directly, relying upon type inference. + /// Specify the parameter name and SqlDbType, manually setting the Value property. + /// Identical to pattern 3, but with a known-invalid parameter size. + /// + /// + /// + /// + /// + /// + private static SqlParameter GetParameterByPattern(int pattern, object? value) => + pattern switch + { + 1 => new SqlParameter + { + ParameterName = VectorParameterName, + SqlDbType = SqlDbTypeExtensions.Vector, + Value = value + }, + 2 => new SqlParameter(VectorParameterName, value), + 3 => new SqlParameter(VectorParameterName, 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(VectorParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + + private static void ValidateSqlVectorObject(bool isNull, SqlVector sqlVector, TElement[] expectedData, int expectedLength) + { + Assert.Equal(expectedData, sqlVector.Memory.ToArray()); + Assert.Equal(expectedLength, sqlVector.Length); + if (!isNull) + { + Assert.False(sqlVector.IsNull, "IsNull set to true for a non-null value"); + } + else + { + Assert.True(sqlVector.IsNull, "IsNull set to false for a null value"); + } + } + + private void ValidateInsertedData(SqlConnection connection, TElement[] expectedData, int expectedLength) + { + using SqlCommand selectCmd = new(_selectCommand, connection); + using SqlDataReader reader = selectCmd.ExecuteReader(); + Assert.True(reader.Read(), "No data found in the table."); + + // For both null and non-null cases, validate the SqlVector object + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + + if (!reader.IsDBNull(0)) + { + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorObject(reader.IsDBNull(0), (SqlVector)reader[VectorColumnName], 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[VectorColumnName]); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetSqlString(0).Value); + Assert.Throws(() => reader.GetFieldValue(0)); + } + } + + [ConditionalTheory(nameof(IsSupported))] + [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] + public void TestSqlVectorParameterInsertionAndReads( + int pattern, + object? value, + TElement[] expectedValues, + int expectedLength) + { + using SqlConnection conn = new(_connectionString); + conn.Open(); + + using SqlCommand insertCmd = new(_insertCommand, conn); + SqlParameter param = GetParameterByPattern(pattern, value); + + insertCmd.Parameters.Add(param); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + + ValidateInsertedData(conn, expectedValues, expectedLength); + } + + private async Task ValidateInsertedDataAsync(SqlConnection connection, TElement[] expectedData, int expectedLength) + { + using SqlCommand selectCmd = new(_selectCommand, connection); + using SqlDataReader 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 + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + + if (!await reader.IsDBNullAsync(0)) + { + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorObject(await reader.IsDBNullAsync(0), (SqlVector)reader[VectorColumnName], 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[VectorColumnName]); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetSqlString(0).Value); + await Assert.ThrowsAsync(async () => await reader.GetFieldValueAsync(0)); + } + } + + [ConditionalTheory(nameof(IsSupported))] + [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] + public async Task TestSqlVectorParameterInsertionAndReadsAsync( + int pattern, + object? value, + TElement[] expectedValues, + int expectedLength) + { + using SqlConnection conn = new(_connectionString); + await conn.OpenAsync(); + + using SqlCommand insertCmd = new(_insertCommand, conn); + SqlParameter param = GetParameterByPattern(pattern, value); + + insertCmd.Parameters.Add(param); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + + await ValidateInsertedDataAsync(conn, expectedValues, expectedLength); + } + + [ConditionalTheory(nameof(IsSupported))] + [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] + public void TestStoredProcParamsForVector( + int pattern, + object? value, + TElement[] expectedValues, + int expectedLength) + { + using SqlConnection conn = new(_connectionString); + conn.Open(); + using SqlCommand command = new(_vectorProcedure.Name, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Set input and output parameters + SqlParameter inputParam = GetParameterByPattern(pattern, value); + command.Parameters.Add(inputParam); + + SqlParameter outputParam = new() + { + ParameterName = VectorOutputParameterName, + SqlDbType = SqlDbTypeExtensions.Vector, + Direction = ParameterDirection.Output, + Value = SqlVector.CreateNull(TestDataInstance.ValidSampleScalarDataLength) + }; + command.Parameters.Add(outputParam); + + // Execute the stored procedure + command.ExecuteNonQuery(); + + // Validate the output parameter + SqlVector vector = (SqlVector)outputParam.Value; + ValidateSqlVectorObject(vector.IsNull, vector, expectedValues, expectedLength); + + // Validate error for conventional way of setting output parameters + command.Parameters.Clear(); + command.Parameters.Add(inputParam); + SqlParameter outputParamWithoutVal = new(VectorOutputParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; + command.Parameters.Add(outputParamWithoutVal); + Assert.Throws(() => command.ExecuteNonQuery()); + } + + [ConditionalTheory(nameof(IsSupported))] + [MemberData(nameof(TestData), DisableDiscoveryEnumeration = true)] + public async Task TestStoredProcParamsForVectorAsync( + int pattern, + object? value, + TElement[] expectedValues, + int expectedLength) + { + using SqlConnection conn = new(_connectionString); + await conn.OpenAsync(); + using SqlCommand command = new(_vectorProcedure.Name, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Set input and output parameters + SqlParameter inputParam = GetParameterByPattern(pattern, value); + command.Parameters.Add(inputParam); + + SqlParameter outputParam = new() + { + ParameterName = VectorOutputParameterName, + SqlDbType = SqlDbTypeExtensions.Vector, + Direction = ParameterDirection.Output, + Value = SqlVector.CreateNull(TestDataInstance.ValidSampleScalarDataLength) + }; + command.Parameters.Add(outputParam); + + // Execute the stored procedure + await command.ExecuteNonQueryAsync(); + + // Validate the output parameter + SqlVector vector = (SqlVector)outputParam.Value; + ValidateSqlVectorObject(vector.IsNull, vector, expectedValues, expectedLength); + + // Validate error for conventional way of setting output parameters + command.Parameters.Clear(); + command.Parameters.Add(inputParam); + SqlParameter outputParamWithoutVal = new(VectorOutputParameterName, SqlDbTypeExtensions.Vector, TestDataInstance.IncorrectScalarDataParameterSize) { Direction = ParameterDirection.Output }; + command.Parameters.Add(outputParamWithoutVal); + await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); + } + + [ConditionalTheory(nameof(IsSupported))] + [InlineData(1)] + [InlineData(2)] + public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) + { + // Setup source with test data and create destination table for bulkcopy. + using SqlConnection sourceConnection = new(_connectionString); + sourceConnection.Open(); + using SqlConnection destinationConnection = new(_connectionString); + destinationConnection.Open(); + + using DataTable table = new(_bulkCopySourceTable.Name) + { + Columns = + { + new DataColumn("Id", typeof(int)), + new DataColumn(VectorColumnName, typeof(SqlVector)) + } + }; + switch (bulkCopySourceMode) + { + + case 1: + { + // Use SQL Server table as source + using SqlCommand insertCmd = new($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); + SqlParameter vectorParam = new(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); + + // 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.Rows.Add(1, new SqlVector(TestDataInstance.SampleScalarData)); + table.Rows.Add(2, DBNull.Value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + + // Bulk copy from SQL Server table to destination table + using SqlCommand sourceDataCommand = new($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); + using SqlDataReader reader = sourceDataCommand.ExecuteReader(); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new(destinationConnection) + { + DestinationTableName = _vectorTable.Name, + }; + + 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}"); + } + + // 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($"SELECT {VectorColumnName} from {_vectorTable.Name}", 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.False(verifyReader.IsDBNull(0), "First row in the table is null."); + Assert.Equal(TestDataInstance.SampleScalarData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(TestDataInstance.SampleScalarData.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([], ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(TestDataInstance.SampleScalarData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + } + + [ConditionalTheory(nameof(IsSupported))] + [InlineData(1)] + [InlineData(2)] + public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) + { + // Setup source with test data and create destination table for bulk copy. + using SqlConnection sourceConnection = new(_connectionString); + await sourceConnection.OpenAsync(); + using SqlConnection destinationConnection = new(_connectionString); + await destinationConnection.OpenAsync(); + + using DataTable table = new(_bulkCopySourceTable.Name) + { + Columns = + { + new DataColumn("Id", typeof(int)), + new DataColumn(VectorColumnName, typeof(SqlVector)) + } + }; + switch (bulkCopySourceMode) + { + + case 1: + { + // Use SQL Server table as source + using SqlCommand insertCmd = new($"insert into {_bulkCopySourceTable.Name} values ({VectorParameterName})", sourceConnection); + SqlParameter vectorParam = new(VectorParameterName, new SqlVector(TestDataInstance.SampleScalarData)); + + // 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.Rows.Add(1, new SqlVector(TestDataInstance.SampleScalarData)); + table.Rows.Add(2, DBNull.Value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + + // Bulk copy from SQL Server table to destination table + using SqlCommand sourceDataCommand = new($"SELECT Id, {VectorColumnName} FROM {_bulkCopySourceTable.Name}", sourceConnection); + using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new($"SELECT COUNT(*) FROM {_vectorTable.Name}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new(destinationConnection) + { + DestinationTableName = _vectorTable.Name, + }; + + // Perform bulk copy + 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}"); + } + + // 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($"SELECT {VectorColumnName} from {_vectorTable.Name}", 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.False(await verifyReader.IsDBNullAsync(0), "First row in the table is null."); + SqlVector vector = await verifyReader.GetFieldValueAsync>(0); + Assert.Equal(TestDataInstance.SampleScalarData, vector.Memory.ToArray()); + Assert.Equal(TestDataInstance.SampleScalarData.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([], vector.Memory.ToArray()); + Assert.Equal(TestDataInstance.SampleScalarData.Length, vector.Length); + } + + [ConditionalFact(nameof(IsSupported))] + public void TestGetFieldTypeReturnsSqlVectorForVectorColumn() + { + using SqlConnection connection = new(_connectionString); + connection.Open(); + + // Insert a row so we can query it + using (SqlCommand insertCmd = new(_insertCommand, connection)) + { + SqlParameter param = insertCmd.Parameters.Add(VectorParameterName, SqlDbTypeExtensions.Vector); + param.Value = new SqlVector(TestDataInstance.SampleScalarData); + insertCmd.ExecuteNonQuery(); + } + + using SqlCommand selectCmd = new(_selectCommand, connection); + using SqlDataReader reader = selectCmd.ExecuteReader(); + + // Verify GetFieldType returns SqlVector for the vector column + Assert.Equal(typeof(SqlVector), reader.GetFieldType(0)); + + // Verify GetProviderSpecificFieldType also returns SqlVector + Assert.Equal(typeof(SqlVector), reader.GetProviderSpecificFieldType(0)); + + // Verify that GetValue returns an instance consistent with GetFieldType + Assert.True(reader.Read(), "No data found in the table."); + object value = reader.GetValue(0); + Assert.IsType>(value); + Assert.Equal(TestDataInstance.SampleScalarData, ((SqlVector)value).Memory.ToArray()); + + // Verify GetFieldValue> returns the correct typed value + SqlVector typedValue = reader.GetFieldValue>(0); + Assert.IsType>(typedValue); + Assert.Equal(TestDataInstance.SampleScalarData, typedValue.Memory.ToArray()); + } + + [ConditionalFact(nameof(IsSupported))] + public void TestInsertVectorsWithPrepare() + { + using SqlConnection conn = new(_connectionString); + conn.Open(); + using SqlCommand command = new(_insertCommand, conn); + SqlParameter vectorParam = new(VectorParameterName, SqlDbTypeExtensions.Vector); + command.Parameters.Add(vectorParam); + command.Prepare(); + + TElement[,] sampleDataSet = TestDataInstance.SampleDataSet; + for (int i = 0; i < sampleDataSet.GetLength(0); i++) + { + TElement[] rowData = GetMultidimensionalArraySlice(sampleDataSet, i); + vectorParam.Value = new SqlVector(rowData); + command.ExecuteNonQuery(); + } + + using SqlCommand validateCommand = new($"SELECT {VectorColumnName} FROM {_vectorTable.Name}", conn); + using SqlDataReader reader = validateCommand.ExecuteReader(); + int rowcnt = 0; + while (reader.Read()) + { + TElement[] expectedData = GetMultidimensionalArraySlice(sampleDataSet, rowcnt); + TElement[] dbData = reader.GetSqlVector(0).Memory.ToArray(); + Assert.Equal(expectedData, dbData); + rowcnt++; + } + Assert.Equal(sampleDataSet.GetLength(0), rowcnt); + + static TElement[] GetMultidimensionalArraySlice(TElement[,] sourceArray, int dimension) + { + TElement[] dst = new TElement[sourceArray.GetLength(1)]; + + for (int i = 0; i < dst.Length; i++) + { + dst[i] = sourceArray[dimension, i]; + } + return dst; + } + } + } +} From f5be62bc78c4cda67a6f76b2f06eed3889f2d443 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Tue, 28 Jul 2026 15:57:40 -0500 Subject: [PATCH 211/298] Repro Test for #3720 (Unobserved Exception) (#4460) * wip * Finish up repro test * Fixing :robot: comments and fixing *all* race conditions in the test. * Create a observable exception helper * :robot: comments * :robot: comments --- .../SqlClient/ManagedSni/SniPacketTests.cs | 146 ++++++++++++++++++ .../SqlClient/Utilities/AsyncHelperTest.cs | 78 ++-------- .../Utilities/ObservableExceptionHelper.cs | 117 ++++++++++++++ 3 files changed, 280 insertions(+), 61 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniPacketTests.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Utilities/ObservableExceptionHelper.cs diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniPacketTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniPacketTests.cs new file mode 100644 index 0000000000..89923c5813 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ManagedSni/SniPacketTests.cs @@ -0,0 +1,146 @@ +// 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.Security.Authentication; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient.ManagedSni; +using Microsoft.Data.SqlClient.UnitTests.Utilities; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests.ManagedSni +{ + /// + /// Unit tests for the class. + /// + public sealed class SniPacketTests + { + /// + /// Tests the behavior of the ReadFromStreamAsync method when the + /// asynchronous IO callback throws an exception. Ensures that such exceptions result in an + /// unobserved task exception being captured by the event handler for unobserved task + /// exceptions. + /// + // @TODO: This test is a repro scenario for GH#3720, its assertions should be inverted + // when the broken scenario has been fixed. + [Fact] + public async Task ReadFromStreamAsync_WhenAsyncIoCallbackThrows_CreatesUnobservedTaskException() + { + // Arrange + // - Set up for unobserved exception handling + using ObservableExceptionHelper exceptionHelper = new ObservableExceptionHelper(); + + // - Set up task completion source to signal end of async IO completion callback + TaskCompletionSource callbackStarted = new(TaskCreationOptions.RunContinuationsAsynchronously); + + // - Set up packet around the test handle, wire up callback + TestSniHandle testHandle = new TestSniHandle(); + SniPacket testPacket = new SniPacket(testHandle, id: 1); + + testPacket.Allocate(headerLength: 0, dataLength: 1); + testPacket.SetAsyncIOCompletionCallback((_, _) => + { + // Throw our special test exception, then before finishing complete the TCS so + // we know the exception was thrown. + try + { + throw exceptionHelper.TestException; + } + finally + { + callbackStarted.TrySetResult(); + } + }); + + // Act + // - Read some bytes from the test asynchronously. This will cause the async IO + // completion callback to fire, which will throw our test exception. + using MemoryStream emptyStream = new(); + testPacket.ReadFromStreamAsync(emptyStream); + + // - Wait for async IO to complete (should be more or less instantaneous) + await callbackStarted.Task.WaitAsync(TimeSpan.FromSeconds(5)); + + // - Force GC and wait for unhandled exception to be raised + testHandle.ReturnPacket(testPacket); + Exception? unobservedException = await exceptionHelper.Wait(TimeSpan.FromSeconds(5)); + + // Assert + Assert.NotNull(unobservedException); + } + + private sealed class TestSniHandle : SniHandle + { + public override Guid ConnectionId { get; } = Guid.NewGuid(); + + public override uint Status => TdsEnums.SNI_SUCCESS; + + public override SslProtocols ProtocolVersion => SslProtocols.None; + + public override uint CheckConnection() => TdsEnums.SNI_SUCCESS; + + public override void DisableSsl() + { + } + + public override void Dispose() + { + } + + public override uint EnableSsl(uint options) => TdsEnums.SNI_SUCCESS; + + public override SniPacket RentPacket(int headerSize, int dataSize) + { + SniPacket packet = new(this, id: 1); + packet.Allocate(headerSize, dataSize); + return packet; + } + + public override uint Receive(out SniPacket packet, int timeoutInMilliseconds) + { + throw new NotSupportedException(); + } + + public override uint ReceiveAsync(ref SniPacket packet) + { + throw new NotSupportedException(); + } + + public override void ReturnPacket(SniPacket packet) + { + packet.Release(); + GC.SuppressFinalize(packet); + } + + public override uint Send(SniPacket packet) + { + throw new NotSupportedException(); + } + + public override uint SendAsync(SniPacket packet) + { + throw new NotSupportedException(); + } + + public override void SetAsyncCallbacks(SniAsyncCallback receiveCallback, SniAsyncCallback sendCallback) + { + } + + public override void SetBufferSize(int bufferSize) + { + } + + #if DEBUG + public override void KillConnection() + { + } + #endif + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Utilities/AsyncHelperTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Utilities/AsyncHelperTest.cs index 8f1529f8ef..a719bc9ca9 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Utilities/AsyncHelperTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/Utilities/AsyncHelperTest.cs @@ -3,9 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient.UnitTests.Utilities; @@ -1730,58 +1728,26 @@ private static Task GetCancelledTask() return Task.FromCanceled(cts.Token); } - private static TaskCompletionSource GetTaskCompletionSource() - => new(TaskCreationOptions.RunContinuationsAsynchronously); + private static TaskCompletionSource GetTaskCompletionSource() => + new(TaskCreationOptions.RunContinuationsAsynchronously); - private static async Task VerifyDoesNotCreateUnobservedException(Func methodUnderTest) + private static async Task VerifyDoesNotCreateUnobservedException(Func methodUnderTest) { // Arrange - // - Set up an unobserved exception handler that only captures the exception that will - // be thrown by the continuation callback. - // NOTE: This only captures the exception thrown by the callback because unit tests run - // in parallel. Thus, other tests may cause unobserved exceptions that would cause - // false failures of this test. - Guid exceptionId = Guid.NewGuid(); - Exception? unhandledException = null; - EventHandler handleUnobservedException = - (_, args) => - { - // Filter inner exceptions on type and expected ID - IEnumerable observableInnerExceptions = - args.Exception.InnerExceptions.OfType(); - if (observableInnerExceptions.Any(oe => oe.Identifier == exceptionId)) - { - unhandledException = args.Exception; - args.SetObserved(); - } - }; - - // - Register the unobserved exception handler - // @TODO: Can we do this with a custom scheduler to avoid changing global state? - TaskScheduler.UnobservedTaskException += handleUnobservedException; - - try - { - // Act - // - Run the test method - await methodUnderTest(new ObservableException(exceptionId)); - - // - Force GC collection to get any unobserved exception to raise an exception. - // However, if the method under test is behaving correctly, this *should not* - // raise any exception. - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - - // Assert - // - The unobserved exception handler should not have captured any exception. - Assert.Null(unhandledException); - } - finally - { - // Cleanup - TaskScheduler.UnobservedTaskException -= handleUnobservedException; - } + // - Generate test exception and register unobserved exception handler that observes + // the test exception. + using ObservableExceptionHelper exceptionHelper = new ObservableExceptionHelper(); + + // Act + // - Run the test method + await methodUnderTest(exceptionHelper.TestException); + + // - Force GC and wait for unobserved exceptions to be processed. + Exception? unobservedException = await exceptionHelper.Wait(TimeSpan.FromSeconds(0.5)); + + // Assert + // - The unobserved handler should not have captured our test exception + Assert.Null(unobservedException); } private static async Task RunWithTimeout([NotNull] Task? taskToRun, TimeSpan timeout) @@ -1800,15 +1766,5 @@ private static async Task RunWithTimeout([NotNull] Task? taskToRun, TimeSpan tim // Force observation of any exception _ = taskToRun.Exception; } - - private class ObservableException : Exception - { - public ObservableException(Guid id) - { - Identifier = id; - } - - public Guid Identifier { get; set; } - } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Utilities/ObservableExceptionHelper.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Utilities/ObservableExceptionHelper.cs new file mode 100644 index 0000000000..1518803390 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Utilities/ObservableExceptionHelper.cs @@ -0,0 +1,117 @@ +// 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.Threading.Tasks; + +namespace Microsoft.Data.SqlClient.UnitTests.Utilities; + +/// +/// Provides a mechanism to observe unhandled exceptions thrown by asynchronous tasks. This helper +/// is specifically designed for cases where tasks may throw unobserved exceptions, allowing +/// these exceptions to be handled and inspected during unit tests or runtime debugging. +/// +/// +/// Usage should follow a pattern similar to the following: +/// * Construct a new instance in the "arrange" section of the test, with `using`. +/// * Set up the method under test such that it throws +/// * In "act" section of test, call and await the method under test +/// * Call and await and supply with suitable timeout, capturing the returned +/// exception, if any. +/// * Assert that the expected outcome occurred (in the "assert" section of the test). If exception +/// was expected, ensure the returned exception is not . Otherwise, ensure +/// the returned exception is . +/// +public class ObservableExceptionHelper : IDisposable +{ + private readonly EventHandler _handler; + private readonly TaskCompletionSource _unobservedExceptionRaised; + + /// + /// Constructs a new instance by initializing a test exception, creating an unobserved + /// exception handler that observes the test exception, and registering that handler on + /// the TaskScheduler. + /// + public ObservableExceptionHelper() + { + // Create special test exception + TestException = new ObservableException(); + + // Set up unobserved exception handler that only observes our special test exception + _unobservedExceptionRaised = new(TaskCreationOptions.RunContinuationsAsynchronously); + _handler = (_, args) => + { + if (args.Exception.InnerExceptions.Contains(TestException)) + { + args.SetObserved(); + _unobservedExceptionRaised.TrySetResult(args.Exception); + } + }; + TaskScheduler.UnobservedTaskException += _handler; + } + + /// + /// Exception that method under test should throw. This exception is checked for in the + /// unobserved exception handler registered at construction. + /// + public ObservableException TestException { get; } + + /// + /// Unregisters unobserved exception handler. + public void Dispose() + { + // Unregister unobserved exception handler + TaskScheduler.UnobservedTaskException -= _handler; + } + + /// + /// Performs a garbage collection, which should trigger the unobserved exception handler that was + /// registered during construction. Waits for up to 50ms for the unobserved exception handler + /// to handle the test exception, then loops again, until has been + /// exceeded. At least one loop will be attempted. + /// + /// + /// Amount of time to wait for our test unobserved exception handler to complete. Use + /// to only perform one loop. + /// + /// + /// Exception raised that included the unobserved task, if handler was invoked before the + /// elapsed. If handler was not invoked before + /// elapsed, will be returned. + /// + public async Task Wait(TimeSpan timeout) + { + DateTime deadline = DateTime.UtcNow + timeout; + do + { + // Force GC collection + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + // Effectively wait up to 50ms before looping and trying to garbage collect again + Task delayTask = Task.Delay(TimeSpan.FromMilliseconds(50)); + Task completedTask = await Task.WhenAny(_unobservedExceptionRaised.Task, delayTask); + if (completedTask == _unobservedExceptionRaised.Task) + { + // Unobserved exception completed before we gave up + return await _unobservedExceptionRaised.Task; + } + } while (DateTime.UtcNow < deadline); + + // Unobserved exception did not complete, so we give up and return null + return null; + } + + /// + /// Test exception type for unique identification of unobserved exceptions. + /// + public class ObservableException : Exception + { + /// + /// Identifier for uniquely identifying the current instance. + /// + public Guid Identifier { get; } = Guid.NewGuid(); + } +} From 9abbd97727980ae727c80a491048c2f6251e5666 Mon Sep 17 00:00:00 2001 From: Apoorv Deshmukh Date: Wed, 29 Jul 2026 08:04:15 +0530 Subject: [PATCH 212/298] Fix #1881 | Make the connection factory pruning timer demand-driven (#4479) --- .../Data/SqlClient/SqlConnectionFactory.cs | 157 +++++++++++++++- .../SqlConnectionFactoryPruningTimerTest.cs | 169 ++++++++++++++++++ 2 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/SqlConnectionFactoryPruningTimerTest.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index 8840864958..db7c6302c7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -45,6 +45,19 @@ internal class SqlConnectionFactory private readonly List _poolGroupsToRelease; private readonly List _poolsToRelease; private readonly Timer _pruningTimer; + private readonly TimeSpan _pruningDueTime; + private readonly TimeSpan _pruningPeriod; + + // Guards every state transition of _pruningTimer. + // + // Lock ordering: _pruningTimerLock is outermost. StopPruningTimerIfIdle takes it and then + // the release-list locks, so neither may be held when arming. It deliberately reads + // _connectionPoolGroups without taking "this" (the dictionary is swapped wholesale, so + // reading the reference is safe), which keeps the lock graph acyclic. + private readonly object _pruningTimerLock = new object(); + private bool _pruningTimerEnabled; + private bool _pruningTimerDisposed; + private Dictionary _connectionPoolGroups; #endregion @@ -52,18 +65,36 @@ internal class SqlConnectionFactory #region Constructors protected SqlConnectionFactory() + : this(PruningDueTime, PruningPeriod) + { + } + + /// + /// Constructs a factory with a custom pruning cadence. Intended for tests, which would + /// otherwise have to wait minutes for the default schedule to produce an observable + /// result. + /// + protected SqlConnectionFactory(TimeSpan pruningDueTime, TimeSpan pruningPeriod) { + _pruningDueTime = pruningDueTime; + _pruningPeriod = pruningPeriod; _connectionPoolGroups = new Dictionary(); _poolsToRelease = new List(); _poolGroupsToRelease = new List(); + + // The timer is created disarmed. It is armed on demand whenever there is something to + // prune and disarmed again by the callback once everything has drained, so that an + // idle process is not woken every pruning period for the life of the process. _pruningTimer = ADP.UnsafeCreateTimer( PruneConnectionPoolGroups, state: null, - PruningDueTime, - PruningPeriod); + Timeout.InfiniteTimeSpan, + Timeout.InfiniteTimeSpan); #if NET SubscribeToAssemblyLoadContextUnload(); + #else + SubscribeToAppDomainUnload(); #endif } @@ -74,6 +105,20 @@ protected SqlConnectionFactory() internal static SqlConnectionFactory Instance { get; } = new SqlConnectionFactory(); internal int ObjectId { get; } = Interlocked.Increment(ref s_objectTypeCount); + + /// + /// Whether the pruning timer is currently armed. Test hook. + /// + internal bool IsPruningTimerActive + { + get + { + lock (_pruningTimerLock) + { + return _pruningTimerEnabled; + } + } + } #endregion @@ -255,6 +300,12 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup( Debug.Assert(connectionPoolGroup != null, "how did we not create a pool entry?"); Debug.Assert(userConnectionOptions != null, "how did we not have user connection options?"); + + // Registering a pool group is the only way pruning work enters the factory: pools + // and pool groups are only ever queued for release while a group is still + // registered, and the timer cannot disarm until every collection is empty. Armed + // outside lock(this) to keep _pruningTimerLock outermost. + EnsurePruningTimerRunning(); } else if (userConnectionOptions is null) { @@ -294,7 +345,7 @@ internal void QueuePoolForRelease(IDbConnectionPool pool, bool clearing) } _poolsToRelease.Add(pool); } - + SqlClientDiagnostics.Metrics.EnterInactiveConnectionPool(); SqlClientDiagnostics.Metrics.ExitActiveConnectionPool(); } @@ -922,7 +973,88 @@ private void PruneConnectionPoolGroups(object state) } _connectionPoolGroups = newConnectionPoolGroups; } + + // Everything above may have drained the last of the pending work. If so, park the + // timer rather than waking the process every pruning period forever. + StopPruningTimerIfIdle(); + } + + /// + /// Arms the pruning timer if it is not already running. + /// + /// + /// The caller must publish the new pool group before calling this, and must not hold + /// lock(this). re-reads all three collections under + /// , so it can never disarm on behalf of work that has + /// already been published. + /// + private void EnsurePruningTimerRunning() + { + lock (_pruningTimerLock) + { + if (_pruningTimerEnabled || _pruningTimerDisposed) + { + return; + } + + _pruningTimerEnabled = true; + _pruningTimer.Change(_pruningDueTime, _pruningPeriod); + + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, pruning timer started", ObjectId); + } } + + /// + /// Disarms the pruning timer if there is nothing left to prune. Called at the tail of + /// . + /// + private void StopPruningTimerIfIdle() + { + lock (_pruningTimerLock) + { + if (!_pruningTimerEnabled || _pruningTimerDisposed || HasPruningWork()) + { + return; + } + + _pruningTimerEnabled = false; + _pruningTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, pruning timer stopped, nothing left to prune", ObjectId); + } + } + + /// + /// Whether any of the three collections walked by + /// still hold work. Must be called while holding . + /// + private bool HasPruningWork() + { + // _connectionPoolGroups is replaced wholesale rather than mutated in place, so reading + // the reference without lock(this) is safe and avoids a lock-ordering inversion. + if (_connectionPoolGroups.Count != 0) + { + return true; + } + + lock (_poolsToRelease) + { + if (_poolsToRelease.Count != 0) + { + return true; + } + } + + lock (_poolGroupsToRelease) + { + return _poolGroupsToRelease.Count != 0; + } + } + + /// + /// Runs a single pruning pass synchronously on the calling thread. Test hook. + /// + internal void RunPruningPass() => PruneConnectionPoolGroups(state: null); private void TryGetConnectionCompletedContinuation(Task task, object state) { @@ -957,12 +1089,20 @@ private void TryGetConnectionCompletedContinuation(Task ta } } - #if NET + /// + /// Disposes the pruning timer and drains the pools when the hosting assembly load context + /// (.NET) or app domain (.NET Framework) is going away. + /// private void Unload(object sender, EventArgs e) { try { - _pruningTimer.Dispose(); + lock (_pruningTimerLock) + { + _pruningTimerDisposed = true; + _pruningTimerEnabled = false; + _pruningTimer.Dispose(); + } } finally { @@ -970,6 +1110,7 @@ private void Unload(object sender, EventArgs e) } } + #if NET private void SqlConnectionFactoryAssemblyLoadContext_Unloading(AssemblyLoadContext obj) { Unload(obj, EventArgs.Empty); @@ -980,6 +1121,12 @@ private void SubscribeToAssemblyLoadContextUnload() AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += SqlConnectionFactoryAssemblyLoadContext_Unloading; } + #else + private void SubscribeToAppDomainUnload() + { + AppDomain.CurrentDomain.DomainUnload += Unload; + AppDomain.CurrentDomain.ProcessExit += Unload; + } #endif #endregion diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlConnectionFactoryPruningTimerTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlConnectionFactoryPruningTimerTest.cs new file mode 100644 index 0000000000..87427e7ed6 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlConnectionFactoryPruningTimerTest.cs @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests +{ + /// + /// Unit tests for the demand-driven pruning timer in . + /// + /// The factory is a process-wide singleton, so before + /// https://github.com/dotnet/SqlClient/issues/1881 its pruning timer was armed in the + /// constructor and never stopped, waking the process every 30 seconds for the lifetime of the + /// process even when there was nothing left to prune. These tests pin the arm/disarm state + /// machine that replaced it. + /// + public class SqlConnectionFactoryPruningTimerTest + { + /// + /// Passes needed to drain a pool group that never had a pool created in it (no test here + /// opens a connection): 1) Active -> Idle, 2) Idle -> Disabled, which returns true in the + /// same pass and queues the group for release, 3) released from _poolGroupsToRelease. + /// + /// Three rather than four because Disabled and queued happen together, and the release + /// loop runs before the pool-group loop within a pass. Deterministic: tests drive + /// synchronously. + /// + private const int ExpectedDrainPasses = 3; + + /// + /// Hang guard, not an expectation: a regression that leaves the timer armed should fail an + /// assertion rather than loop forever. + /// + private const int MaxPruningPasses = 10; + + #region Helpers + + /// + /// A factory whose timer never actually fires on its own, so tests drive pruning + /// deterministically through instead of + /// racing a background thread. + /// + private sealed class TestSqlConnectionFactory : SqlConnectionFactory + { + internal TestSqlConnectionFactory() + : base(TimeSpan.FromDays(1), TimeSpan.FromDays(1)) + { + } + + protected override DbConnectionInternal CreateConnection( + SqlConnectionOptions options, + ConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + TimeoutTimer timeout) + => throw new NotSupportedException("These tests never open a connection."); + } + + private static DbConnectionPoolGroup AddPoolGroup( + SqlConnectionFactory factory, + string connectionString) + { + SqlConnectionOptions userOptions = null!; + var key = new ConnectionPoolKey( + connectionString, + credential: null, + accessToken: null, + accessTokenCallback: null, + sspiContextProvider: null); + + return factory.GetConnectionPoolGroup(key, poolOptions: null, ref userOptions); + } + + /// + /// Runs pruning passes until the timer disarms itself, or until + /// is exhausted. + /// + /// The number of passes executed. + private static int DrainPruningWork(SqlConnectionFactory factory) + { + int passes = 0; + while (factory.IsPruningTimerActive && passes < MaxPruningPasses) + { + factory.RunPruningPass(); + passes++; + } + + return passes; + } + + #endregion + + /// + /// The timer must start disarmed. A process that creates the factory but never opens a + /// connection should not be woken at all. + /// + [Fact] + public void PruningTimer_IsDisarmed_WhenFactoryIsConstructed() + { + var factory = new TestSqlConnectionFactory(); + + Assert.False(factory.IsPruningTimerActive); + } + + /// + /// Registering a pool group creates work for the pruner, so the timer must arm. + /// + [Fact] + public void PruningTimer_IsArmed_WhenPoolGroupIsRegistered() + { + var factory = new TestSqlConnectionFactory(); + + Assert.NotNull(AddPoolGroup(factory, "Data Source=localhost;")); + + Assert.True(factory.IsPruningTimerActive); + } + + /// + /// The regression test for #1881: the timer must stay armed for every pass of the drain so + /// pruning cannot stall half-drained, then disarm once there is nothing left to prune + /// instead of firing forever with nothing to do. + /// + [Fact] + public void PruningTimer_StaysArmedUntilAllWorkDrains() + { + var factory = new TestSqlConnectionFactory(); + AddPoolGroup(factory, "Data Source=localhost;"); + Assert.True(factory.IsPruningTimerActive); + + for (int pass = 1; pass < ExpectedDrainPasses; pass++) + { + factory.RunPruningPass(); + + Assert.True( + factory.IsPruningTimerActive, + $"Timer disarmed after pass {pass}, but the pool group had not drained yet."); + } + + factory.RunPruningPass(); + + Assert.False(factory.IsPruningTimerActive); + } + + /// + /// Disarming must not be a one-way door: new connection activity has to bring pruning + /// back, otherwise the fix would simply have disabled pruning. + /// + [Fact] + public void PruningTimer_IsRearmed_WhenNewPoolGroupIsRegisteredAfterDraining() + { + var factory = new TestSqlConnectionFactory(); + AddPoolGroup(factory, "Data Source=localhost;"); + int passes = DrainPruningWork(factory); + Assert.False(factory.IsPruningTimerActive); + Assert.Equal(ExpectedDrainPasses, passes); + + AddPoolGroup(factory, "Data Source=otherhost;"); + + Assert.True(factory.IsPruningTimerActive); + } + } +} From bf3bd941e88ed9cfda3155bb1ed76201ce7eb7f5 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:16:41 -0300 Subject: [PATCH 213/298] Quarantine flaky pipeline tests; add triage prompt (#4476) Signed-off-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> --- .../triage-pipeline-failures.prompt.md | 185 ++++++++++++++++++ .../ConnectionPoolTest/TransactionPoolTest.cs | 13 ++ .../ChannelDbConnectionPoolTest.cs | 33 ++++ .../ChannelDbConnectionPoolWarmupTest.cs | 15 ++ ...andleDbConnectionPoolBlockingPeriodTest.cs | 25 +++ ...itHandleDbConnectionPoolTransactionTest.cs | 19 ++ .../ConnectionFailoverTests.cs | 37 ++++ .../SimulatedServerTests/ConnectionTests.cs | 39 ++++ 8 files changed, 366 insertions(+) create mode 100644 .github/prompts/triage-pipeline-failures.prompt.md diff --git a/.github/prompts/triage-pipeline-failures.prompt.md b/.github/prompts/triage-pipeline-failures.prompt.md new file mode 100644 index 0000000000..c4ff676d17 --- /dev/null +++ b/.github/prompts/triage-pipeline-failures.prompt.md @@ -0,0 +1,185 @@ +--- +name: triage-pipeline-failures +description: Find and classify failing tests in the CI/CD pipelines at or after a given commit, then fix or quarantine them. +argument-hint: [optional scope, e.g. specific pipelines/branches] +agent: agent +# No `tools:` scoping on purpose: this prompt is access-agnostic and must be able +# to call whatever Azure DevOps MCP server is connected (e.g. `ado/*`) in addition +# to the built-in terminal/read/search/edit tools. Declaring a scoped `tools:` list +# would strip out MCP/extension tools and break the preferred ADO MCP access path. +--- + +Triage failing tests in the CI/CD pipelines for commit +`${input:commit}` and later. Treat only the **first whitespace-delimited token** of +`${input:commit}` as the target commit SHA — that token is what every git ancestry +check (`git merge-base --is-ancestor ...`) uses. Any remaining text is +**optional scope** (e.g. a pipeline name or branch): honor it when present, otherwise +use the defaults below. + +## Azure DevOps access is agnostic + +Every data-retrieval step below is described as an **operation**, not a command. +Perform each operation with whatever Azure DevOps access is available, in this order +of preference: + +1. An **Azure DevOps MCP server**, if one is connected (preferred — no shell needed). +2. The **`az` CLI** (`az rest --resource ...`, + `az pipelines ...`, `az boards ...`). +3. **Direct ADO REST** calls over HTTPS with a bearer token. + +Do not assume a specific mechanism. If the first choice is unavailable or errors, +fall back to the next. Keep read operations read-only; only edits to test source +files (quarantine/fix) modify state, and those happen in the repo, not in ADO. + +## Environment + +- **ADO organization**: ``. +- **Projects**: + - `` — CI/PR pipelines target the upstream GitHub repo. + - `` — CI/OneBranch pipelines target the ADO mirror repo. +- The ADO mirror repo preserves the **same commit SHAs** as GitHub, so git + ancestry against a GitHub SHA works. It **lags** GitHub because synchronization is + gated by PRs: a commit only appears in the mirror once its sync PR completes, so a + target commit may not be present in the mirror yet even though it is on GitHub. + +## Step 1 — Scope to the right pipelines + +**Operation:** list build definitions in both `public` and `ADO.Net`, with each +definition's folder path, `queueStatus`, and `repository.type` / `repository.name`. + +Ignore any definition that is **not currently enabled** (`queueStatus != enabled`, +i.e. disabled or paused) — also skip names flagged `[Disabled]`, `[Retired]`, or under +`\Retired\` folders. Then keep only definitions whose repo is the upstream GitHub repo +or the ADO mirror repo. Exclude legacy driver repos and native SNI repos unless the +user asks for SNI. + +Because this triage targets **non-PR commit runs** (see Step 2), prefer CI/branch +definitions over PR-validation ones. Prefer CI, package, stress, Kerberos, +Managed-Instance, and OneBranch official/non-official definitions across both projects. +PR-triggered definitions are in scope only for the CI/branch runs they may also host — +their PR-ref runs are excluded in Step 2 unless the user asks to include PR runs. + +## Step 2 — Find runs at/after the target commit + +**Operation:** for each in-scope definition, list recent runs (filter to +`failed`, `partiallySucceeded`, `canceled`) with their `sourceBranch`, +`sourceVersion`, `result`, and `finishTime`. + +**Limit to non-PR commit runs.** Only consider runs triggered by real commits on +tracked branches (e.g. `refs/heads/main`, `refs/heads/release/*`); **exclude PR +validation runs**. A run is a PR run — and therefore out of scope — when any of these +hold: + +- Its `sourceBranch` is an ephemeral merge ref such as `refs/pull/N/merge` or + `refs/pull/N/head`. +- Its build `reason` is `pullRequest`. +- It is a PR-triggered definition running against a PR ref. + +Keep only runs whose `sourceVersion` is a committed SHA on a tracked branch. If the +user explicitly asks to include PR runs, honor that override. + +Resolve **"at or after `${input:commit}`" by commit graph, not timestamp**: + +- `git merge-base --is-ancestor ` → true means the run's + commit is the target or a descendant (in scope). +- `sourceVersion == ` → the target itself (in scope). +- Divergent `release/*` or `dev/*` commits do **not** descend from a `main` target — + exclude them. + +Mirror runs use the **same SHAs** as GitHub, so apply the same +ancestry checks. Because mirror sync is PR-gated, the target commit may not have +reached the mirror yet — in that window there simply are no in-scope mirror runs, so +do not infer a run is out of scope from a SHA mismatch (there is none); it is only a +timing lag. + +## Step 3 — Enumerate failing test runs per build + +**Operation:** for each in-scope build, list its test runs. + +Compute real failures as `totalTests - passedTests - notApplicableTests`. Do **not** +treat `unanalyzedTests`/`notApplicableTests` as failures. Keep runs with failures > 0. + +## Step 4 — Get failing test names and errors + +**Operation:** for each failing test run, fetch the `Failed` results with their +`automatedTestName`, `errorMessage`, and `stackTrace`. + +**You must capture the actual xUnit output and full stack trace for every failed +test — do not classify a failure without it.** The one-line `errorMessage` is not +enough; get the complete assertion text (e.g. `Assert.Equal() Failure: Values differ / +Expected / Actual`) and the full stack frames (the test method and the failing product +frames). If any source truncates it, cross-check another until you have the whole thing: + +- The result's `errorMessage` + `stackTrace` fields (expand sub-results — see below). +- The test run's **attachments** (TRX / `*.trx`, console logs) when the API truncates + long stacks. +- The **job log** for the test step (Step 5) — the raw `dotnet test` / xUnit output + always contains the assertion and stack, even when the results API does not. + +**CRITICAL — data-driven (Theory) results hide the error on a child:** xUnit +`[Theory]`/`[ClassData]`/`[InlineData]` tests publish as a parent result with +`resultGroupType == "dataDriven"` whose own `errorMessage`/`stackTrace` are **null**. +The real assertion lives on the failing **sub-result**. When a `Failed` result has a +null error, re-fetch that result **including sub-results** and read the child. A null +parent error means "look at the children", not "the test aborted". Only treat it as an +abort when the build log also shows no assertion and the process was terminated +(e.g. a `--blame-hang` dump). + +## Step 5 — Locate each failure's job (for logs/links) + +**Operation:** for a failing run, read its `pipelineReference` (stage/phase/job), then +read the build's timeline and walk Stage → Phase → Job by `parentId` to get the job +record id. Build deep links: + +- Tests tab: `.../_build/results?buildId=&view=ms.vss-test-web.build-test-results-tab` +- A specific result: append `&runId=&resultId=&paneView=debug` +- Job logs: `.../_build/results?buildId=&view=logs&j=` + +## Step 6 — Classify every failure + +| Class | Signals | Action | +|-------|---------|--------| +| **True positive** (broken driver) | Deterministic; fails on every leg for the commit; assertion tied to changed code; absent on the parent commit | Fix the bug; keep/add a failing test | +| **Test-isolation / concurrency** | Off-by-a-small-count on a process-global resource (e.g. pool `ConnectionCount` Expected 2 Actual 3); some data rows pass, others fail; depends on parallel tests | Isolate the resource (unique connection string, `[Collection]`); else quarantine | +| **Flaky (timing/GC/load)** | Intermittent; only under CI load; GC-finalizer or retry/failover timing; "connection is broken" under contention | Deterministic fix (poll not sleep, set retry interval/timeouts); else quarantine | +| **Environmental / infra** | Empty error AND no assertion in the log; host/agent crash; blame-hang dump; network/DTC outage; many unrelated tests fail at once | Re-run to confirm; report infra; don't quarantine on a single infra hit | + +Determine **regression vs pre-existing** by repeating Steps 3–4 on the +immediately-preceding in-scope build (the parent commit). A failure present before +`${input:commit}` was not introduced by it. + +## Step 7 — Check quarantine status before acting + +A test is already quarantined if it carries `[Trait("Category", "flaky")]`; those run +in a separate, non-blocking quarantine step (`TestFilters="category=flaky"`) while the +regular step excludes `category!=failing&category!=flaky&category!=interactive`. + +- Already-quarantined failure = expected quarantine noise, not a blocker. Only escalate + with a real fix. +- Non-quarantined failure in a regular step = a real blocker. + +## Step 8 — Present findings and STOP (checkpoint) + +Steps 1–7 are **read-only investigation**. Before changing anything, present your +findings and wait for the user's explicit go-ahead. Do **not** edit any files or take +any action until the user approves. + +Present a per-failure table: test name, in-scope build(s), full xUnit assertion + +key stack frames, classification, whether it is a regression, current quarantine +status, and the **proposed** action (fix / quarantine / already quarantined / +re-run to confirm). Link each failure to its build/result and job. Then explicitly ask +the user which items to act on. + +## Step 9 — Act (only after approval) + +For each item the user approves: + +1. Prefer a **deterministic fix** that removes the race/isolation/timing dependency. +2. Otherwise **quarantine**: add `[Trait("Category", "flaky")]` plus a comment holding + the observed failure signature (test name, assertion, key stack frames) and the + root-cause reasoning. Mirror the style of existing quarantine comments in the test suite. +3. Cover both sync and async variants when the API has both. +4. Un-quarantine once fixed and consistently green. + +Make only the source edits needed to fix or quarantine; do not modify pipeline YAML or +ADO state. After editing, report what changed. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/TransactionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/TransactionPoolTest.cs index 96432c8bd6..381d35bf02 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/TransactionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/TransactionPoolTest.cs @@ -74,6 +74,19 @@ public static void BasicTransactionPoolTest(string connectionString) /// Synapse: only supports local transaction request. /// /// + // + // Flaky under CI load only: the connection pool intermittently reports one more + // connection than expected because the process-global pool (keyed by connection string) + // is contaminated by a connection opened elsewhere while this test runs, i.e. it is a + // test-isolation / pool-count race, not a product defect. It cannot be made deterministic + // without isolating the shared pool, so it is quarantined until that isolation is added. + // + // Failed Microsoft.Data.SqlClient.ManualTesting.Tests.TransactionPoolTest.TransactionCleanupTest(connectionString: "Data Source=tcp:localhost;Initial Catalog=Northwin"...) [2 s] + // Assert.Equal() Failure: Values differ + // Expected: 2 + // Actual: 3 + // at Microsoft.Data.SqlClient.ManualTesting.Tests.TransactionPoolTest.TransactionCleanupTest(String connectionString) in TransactionPoolTest.cs:line 101 + [Trait("Category", "flaky")] [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] [ClassData(typeof(ConnectionPoolConnectionStringProvider))] public static void TransactionCleanupTest(string connectionString) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs index 9d24200b17..3592a3e54b 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -1618,6 +1618,11 @@ public void Constructor_WithValidSmallPoolSizes_WorksCorrectly() /// Default/Auto enable blocking for a non-Azure host (localhost), AlwaysBlock forces it on, /// and NeverBlock suppresses it. FR-006, FR-007. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; fails on PR merge + // builds across multiple jobs with Assert Expected:True/Actual:False in <2ms): the + // assertion races the background warmup/replenishment work added in #4452 before the + // pool's error-state transition is observable. Not a defect in this PR. + [Trait("Category", "flaky")] [Theory] [InlineData("", true)] // Default (unspecified) => Auto => blocks for localhost [InlineData("Pool Blocking Period=Auto;", true)] // Auto => blocks for non-Azure host @@ -1653,6 +1658,10 @@ public void ErrorOccurred_OnFailure_FollowsBlockingPeriod(string blockingPeriodC /// Verifies that once the pool enters the blocking period, subsequent synchronous requests /// fail fast with the cached exception without attempting another physical open. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; fails on PR merge + // builds): races the background warmup/replenishment work added in #4452 before the + // pool's error-state transition is observable. Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public void ErrorOccurred_BlockingEnabled_SubsequentRequestFastFails() { @@ -1693,6 +1702,10 @@ public void ErrorOccurred_BlockingEnabled_SubsequentRequestFastFails() /// Verifies that clearing the pool while in the blocking-period error state resets the /// externally visible error indicator. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; fails on PR merge + // builds): races the background warmup/replenishment work added in #4452 before the + // pool's error-state transition is observable. Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public void Clear_InErrorState_ResetsErrorOccurred() { @@ -1729,6 +1742,10 @@ public void Clear_InErrorState_ResetsErrorOccurred() /// so the test is deterministic and does not wait on /// wall-clock time. FR-006, FR-009. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; fails on PR merge + // builds): races the background warmup/replenishment work added in #4452 before the + // pool's error-state transition is observable. Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public void Failure_ThenBlockingPeriodExpiry_AllowsSuccessfulCreate() { @@ -2239,6 +2256,22 @@ protected override DbConnectionInternal CreateConnection( /// advancing virtual time deterministically expires only the short-timeout /// caller's CTS without consuming any wall-clock time. /// + // + // Flaky/hang-prone under CI load: caller A runs TryGetConnection on a background + // task that blocks on the pool's channel wait. The test then calls + // fakeTime.Advance(2s) to fire A's timeout CTS. If Advance runs before A has + // subscribed its wait to the fake time provider, A's cancellation is missed and A + // blocks forever, so `await callerATask` never returns — the testhost sits idle + // until the 10-minute --blame-hang timeout aborts the whole run. This is a + // virtual-time-vs-subscription race in the test, not a pool defect; it cannot be + // made deterministic without a barrier guaranteeing A is parked before Advance. + // + // The active test run was aborted. Reason: Test host process crashed + // Data collector 'Blame' message: The specified inactivity time of 10 minutes has elapsed. Collecting hang dumps from testhost and its child processes. + // The test running when the crash occurred: + // Microsoft.Data.SqlClient.UnitTests.ConnectionPool.ChannelDbConnectionPoolTest.ConcurrentCallers_ShouldTimeoutIndependently + // testhost_7576_20260724T235829_hangdump.dmp + [Trait("Category", "flaky")] [Fact] public async Task ConcurrentCallers_ShouldTimeoutIndependently() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolWarmupTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolWarmupTest.cs index d4c29d0459..7d2ad7347a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolWarmupTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/ChannelDbConnectionPoolWarmupTest.cs @@ -334,6 +334,11 @@ public async Task Warmup_RateLimiterSaturated_UserSharesSameLimiter() /// does enter the pool's blocking-period error state. Warmup then stops the pass rather than /// spinning on the persistent failure. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; fails on PR merge + // builds): the background warmup thread added in #4452 races the assertion so the + // pool's error-state transition is observed late (Assert Expected:True/Actual:False). + // Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public async Task Warmup_AllCreationsFail_AbsorbedAndEntersErrorState() { @@ -365,6 +370,11 @@ public async Task Warmup_AllCreationsFail_AbsorbedAndEntersErrorState() /// exception rather than attempting a fresh on-demand open. The pool remains operational and /// resumes creating on demand once the blocking period expires. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; fails on PR merge + // builds): the background warmup thread added in #4452 races the assertion so the + // pool's error-state transition is observed late (Assert Expected:True/Actual:False). + // Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public async Task Warmup_Fails_UserRequestFastFailsDuringBlockingPeriod() { @@ -390,6 +400,11 @@ public async Task Warmup_Fails_UserRequestFastFailsDuringBlockingPeriod() /// request (rather than warmup itself) keeps the two behaviors independent, and awaiting the /// warmup task makes the stand-down deterministic. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; fails on PR merge + // builds): the background warmup thread added in #4452 races the assertion so the + // pool's error-state transition is observed late (Assert Expected:True/Actual:False). + // Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public async Task Warmup_RespectsErrorState_StandsDownWhileBlocking() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBlockingPeriodTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBlockingPeriodTest.cs index e84f040caa..0197d62292 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBlockingPeriodTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolBlockingPeriodTest.cs @@ -101,6 +101,11 @@ private static bool TryGetConnectionSync( /// the originating exception is surfaced to the caller and /// becomes true. Guards the CreateObject → wiring. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; failed on a PR merge + // build): the WaitHandle pool's background create/prune thread races the ErrorOccurred + // assertion under load, and this file shares the DbConnectionInternal/SqlConnectionFactory + // changes from #4452. Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public void TryGetConnection_WhenFactoryThrows_EntersBlockingPeriod() { @@ -126,6 +131,11 @@ public void TryGetConnection_WhenFactoryThrows_EntersBlockingPeriod() /// rethrows the original instance; the fast-fail throw returns a clone (to avoid sharing stack /// traces). Guards the error wait-handle → path. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; failed on a PR merge + // build): the WaitHandle pool's background create/prune thread races the assertion under + // load, and this file shares the DbConnectionInternal/SqlConnectionFactory changes from + // #4452. Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public void TryGetConnection_WhileBlocked_FastFailsWithCachedExceptionWithoutInvokingFactory() { @@ -207,6 +217,11 @@ public void TryGetConnection_WhenFactorySucceeds_DoesNotEnterBlockingPeriod() /// an injected , guarding the /// timer-exit → retry → Clear path at the pool level. /// + // Flaky under CI load only (passes locally 3/3 and on main CI; failed on a PR merge + // build): the WaitHandle pool's background create/prune thread races the ErrorOccurred/timer + // assertions under load, and this file shares the DbConnectionInternal/SqlConnectionFactory + // changes from #4452. Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public void TryGetConnection_AfterBlockingPeriodExpires_RetriesFactoryAndRecovers() { @@ -248,6 +263,11 @@ public void TryGetConnection_AfterBlockingPeriodExpires_RetriesFactoryAndRecover /// reset is wired through the pool. Drives timing deterministically with an injected /// . /// + // Flaky under CI load only (passes locally 3/3 and on main CI; failed on a PR merge + // build): the WaitHandle pool's background create/prune thread races the ErrorOccurred/timer + // assertions under load, and this file shares the DbConnectionInternal/SqlConnectionFactory + // changes from #4452. Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public void TryGetConnection_SuccessfulCreate_ResetsBackoffToInitialWait() { @@ -300,6 +320,11 @@ public void TryGetConnection_SuccessfulCreate_ResetsBackoffToInitialWait() /// backoff at the pool level. Drives timing deterministically with an injected /// . /// + // Flaky under CI load only (passes locally 3/3 and on main CI; failed on a PR merge + // build): the WaitHandle pool's background create/prune thread races the ErrorOccurred/timer + // assertions under load, and this file shares the DbConnectionInternal/SqlConnectionFactory + // changes from #4452. Not a defect in this PR. + [Trait("Category", "flaky")] [Fact] public void TryGetConnection_FailingAgainAfterExitTimer_StillDoublesBackoff() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs index 712708375a..5230f9e22e 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/ConnectionPool/WaitHandleDbConnectionPoolTransactionTest.cs @@ -633,6 +633,25 @@ public void PoolSaturation_BlocksUntilConnectionAvailable() #region Controlled Concurrency Tests + // Flaky under CI load only (never reproduces locally): the two worker tasks are + // scheduled via Task.Run on the thread pool. On a loaded x86 agent the pool can be + // slow to spin up a worker, so task1 starts late and fails to signal task1Returned + // within task2's 10s wait, producing a WaitAll timeout. task1 itself throws no + // assertion (none appears in the AggregateException) — this is thread-pool + // starvation, not a pool/transaction defect. + // + // [xUnit.net 00:00:14.24] Microsoft.Data.SqlClient.UnitTests.ConnectionPool.WaitHandleDbConnectionPoolTransactionTest.TwoThreads_SharedTransaction_AccessSameTransactedEntry [FAIL] + // Failed Microsoft.Data.SqlClient.UnitTests.ConnectionPool.WaitHandleDbConnectionPoolTransactionTest.TwoThreads_SharedTransaction_AccessSameTransactedEntry [10 s] + // System.AggregateException : One or more errors occurred. (Timed out waiting for task1 to return its connection.) + // ---- Timed out waiting for task1 to return its connection. + // Stack Trace: + // at System.Threading.Tasks.Task.WaitAllCore(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken) + // at System.Threading.Tasks.Task.WaitAll(Task[] tasks) + // at Microsoft.Data.SqlClient.UnitTests.ConnectionPool.WaitHandleDbConnectionPoolTransactionTest.TwoThreads_SharedTransaction_AccessSameTransactedEntry() in WaitHandleDbConnectionPoolTransactionTest.cs:line 681 + // ----- Inner Stack Trace ----- + // at Microsoft.Data.SqlClient.UnitTests.ConnectionPool.WaitHandleDbConnectionPoolTransactionTest.<>c__DisplayClass29_0.b__1() in WaitHandleDbConnectionPoolTransactionTest.cs:line 670 + // at System.Threading.Tasks.Task.InnerInvoke() + [Trait("Category", "flaky")] [Fact] public void TwoThreads_SharedTransaction_AccessSameTransactedEntry() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index 067ffe3072..23a77ef45d 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -263,6 +263,43 @@ public void NetworkDelay_ShouldConnectToPrimary() Assert.Equal(0, failoverServer.PreLoginCount); } + // Flaky under CI load only (never reproduces locally): the tight 5-second + // ConnectTimeout can be exhausted by the legitimate failover connection itself + // when the agent is slow (observed pre-login handshake ~3.4s + post-login ~5.4s), + // producing a Connection Timeout on the failover attempt rather than a clean + // failover. This is agent-timing sensitivity, not a driver defect. + // + // [xUnit.net 00:00:16.50] Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFailoverPartner [FAIL] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFailoverPartner [5 s] + // Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. The connection could have timed out while waiting for server to complete the login process and respond; Or it could have timed out while attempting to create multiple active connections. This failure occurred while attempting to connect to the Principle server. The duration spent while attempting to connect to this server was - [Pre-Login] initialization=5; handshake=3398; [Login] initialization=0; authentication=0; [Post-Login] complete=5385; + // ---- System.ComponentModel.Win32Exception : The wait operation timed out. + // Stack Trace: + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) + // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) + // at Microsoft.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose) + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket() + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer() + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value) + // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) + // at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginWithFailover(Boolean useFailoverHost, ServerInfo primaryServerInfo, String failoverHost, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionOptions connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal..ctor(...) + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(SqlConnectionOptions options, ConnectionPoolKey poolKey, DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, IDbConnectionPool pool, DbConnection owningConnection, TimeoutTimer timeout) + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, TimeoutTimer timeout) + // at Microsoft.Data.SqlClient.SqlConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionInternal oldConnection, TimeoutTimer timeout, Boolean forceNewConnection, DbConnectionInternal& connection) + // at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, Boolean forceNewConnection, TimeoutTimer timeout) + // at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, SqlConnectionFactory connectionFactory, TaskCompletionSource`1 retry, TimeoutTimer timeout) + // at Microsoft.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry, Boolean forceNewConnection) + // at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, Boolean forceNewConnection, SqlConnectionOverrides overrides) + // at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides) + // at Microsoft.Data.SqlClient.SqlConnection.Open() + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionFailoverTests.NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFailoverPartner() in ConnectionFailoverTests.cs:line 304 + [Trait("Category", "flaky")] [Fact] public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFailoverPartner() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index 8a848c365c..a082c8c7e6 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -81,6 +81,17 @@ public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() } + // Flaky under CI load only (never reproduces locally): the simulated transient error + // occasionally surfaces on the retry login as well, so the async open propagates the + // SqlException instead of succeeding. This is the transient-fault retry timing behavior + // this test guards, not a harness race, so it cannot be made deterministic here. + // + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.TransientFault_RetryEnabled_ShouldSucceed_Async(errorCode: 40613) + // Microsoft.Data.SqlClient.SqlException : + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(...) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(...) + [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -192,6 +203,34 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } + // Flaky under CI load only (never reproduces locally): the retry login can exhaust + // the connect-timeout budget on a slow agent and surface a post-login Connection + // Timeout (observed pre-login handshake ~4.4s), so the async open propagates a + // SqlException instead of succeeding. Same CI-load post-login timing family as the + // already-quarantined sibling TransientFault_RetryEnabled_ShouldSucceed_Async; not a + // driver defect. + // + // [xUnit.net 00:00:11.76] Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async(multiSubnetFailoverEnabled: True) [FAIL] + // Failed Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async(multiSubnetFailoverEnabled: True) [5 s] + // Microsoft.Data.SqlClient.SqlException : Connection Timeout Expired. The timeout period elapsed during the post-login phase. ... The duration spent while attempting to connect to this server was - [Pre-Login] initialization=5; handshake=4393; [Login] initialization=0; authentication=0; [Post-Login] complete=1014; + // ---- System.ComponentModel.Win32Exception : The wait operation timed out. + // Stack Trace: + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) + // at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose) + // at Microsoft.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose) + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) + // at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket() + // at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value) + // at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.CompleteLogin(Boolean enlistOK) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionOptions connectionOptions, SqlCredential credential, TimeoutTimer timeout) + // at Microsoft.Data.SqlClient.Connection.SqlConnectionInternal.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionOptions connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance) + // at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, TimeoutTimer timeout) + // at Microsoft.Data.SqlClient.SqlConnectionFactory.<>c__DisplayClass41_0.b__0(Task`1 _) + // at Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests.ConnectionTests.NetworkError_RetryEnabled_ShouldSucceed_Async(Boolean multiSubnetFailoverEnabled) + [Trait("Category", "flaky")] [Theory] [InlineData(false)] [InlineData(true)] From 45f47599cd3a5ec268dcb264fcc590a00888bf65 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:06:17 +0100 Subject: [PATCH 214/298] Fix | Enable transport of large decimal values with explicit Precision and Scale (#4443) --- .../src/Microsoft/Data/Common/AdapterUtil.cs | 3 -- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 42 +++++++++---------- .../ManualTests/AlwaysEncrypted/ApiShould.cs | 4 +- .../Setup/SqlParameterPropertiesTable.cs | 4 +- .../SQL/ParameterTest/ParametersTest.cs | 32 +++++++++++++- 5 files changed, 54 insertions(+), 31 deletions(-) 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 22aa0360ec..b4a7eb54df 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -1396,9 +1396,6 @@ internal static ArgumentOutOfRangeException InvalidParameterDirection(ParameterD internal static ArgumentException InvalidSizeValue(int value) => Argument(StringsHelper.GetString(Strings.ADP_InvalidSizeValue, value.ToString(CultureInfo.InvariantCulture))); - internal static ArgumentException ParameterValueOutOfRange(decimal value) - => ADP.Argument(StringsHelper.GetString(Strings.ADP_ParameterValueOutOfRange, value.ToString((IFormatProvider)null))); - internal static ArgumentException ParameterValueOutOfRange(SqlDecimal value) => ADP.Argument(StringsHelper.GetString(Strings.ADP_ParameterValueOutOfRange, value.ToString())); internal static ArgumentException ParameterValueOutOfRange(string value) => ADP.Argument(StringsHelper.GetString(Strings.ADP_ParameterValueOutOfRange, value)); 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 1a42894779..8a4186e299 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -8260,7 +8260,7 @@ internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale) return d; } - internal static decimal AdjustDecimalScale(decimal value, int newScale) + internal static SqlDecimal AdjustDecimalScale(decimal value, int newScale) { #if NET Span decimalBits = stackalloc int[4]; @@ -8269,16 +8269,15 @@ internal static decimal AdjustDecimalScale(decimal value, int newScale) int[] decimalBits = decimal.GetBits(value); #endif int oldScale = (decimalBits[3] & 0x00ff0000) >> 0x10; + SqlDecimal num = new SqlDecimal(value); if (newScale != oldScale) { bool round = !LocalAppContextSwitches.TruncateScaledDecimal; - SqlDecimal num = new SqlDecimal(value); num = SqlDecimal.AdjustScale(num, newScale - oldScale, round); - return num.Value; } - return value; + return num; } internal byte[] SerializeSqlDecimal(SqlDecimal d, TdsParserStateObject stateObj) @@ -10411,34 +10410,31 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet // bug 49512, make sure the value matches the scale the user enters if (!isNull) { + SqlDecimal adjustedValue; + if (isSqlVal) { - 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); - } - } + adjustedValue = AdjustSqlDecimalScale((SqlDecimal)value, scale); } else { - value = AdjustDecimalScale((Decimal)value, scale); - - SqlDecimal sqlValue = new SqlDecimal((Decimal)value); + // If we encounter a System.Decimal at this point, we always convert it to + // a SqlDecimal. It's necessary in order to adjust the scale and to be able + // to transport the full range of numeric(38,X) values without overflowing. + adjustedValue = AdjustDecimalScale((decimal)value, scale); + isSqlVal = true; + } - // If Precision is specified, verify value precision vs param precision - if (precision != 0) + // If Precision is specified, verify value precision vs param precision + if (precision != 0) + { + if (precision < adjustedValue.Precision) { - if (precision < sqlValue.Precision) - { - throw ADP.ParameterValueOutOfRange((Decimal)value); - } + throw ADP.ParameterValueOutOfRange(adjustedValue); } } + + value = adjustedValue; } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index 556eaf04c2..8ecb3c840c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -175,7 +175,7 @@ public void SqlParameterProperties(string connection) string inputProcedureName = DataTestUtility.GetShortName("InputProc").ToString(); string outputProcedureName = DataTestUtility.GetShortName("OutputProc").ToString(); const int charColumnSize = 100; - const int decimalColumnPrecision = 10; + const int decimalColumnPrecision = 38; const int decimalColumnScale = 4; const int timeColumnScale = 5; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/SqlParameterPropertiesTable.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/SqlParameterPropertiesTable.cs index 90e5371cd0..dd2c44d5e8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/SqlParameterPropertiesTable.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/SqlParameterPropertiesTable.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -16,7 +16,7 @@ public override void Create(SqlConnection sqlConnection) $@"CREATE TABLE [dbo].[{Name}] ( [firstColumn] [nchar](100) NULL, - [secondColumn] [decimal](10, 4) NULL, + [secondColumn] [decimal](38, 4) NULL, [thirdColumn] [time](5) NULL )"; 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 d2cafd3c76..01bbf586fc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -532,6 +532,36 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr Assert.True(ValidateInsertedValues(connection, decimalTable.Name, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static void TestOutOfRangeDecimalParameter_CommandSelect() + { + using SqlConnection connection = new(DataTestUtility.TCPConnectionString); + connection.Open(); + + using SqlCommand cmd = new("SELECT @Value", connection); + // A System.Decimal value has a maximum precision of 29 digits. We specify a Precision of 38 and a Scale of 2 in order + // to prove that the client can change the scale and precision of the decimal value in ways which System.Decimal doesn't + // intrinsically support. + SqlParameter p = new("@Value", decimal.MaxValue) + { + Precision = 38, + Scale = 2 + }; + + cmd.Parameters.Add(p); + + using SqlDataReader reader = cmd.ExecuteReader(); + + reader.Read(); + // Read the original value back as a SqlDecimal, with matching scale and precision. + SqlDecimal roundtrippedDecimal = reader.GetSqlDecimal(0); + + Assert.Equal(38, roundtrippedDecimal.Precision); + Assert.Equal(2, roundtrippedDecimal.Scale); + Assert.Throws(() => roundtrippedDecimal.Value); + Assert.Equal($"{decimal.MaxValue}.00", roundtrippedDecimal.ToString()); + } + // Enumeration is disabled to prevent generating empty test set when connection strings are not setup. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(TestScaledDecimalParameter_Data), DisableDiscoveryEnumeration = true)] From 25dba6ba7ed7a8cdda68820bf57712267428f97c Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:21:56 -0300 Subject: [PATCH 215/298] AE tests: create ConversionTests keys once per class to slow error 3807 identifier exhaustion (#4478) --- .../AlwaysEncrypted/ConversionTests.cs | 42 ++------ .../TestFixtures/ConversionTestFixture.cs | 95 +++++++++++++++++++ 2 files changed, 103 insertions(+), 34 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/ConversionTestFixture.cs diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs index 5574b83c45..5e122b1ab7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs @@ -11,15 +11,13 @@ using System.Linq; using System.Reflection; using System.Text; -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 { [Trait("Set", "AE")] - public sealed class ConversionTests : IDisposable, IClassFixture + public sealed class ConversionTests : IDisposable, IClassFixture { private const string IdentityColumnName = "IdentityColumn"; @@ -30,9 +28,7 @@ public sealed class ConversionTests : IDisposable, IClassFixture _databaseObjects = new List(); + private readonly ColumnEncryptionKey columnEncryptionKey; private List _tables = new(); private class ColumnMetaData @@ -53,33 +49,12 @@ public ColumnMetaData(SqlDbType columnType, int columnSize, int precision, int s public bool UseMax { get; set; } } - public ConversionTests(ColumnMasterKeyCertificateFixture fixture) + public ConversionTests(ConversionTestFixture fixture) { - 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) - { - var connectionString = new SqlConnectionStringBuilder(connectionStr); - connectionString.ConnectTimeout = Math.Max(connectionString.ConnectTimeout, 30); // The AE tests often fail with a connect timeout in this constructor. Making sure we have a reasonable timeout. - - using (SqlConnection sqlConnection = new SqlConnection(connectionString.ConnectionString)) - { - sqlConnection.Open(); - _databaseObjects.ForEach(o => o.Create(sqlConnection)); - } - } + // The Column Master Key and Column Encryption Key are created once per test class by + // the fixture, rather than once per test case, to avoid rapidly exhausting SQL Server's + // per-database identifier allocator (error 3807) on long-lived shared databases. + columnEncryptionKey = fixture.ColumnEncryptionKey; } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] @@ -1355,7 +1330,7 @@ private void SetParamSizeScalePrecision(ref SqlParameter param, ColumnMetaData c public void Dispose() { - _databaseObjects.Reverse(); + // Only the per-test tables are dropped here; the CMK/CEK are owned by the class fixture. foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) { using (SqlConnection sqlConnection = new SqlConnection(connectionStr)) @@ -1365,7 +1340,6 @@ public void Dispose() { DropTableIfExists(sqlConnection, table); } - _databaseObjects.ForEach(o => o.Drop(sqlConnection)); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/ConversionTestFixture.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/ConversionTestFixture.cs new file mode 100644 index 0000000000..abae589981 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/ConversionTestFixture.cs @@ -0,0 +1,95 @@ +// 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.ManualTesting.Tests.AlwaysEncrypted.Setup; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted +{ + /// + /// Class fixture for . Creates a single Column Master Key (CMK) and + /// Column Encryption Key (CEK) once for the whole test class instead of once per test case. + /// + /// + /// xUnit constructs a test class instance for every test case, so creating the keys in the + /// constructor issued a CREATE COLUMN MASTER KEY and + /// CREATE COLUMN ENCRYPTION KEY for each of the dozens of cases in the class. On a + /// long-lived shared database that rapidly advances SQL Server's internal per-database + /// identifier allocator, which is never reclaimed on DROP, eventually causing error 3807 + /// ("Create failed because all available identifiers have been exhausted"). Creating the keys + /// once per class via reduces that churn by roughly + /// two orders of magnitude. + /// + public sealed class ConversionTestFixture : ColumnMasterKeyCertificateFixture + { + private readonly ColumnMasterKey _columnMasterKey; + private readonly SqlColumnEncryptionCertificateStoreProvider _certStoreProvider = + new SqlColumnEncryptionCertificateStoreProvider(); + + /// + /// The single Column Encryption Key shared by all cases in . + /// + public ColumnEncryptionKey ColumnEncryptionKey { get; } + + public ConversionTestFixture() + { + _columnMasterKey = new CspColumnMasterKey( + DatabaseHelper.GenerateUniqueName("CMK"), + ColumnMasterKeyCertificate.Thumbprint, + _certStoreProvider, + DataTestUtility.EnclaveEnabled); + + ColumnEncryptionKey = new ColumnEncryptionKey( + DatabaseHelper.GenerateUniqueName("CEK"), + _columnMasterKey, + _certStoreProvider); + + foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) + { + SqlConnectionStringBuilder connectionString = new SqlConnectionStringBuilder(connectionStr); + // The AE setup often fails with a connect timeout here; ensure a reasonable minimum. + connectionString.ConnectTimeout = Math.Max(connectionString.ConnectTimeout, 30); + + using SqlConnection sqlConnection = new SqlConnection(connectionString.ConnectionString); + sqlConnection.Open(); + _columnMasterKey.Create(sqlConnection); + ColumnEncryptionKey.Create(sqlConnection); + } + } + + protected override void Dispose(bool disposing) + { + try + { + foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) + { + SqlConnectionStringBuilder connectionString = new SqlConnectionStringBuilder(connectionStr); + // Match the constructor's minimum timeout; AE teardown is prone to connect timeouts, + // and skipping cleanup would leave the shared CMK/CEK behind. + connectionString.ConnectTimeout = Math.Max(connectionString.ConnectTimeout, 30); + + // Key drops are best-effort: a failure here must not prevent the base fixture + // from removing the test certificate from the certificate store (see finally). + try + { + using SqlConnection sqlConnection = new SqlConnection(connectionString.ConnectionString); + sqlConnection.Open(); + ColumnEncryptionKey.Drop(sqlConnection); + _columnMasterKey.Drop(sqlConnection); + } + catch (Exception ex) + { + Console.WriteLine( + $"ConversionTestFixture: failed to drop keys on '{connectionString.DataSource}': {ex.Message}"); + } + } + } + finally + { + base.Dispose(disposing); + } + } + } +} From f3db5d4bb44f24ddbd16436ba33579fe88f78723 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:44:30 -0700 Subject: [PATCH 216/298] Add SqlClient performance test pipeline with baseline comparison, noise reduction, and ADX ingestion (#4473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add SqlClient performance test pipeline for internal Perf Test Lab Adds a new Azure DevOps pipeline that runs the Microsoft.Data.SqlClient BenchmarkDotNet performance tests on the internal "Perf Test Lab" by consuming the reusable extends template v1/Perf.Test.Job.yml from the InternalDriverTools/PerfTest repository (per wiki page 284). Files: - eng/pipelines/perf/sqlclient-perf-pipeline.yml: extends the perf template. - eng/pipelines/perf/scripts/run-perf-tests.sh: Linux on-VM entry point. - eng/pipelines/perf/scripts/run-perf-tests.ps1: Windows on-VM entry point. The on-VM scripts install the pinned .NET SDK (global.json), create the perf database, inject the VM SQL Server connection string into the benchmark runner config, pin the client to the reserved CPU set, run the benchmarks, and collect BenchmarkDotNet artifacts for the template to publish. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4a21e2a3-6efa-45c7-9bfd-81e7f3d24a89 * Add baseline-vs-current perf comparison and Kusto ingestion Extend the SqlClient performance pipeline to: - Run benchmarks against a released NuGet baseline (default 7.0.2, overridable at queue time) and against the branch under test built from source, both CPU-pinned, in two passes on the VM. - Compare the two passes and emit a per-benchmark delta (markdown + json) surfaced as the run summary. - Translate BenchmarkDotNet "full" JSON into the perf-results Kusto schema (PerfRun + PerfBenchmarkResult, wiki 270) and optionally ingest via an ADO service connection (AzureCLI@2 + queued ingestion). Enables JsonExporter.Full and adds a CPM VersionOverride switch (MdsPackageVersion) so the baseline pass can pin a released MDS version restored from NuGet.org through a dedicated single-source config. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4a21e2a3-6efa-45c7-9bfd-81e7f3d24a89 * Set default Kusto ingestion settings for perf pipeline Pre-fill the Kusto parameters so ingestion runs without queue-time input: - kustoClusterUri: https://sqldrivers.westus2.kusto.windows.net - kustoDatabase: PerfResultsTestDB - kustoServiceConnection: PerfLab Infra Deployments Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4a21e2a3-6efa-45c7-9bfd-81e7f3d24a89 * Fix Kusto ingest step: use virtualenv for PEP 668 externally-managed Python The 'Ingest results into Kusto' step ran a system-wide pip install, which Ubuntu agents (Python 3.12, PEP 668) reject with 'externally-managed-environment', failing the step. Install azure-kusto-data/azure-kusto-ingest into an isolated virtualenv under Agent.TempDirectory and run ingest_kusto.py with the venv interpreter instead. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4a21e2a3-6efa-45c7-9bfd-81e7f3d24a89 * Fix Kusto ingest: use pip --user --break-system-packages (no venv) The agent lacks python3-venv/ensurepip, so 'python3 -m venv' fails too. pip itself is present, so install azure-kusto-data/azure-kusto-ingest into the per-user site and bypass the PEP 668 externally-managed marker with --break-system-packages, then run ingest_kusto.py with python3. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4a21e2a3-6efa-45c7-9bfd-81e7f3d24a89 * Fix perf run: set DATATYPES_CONFIG so datatypes.json resolves Each benchmark pass runs from an empty perf-run-